How to use FOREACH to query multiple times using the values in an aray - postgresql

I want to run a query and get the rows by iterating over an array using FOREACH loop in postgresql.
I have tried using the FOREACH loop as explained in the docs but it returns an error "cannot use RETURN QUERY in a non-SETOF function"
DO
$do$
DECLARE
a integer[] := array[1,2,3];
i integer;
begin
foreach i IN ARRAY a
LOOP
RETURN QUERY
select
models.sku,
(sum(models.unitretailprice) * sum(coefficients.unit_retail_price)) +
(sum(models.flag::int) * sum(coefficients.flag::int)) +
(sum(models.mc_baseline) * sum(coefficients.mc_baseline)) +
(sum(models.mc_day_avg) * sum(coefficients.mc_day_avg)) +
(sum(models.mc_day_normal) * sum(coefficients.mc_day_normal)) +
(sum(models.mc_week_avg) * sum(coefficients.mc_week_avg)) +
(sum(models.mc_week_normal) * sum(coefficients.mc_week_normal)) +
(sum(models.sku_day_avg) * sum(coefficients.sku_day_avg)) +
(sum(models.sku_month_avg) * sum(coefficients.sku_month_avg)) +
(sum(models.sku_month_normal)* sum(coefficients.sku_month_normal)) +
(sum(models.sku_moving_avg) * sum(coefficients.sku_moving_avg)) +
(sum(models.sku_week_avg) * sum(coefficients.sku_week_avg)) +
(sum(models.sku_week_normal)* sum(coefficients.sku_week_normal)) as baseline,
(i * sum(coefficients.f)) +
(5 * sum(coefficients.p)) +
(0 * sum(coefficients.a)) as promoIncremental,
(sum(models.basket_dollar_off) * sum(coefficients.basket_dollar_off)) +
(sum(models.basket_per_off) * sum(coefficients.basket_per_off)) +
(sum(models.category_dollar_off) * sum(coefficients.category_dollar_off)) +
(sum(models.category_per_off) * sum(coefficients.category_per_off)) +
(sum(models.disc_per) * sum(coefficients.disc_per)) as couponIncremnetal
from
models join coefficients
on
models.sku = coefficients.sku
and
models.si_type = coefficients.si_type
and
models.model_type = coefficients.model_type
where
coefficients.sku in ('12841276', '11873916') and coefficients.shop_descr = 'Papercrafting Technology'
group by models.sku ;
END LOOP;
end
$do$

You can cross join a CTE with the factors to a derived table that is made of your current table.
WITH a (i)
AS
(
VALUES (1),
(2),
(3)
)
SELECT ...
a.i * x.sum_f + 5 * x.sum_p promoincremental,
...
FROM a
CROSS JOIN (SELECT ...
sum(coefficients.f) sum_f,
sum(coefficients.p) sum_p,
...
FROM ...) x;

Related

PostgreSQL column does not exist when sub select

I have a table called user, and I need to get another users in an expecific distance range and the distance between them. My query is this:
select ( 6371 * acos(
cos( radians(users.latitude) )
* cos( radians(-22.9035) )
* cos( radians(-43.2096) - radians(users.longitude) )
+ sin( radians(users.latitude) )
* sin( radians(-22.9035) )
)
) as distance, users from users where users.id != 41 and distance > 50
I need to recover the user list and the distance, but the "as distance" is not working:
ERROR: column "distance" does not exist.
I tried with:
with distance as (
select ( 6371 * acos(
cos( radians(users.latitude) )
* cos( radians(-22.9035) )
* cos( radians(-43.2096) - radians(users.longitude) )
+ sin( radians(users.latitude) )
* sin( radians(-22.9035) )
)
) from users
)
select * from users where users.id != 41 and distance > 50
However, the error remains the same.
In Postgres, you cannot use column aliases in a where clause. That's why your first query does not work.
Your second query using a CTE can be made to work, but distance is a virtual table, not a column. Join with it.
with users_distance as (
select
id,
( 6371 * acos(
cos( radians(users.latitude) )
* cos( radians(-22.9035) )
* cos( radians(-43.2096) - radians(users.longitude) )
+ sin( radians(users.latitude) )
* sin( radians(-22.9035) )
)
) as distance
from users
)
select *
from users
join users_distance ud on users.id = ud.id
where users.id != 41 and ud.distance > 50
If you do this a lot, consider adding a generated column to the table so you don't have to recalculated it all the time.
alter table users add column distance numeric
generated always as (
6371 * acos(
cos( radians(users.latitude) )
* cos( radians(-22.9035) )
* cos( radians(-43.2096) - radians(users.longitude) )
+ sin( radians(users.latitude) )
* sin( radians(-22.9035) )
)
) stored
Try it
Finally, rather than doing these calculations yourself, consider using the very powerful PostGIS extension.

How to factor this postgis query with subquery?

I have a query to found POIs around trace :
SELECT
ptb.* AS pois
FROM traces tr, pois pta, pois ptb
WHERE tr.id = #{trace.id}
AND pta.id = #{poi.id}
AND ST_DWithin(
ST_LineSubstring(
tr.path,
ST_LineLocatePoint(tr.path, pta.lonlat::geometry) + (#{dist} * 1000) / ST_Length(tr.path, false),
ST_LineLocatePoint(tr.path, pta.lonlat::geometry) + (#{end_point} * 1000) / ST_Length(tr.path, false)
)::geography,
ptb.lonlat::geography,
4000)
How can I use subquery for :
ST_LineLocatePoint(tr.path, pta.lonlat::geometry)
ST_Length(tr.path, false)
EDIT :
WITH RECURSIVE locate_point_a AS (
select ST_LineLocatePoint(tr.path, pta.lonlat::geometry) AS locate_point_a
FROM traces tr, pois pta
WHERE tr.id = 2
AND pta.id = 2
)
SELECT
ptb.* AS pois
FROM traces tr, pois pta, pois ptb, locate_point_a
WHERE tr.id = 2
AND pta.id = 2
AND ST_DWithin(
ST_LineSubstring(
tr.path,
locate_point_a + (25 * 1000) / ST_Length(tr.path, false),
locate_point_a + (250 * 1000) / ST_Length(tr.path, false)
)::geography,
ptb.lonlat::geography,
4000)
SQL
This should do the same (but I do not understand the logic, TBH):
SELECT
ptb.* -- AS pois
FROM pois ptb
WHERE EXISTS (
SELECT *
FROM traces tr
JOIN pois pta ON ST_DWithin(
ST_LineSubstring( tr.path
, ST_LineLocatePoint(tr.path, pta.lonlat::geometry) + (#{dist} * 1000) / ST_Length(tr.path, false)
, ST_LineLocatePoint(tr.path, pta.lonlat::geometry) + (#{end_point} * 1000) / ST_Length(tr.path, false)
)::geography
, ptb.lonlat::geography
, 4000
)
WHERE tr.id = #{trace.id}
AND pta.id = #{poi.id}
;

Column in having clause does not exist issue in Doctrine and pgadmin

I am running a query in pgadmin but facing issue column distance does not exist
select f.title, f.longitude, f.latitude, (3959 * cos(cos(radians('52.512452')) * cos(radians(latitude)) * cos(radians(longitude) - radians('13.390931')) + sin(radians('52.512452')) * sin(radians(latitude)))) AS distance from fitness_studio f having distance<1 order by distance desc
Thanks in advance for any help.
Regards,
Aisha
As far as I know postgresql has no way to directly use an alias column in where clause. So you should either try to duplicate the logic:
SELECT
f.title,
f.longitude,
f.latitude,
(3959 * cos(cos(radians('52.512452')) * cos(radians(latitude)) * cos(radians(longitude)
- radians('13.390931')) + sin(radians('52.512452')) * sin(radians(latitude)))) AS distance
FROM fitness_studio f
WHERE (3959 * cos(cos(radians('52.512452')) * cos(radians(latitude)) *
cos(radians(longitude) - radians('13.390931')) +
sin(radians('52.512452')) * sin(radians(latitude)))) < 1
ORDER BY distance DESC
either to use a subquery:
WITH container AS (
SELECT
f.title,
f.longitude,
f.latitude,
(3959 * cos(cos(radians('52.512452')) * cos(radians(latitude)) *
cos(radians(longitude) - radians('13.390931')) +
sin(radians('52.512452')) * sin(radians(latitude)))) AS distance
FROM fitness_studio f)
SELECT *
FROM container
WHERE distance < 1
ORDER BY distance DESC
Please keep in mind that using such subquery may negatively affect execution plan and when your table is large enough execution speed becomes more important than query awkwardness.
PS: Note that ORDER BY may correctly get alias as parameter. Suppose it's cause ORDER BY doesn't affect selected rows, it just rotates them. Same picture with GROUP BY

How can I create a datatable of my 3 select SQL statements

I have a database with one table and I have 3 select SQL statement.
Those select items have different conditions. How can I merge the answer of this 3 SQL command?
I don't want to merge them, rows by rows to a data table. Is there any other way?
Some thing like this..
OleDbCommand cmd = new OleDbCommand("select top "+ cont0 +" * from (select * from db where tablenumber=0) order by ID ASC", mycon);
OleDbDataAdapter adapt=new OleDbDataAdapter(cmd);
DataTable dt = new DataTable();
DataTable dttemp = new DataTable();
adapt = new OleDbDataAdapter(cmd);
adapt.Fill(dt);
cmd = new OleDbCommand("select top "+ cont1 +" * from (select * from db where tablenumber=1) order by ID ASC", mycon);
adapt = new OleDbDataAdapter(cmd);
adapt.Fill(dttemp);
foreach (DataRow row in dttemp.Rows)
{
dt.Rows.Add(row.ItemArray);
}
if (cont2 != 0)
{
cmd = new OleDbCommand("select top " + cont2 + " * from (select * from db where tablenumber=2) order by ID ASC", mycon);
adapt = new OleDbDataAdapter(cmd);
dttemp = new DataTable();
adapt.Fill(dttemp);
foreach (DataRow row in dttemp.Rows)
{
dt.Rows.Add(row.ItemArray);
}
}
if (cont3 != 0)
{
cmd = new OleDbCommand("select top " + cont3 + " * from (select * from db where tablenumber=3) order by ID ASC", mycon);
adapt = new OleDbDataAdapter(cmd);
dttemp = new DataTable();
adapt.Fill(dttemp);
foreach (DataRow row in dttemp.Rows)
{
dt.Rows.Add(row.ItemArray);
}
}
if (cont4 != 0)
{
cmd = new OleDbCommand("select top " + cont4 + " * from (select * from db where tablenumber=4) order by ID ASC", mycon);
adapt = new OleDbDataAdapter(cmd);
dttemp = new DataTable();
adapt.Fill(dttemp);
foreach (DataRow row in dttemp.Rows)
{
dt.Rows.Add(row.ItemArray);
}
}
if (cont5 != 0)
{
cmd = new OleDbCommand("select top " + cont5 + " * from (select * from db where tablenumber=5) order by ID ASC", mycon);
adapt = new OleDbDataAdapter(cmd);
dttemp = new DataTable();
adapt.Fill(dttemp);
foreach (DataRow row in dttemp.Rows)
{
dt.Rows.Add(row.ItemArray);
}
}
You can merge them simply like this:
"select * from
(select *, row_number() over(partition by field order by id) as rn
from table1 where field in(0, 5, 9)) t
where rn <= " + cont
EDIT:
string command = "select * from ( select top "+ cont0 +" * from db where tablenumber=0 order by ID) t union all
select * from ( select top "+ cont1 +" * from db where tablenumber=1 order by ID) t";
if (cont2 != 0)
comand += " union all select * from ( select top " + cont2 + " * from db where tablenumber=2 order by ID) t";
if (cont3 != 0)
comand += " union all select * from ( select top " + cont3 + " * from db where tablenumber=3 order by ID) t";
....
OleDbCommand cmd = new OleDbCommand(command, mycon);
OleDbDataAdapter adapt=new OleDbDataAdapter(cmd);
DataTable dt = new DataTable();
....

Using aggregate functions on alias?

I want to make a query where I am computing the difference between two columns. Something like:
SELECT a,
b,
a - b as "diff"
FROM ...
Now I would like to calculate the stddev of the "diff" column using postgresql built-in stddev aggregate function. How can I achieve this?
Thanks.
EDIT:
The actual query is this:
SELECT tr.date_start,
tr.date_end,
(((CASE when(tourney_summary.val_curr_conv != 0) THEN tourney_summary.val_curr_conv * (tr.amt_won + tr.cnt_bounty * tourney_summary.amt_bounty) ELSE 0.0 END))) AS "amt_won_curr_conv",
(((CASE when(tourney_summary.val_curr_conv != 0) THEN tourney_summary.val_curr_conv * (tourney_summary.amt_buyin + tourney_summary.amt_fee + tourney_summary.amt_rebuy * tr.cnt_rebuy + tourney_summary.amt_addon * tr.cnt_addon + tourney_summary.amt_bounty) ELSE 0.0 END))) AS "amt_buyin_ttl_curr_conv",
((((CASE when(tourney_summary.val_curr_conv != 0) THEN tourney_summary.val_curr_conv * (tr.amt_won + tr.cnt_bounty * tourney_summary.amt_bounty) ELSE 0.0 END))) - (((CASE when(tourney_summary.val_curr_conv != 0) THEN tourney_summary.val_curr_conv * (tourney_summary.amt_buyin + tourney_summary.amt_fee + tourney_summary.amt_rebuy * tr.cnt_rebuy + tourney_summary.amt_addon * tr.cnt_addon + tourney_summary.amt_bounty) ELSE 0.0 END)))) as net_amt_won,
stddev((((CASE when(tourney_summary.val_curr_conv != 0) THEN tourney_summary.val_curr_conv * (tr.amt_won + tr.cnt_bounty * tourney_summary.amt_bounty) ELSE 0.0 END))) - (((CASE when(tourney_summary.val_curr_conv != 0) THEN tourney_summary.val_curr_conv * (tourney_summary.amt_buyin + tourney_summary.amt_fee + tourney_summary.amt_rebuy * tr.cnt_rebuy + tourney_summary.amt_addon * tr.cnt_addon + tourney_summary.amt_bounty) ELSE 0.0 END)))) as diff_std_dev
FROM tourney_summary,
tourney_results tr
WHERE
tr.id_player=1
AND tourney_summary.id_tourney = tr.id_tourney
AND ((tourney_summary.id_gametype = 1)
AND (((((((tourney_summary.id_table_type IN
(SELECT lttt.id_table_type
FROM tourney_table_type lttt
WHERE lttt.val_seats = 2))))))
AND (((((tourney_summary.id_table_type IN
(SELECT lttt.id_table_type
FROM tourney_table_type lttt
WHERE position('S' IN lttt.val_speed) > 0))
OR (tourney_summary.id_table_type IN
(SELECT lttt.id_table_type
FROM tourney_table_type lttt
WHERE position('H' IN lttt.val_speed) > 0))))))))
AND ((tourney_summary.date_start >= '2013/08/15 23:00:00')))
GROUP BY tr.date_start,
tr.date_end,
tourney_summary.val_curr_conv,
tr.amt_won,
tr.cnt_bounty,
tourney_summary.amt_bounty,
tourney_summary.amt_buyin,
tourney_summary.amt_fee,
tourney_summary.amt_rebuy,
tr.cnt_rebuy,
tourney_summary.amt_addon,
tr.cnt_addon
ORDER BY tr.date_end DESC;
The "a" and "b" expressions (the ones with CASE) are big. And I don't know how to avoid the copy/paste. In any case using stddev on the a-b expression returns a blank column. What am I doing wrong?
Thanks.
You pretty much answer it yourself. Calculate the standard deviation of the difference:
SELECT a,
b,
a - b as "diff",
stddev(a - b) AS "diff_stddev"
FROM ...
If a - b is a computationally expensive operation or is in fact a much more complex expression in reality, you can wrap it in a subquery:
SELECT a, b, "diff", stddev("diff") AS diff_stddev
FROM (
SELECT a, b, a - b
FROM ...
) x (a, b, "diff")
x is just a throw-away alias for the subquery table.
it's also possible to do this with cte
with cte1 as (
select a, b, a - b as diff
from ...
)
select
a, b, diff, stddev(diff) as diff_stddev
from cte1