How to generate SQL query from arithmetic relation - sql

How would you parse (that's a simple case) something like that: ${table_1} + ${table_2} + ${table_3}
and generate a sql query such as:
WITH
TMP_1 AS (SELECT date, quantity FROM table_1),
TMP_2 AS (SELECT date, quantity FROM table_2),
TMP_3 AS (SELECT date, quantity FROM table_3)
SELECT time_bucket('1 day', date) AS date,
sum(quantity) AS sum FROM
(
SELECT date, quantity FROM TMP_1
UNION ALL
SELECT date, quantity FROM TMP_2
UNION ALL
SELECT date, quantity FROM TMP_3
) AS res_1
GROUP BY 1
ORDER BY 1;
Do you know of any kind of pseudo language that can be transformed to SQL queries
Update 1
The relation can change depending on user input: ${table_1} + ${table_2} * ( ${table_3} * 60 )

You can do it with sql itself:
do
$$
declare
_i text;
_r text;
begin
_i := '${table_1} + ${table_2} + ${table_3}';
_r := (
with u as (select * from unnest(string_to_array(_i,'+')) with ordinality t(e,o))
, c as (select format('TMP_%s AS (SELECT date, quantity FROM %I)'
, o
, substring(unnest(string_to_array(e,'+')) from '\{(.+)\}')
) from u
)
select string_agg(format,','||chr(10)) from c);
_r := 'WITH '||chr(10)||_r||chr(10)||$s$SELECT time_bucket('1 day', date) AS date,
sum(quantity) AS sum FROM
(
SELECT date, quantity FROM TMP_1
UNION ALL
SELECT date, quantity FROM TMP_2
UNION ALL
SELECT date, quantity FROM TMP_3
) AS res_1
GROUP BY 1
ORDER BY 1;$s$;
raise info '%',_r;
execute _r;
end;
$$
;

Related

why does this function not return the excpected tsrange[]

I have these table with this dataset
CREATE TABLE employee (entity_id uuid, valid tsrange);
INSERT into employee (entity_id, valid)
VALUES
('0006f79d-5af7-4b29-a200-6aef3bb0105f', tsrange('2009-05-23 02:00:00','2010-08-27 02:00:00')),
('0006f79d-5af7-4b29-a200-6aef3bb0105f', tsrange('2010-08-27 02:00:00','2010-10-27 02:00:00')),
('0006f79d-5af7-4b29-a200-6aef3bb0105f', tsrange('2011-05-23 02:00:00','infinity'))
for which I want to select an each continous aggregated tsrange, as an tsrange[], as for a specific entity_id.
For this I have this function
CREATE OR REPLACE FUNCTION tsrange_cluster_aggregate(_tbl regclass, selected_entity_id uuid)
RETURNS SETOF TSRANGE AS
$$
BEGIN
EXECUTE
FORMAT(
'SELECT tsrange(min(COALESCE(lower(valid), ''-infinity'')), max(COALESCE(upper(valid), ''infinity'')))
FROM (
SELECT *, count(nextstart > enddate OR NULL) OVER (ORDER BY valid DESC NULLS LAST) AS grp
FROM (
SELECT valid
, max(COALESCE(upper(valid), ''infinity'')) OVER (ORDER BY valid) AS enddate
, lead(lower(valid)) OVER (ORDER BY valid) As nextstart
FROM %1$I
where entity_id = %2$L
) a
) b
GROUP BY grp
ORDER BY 1;'
, _tbl , selected_entity_id)
USING _tbl, selected_entity_id;
END
$$ LANGUAGE plpgsql;
SELECT tsrange_cluster_aggregate('employee'::regclass, '0006f79d-5af7-4b29-a200-6aef3bb0105f');
Problem here is that output of this function is always empty?
I get the expected output when I run it outside an function?
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=a09f8033091ea56e533880feebd0d5d4
You are missing a RETURN in your function:
CREATE OR REPLACE FUNCTION tsrange_cluster_aggregate(_tbl regclass, selected_entity_id uuid)
RETURNS SETOF tsrange[] AS
$$
BEGIN
RETURN QUERY EXECUTE
FORMAT(
' SELECT array_agg(j) FROM (
SELECT tsrange(min(COALESCE(lower(valid), ''-infinity'')), max(COALESCE(upper(valid), ''infinity'')))
FROM (
SELECT *, count(nextstart > enddate OR NULL) OVER (ORDER BY valid DESC NULLS LAST) AS grp
FROM (
SELECT valid
, max(COALESCE(upper(valid), ''infinity'')) OVER (ORDER BY valid) AS enddate
, lead(lower(valid)) OVER (ORDER BY valid) As nextstart
FROM %1$I
where entity_id = %2$L
) a
) b
GROUP BY grp
ORDER BY 1) z (j);'
, _tbl , selected_entity_id)
USING _tbl, selected_entity_id;
END
$$ LANGUAGE plpgsql;
Demo: db<>fiddle

SQLPlus Pivot Date Column

I have a query where I need to pivot a field that includes multiple dates. In short, the administereddate column needs to be pivoted to 6 columns with the dates from newest to oldest across the row.
My current effort is as follows
SELECT student_number,
wheninput,
whoinput,
certificatetype,
MAX(CASE
WHEN vaccine_name = 'DTP' THEN
TO_CHAR(administereddate, 'mm/dd/yyyy')
ELSE
''
END) AS "DTP"
FROM
(
SELECT TO_CHAR(administeredDate, 'mm/dd/yyyy') dose_date,
ROW_NUMBER() OVER (PARTITION BY vaccine_name ORDER BY administereddate DESC) dose
FROM ps.pshealthgrade1)
PIVOT(
LISTAGG(dose_date) WITHIN GROUP (ORDER BY dose_date DESC)
FOR dose IN (1 DTP_dose1,
2 DTP_dose2,
3 DTP_dose3,
4 DTP_dose4,
5 DTP_dose5,
6 DTP_dose6)
)
)
WHERE vaccine_name = 'DTP'
GROUP BY student_number,
wheninput,
whoinput,
vaccine_name,
administereddate,
certificatetype
ORDER BY student_number, vaccine_name, administereddate;
You need dynamically detecting the latest six vaccine dates probably to be counted per each day. In order to accomplish this, create such a function firstly ;
CREATE OR REPLACE FUNCTION get_vaccine_rs(
i_vcc ps.pshealthgrade1.vaccine_name%TYPE
) RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_str VARCHAR2(32767);
BEGIN
SELECT LISTAGG( ''''||TO_CHAR(administeredDate, 'mm/dd/yyyy')||''' AS "DTP_dose'||rn||'"' , ',' )
WITHIN GROUP ( ORDER BY rn )
INTO v_str
FROM ( SELECT *
FROM
( SELECT administeredDate, ROW_NUMBER() OVER (ORDER BY administeredDate DESC) AS rn
FROM ( SELECT DISTINCT administeredDate
FROM ps.pshealthgrade1
WHERE vaccine_name = i_vcc )
)
WHERE rn <= 6 );
v_sql :=
'SELECT *
FROM ps.pshealthgrade1
PIVOT
(
COUNT(*) FOR administeredDate IN ( '|| v_str ||' )
)
';
OPEN v_recordset FOR v_sql USING i_vcc;
RETURN v_recordset;
END;
/
Then run the below code :
VAR rc REFCURSOR
EXEC :rc := get_vaccine_rs('DTP');
PRINT rc
from SQL Developer's Command Line in order to see the result set.

PL/SQL: ORA-01744: inappropriate INTO

I want to store the item I get from the loop into the variable I created and print it out, but I faced this error
28/7 PL/SQL: SQL Statement ignored
30/39 PL/SQL: ORA-01744: inappropriate INTO
Here are my loop and my declare:
v_num1 number;
v_menuid varchar(6);
v_menuname varchar(100);
v_stock number;
...
FOR loop_counter in 1..v_num1
LOOP
SELECT *
FROM ( SELECT a.*, rownum rnum
FROM ( SELECT menuitemid INTO v_menuid, menuitemname INTO v_menuname, quantityinstock INTO v_stock
FROM menuitem m, restaurant r
WHERE m.restaurantid = r.restaurantid AND restaurantname = IN_ResName AND quantityinstock <= 10) a
WHERE rownum <= loop_counter )
WHERE rnum >= loop_counter;
DBMS_OUTPUT.PUT_LINE(chr(10));
DBMS_OUTPUT.PUT_LINE('Menu ID: '||v_menuid);
DBMS_OUTPUT.PUT_LINE('Menu Name: '||v_menuname);
DBMS_OUTPUT.PUT_LINE('Quantity In Stock: '||v_stock);
END LOOP;
I want to print out the result like :
Menu ID: M00001
Menu Name: Burger
Stock : 10
Only one INTO statement per projection.
The INTO statement belongs in the outermost projection.
SELECT menuitemid , menuitemname, quantityinstock
INTO v_menuid, v_menuname, v_stock
FROM ( SELECT a.*, rownum rnum
FROM ( SELECT menuitemid, menuitemname, quantityinstock
FROM menuitem m, restaurant r
WHERE m.restaurantid = r.restaurantid AND restaurantname = IN_ResName AND quantityinstock <= 10) a
WHERE rownum <= loop_counter )
WHERE rnum >= loop_counter;

PL/SQL procedure to output line the given date if not existing, latest date should be given

I have this table informationvalues with the contents:
Now I create a procedure where I need to input a date parameter which should output line the correct attr with given price. If the date doesn't exist the latest date should be selected.
The solution table for to_date('01-jan-19') would look like this:
This would be then output line in the procedure.
Should I select to correct tuple and output line it or would it be best to just bulk collect everything and then check in a for loop with an if statement what tuple I need to display.
What I have so far:
A select statement with the tuples I am looking for:
create or replace procedure print_inf_value(closingDate Date) is
cursor d1 (closingDate Date) is
select t.attr, t.dateOfValue, t.price
from (
select i.*,
row_number() over (
partition by attr
order by case when dateOfValue = closingdate then 1 else 2 end, dateOfValue desc
) rn
from InformationValues i
) t
where t.rn = 1;
BEGIN
dbms_output.put_line('Information Value ');
dbms_output.put_line('--------------------------------');
FOR d1_rec IN d1 LOOP
dbms_output.put_line(d1_rec.attr || ' ' || d1_rec.price );
END LOOP;
END;
Or a procedure where I bulk collect everything and then I need to sort out what tuple I need:
create or replace procedure print_inf_value(closingDate Date) is
TYPE d1 IS TABLE OF informationvalues%rowtype;
emps d1;
begin select * bulk collect into emps
from informationvalues;
FOR i IN 1 .. emps.COUNT LOOP
if emps(i).dateofvalue = closingDate then
dbms_output.put_line(emps(i).attr || ' ' || emps(i).price );
/*else*/
end if;
END LOOP;
END;
Both are not working right, so what am I missing to display tuple with the correct date.
Please try:
CREATE OR REPLACE PROCEDURE print_inf_value (closingDate DATE)
IS
BEGIN
DBMS_OUTPUT.put_line (RPAD ('ATTR', 20) || RPAD ('PRICE', 20));
FOR o
IN (select attr, trim(case when price < 1 then to_char(price,90.9) else to_char(price) end) price from (
select attr, price, dateofvalue,
row_number() over (partition by attr order by dateofvalue desc) rn from informationvalues
) i where dateofvalue = closingdate
or (rn = 1 and not exists (select 1 from informationvalues iv where iv.attr = i.attr and dateofvalue = closingdate) )
)
LOOP
DBMS_OUTPUT.put_line (RPAD (o.attr, 20) || RPAD ( o.price, 20));
END LOOP;
END;
Sample execution:
set serveroutput on;
begin
print_inf_value(date'2019-01-01');
end;
Output:
ATTR PRICE
age 2
electronics 0.5
gender 3
hobbies 0.5
homeAddress 7
maritalStatus 1
mobilePhone 5
musicTaste 0.1
socialContacts 1

Use a function to handle a negative offset

I am creating a Postgresql function in order to handle a negative offset. When the offset is negative then my function should return 0. However, when I try to run this function, there is an error near "row", I am not sure why. What is wrong with my conditional branch?
CREATE OR REPLACE FUNCTION
calc_offset(row integer, padding integer) RETURNS integer AS $$
BEGIN
if (row-1-padding) < 0 then return 0;
else return (row-1-padding);
END;
$$ LANGUAGE plpgsql;
WITH all_the_trimmings AS (
SELECT ui.id, ui.name, ui.time,
row_number() over(order by name asc) as rownumber
FROM user_infos ui
), my_row AS (
SELECT * FROM all_the_trimmings WHERE id=1
), the_slice AS (
SELECT * FROM all_the_trimmings LIMIT 5
OFFSET calc_offset((SELECT rownumber FROM my_row)::int,2)
)
SELECT * from my_row
UNION ALL
SELECT * from the_slice;
SQL fiddle for the full picture
Your function calc_offset() can be replaced with this simple SQL function:
CREATE OR REPLACE FUNCTION calc_offset(p_row int, padding int)
RETURNS int AS 'SELECT GREATEST ($1 - $2 - 1, 0)' LANGUAGE sql;
And you don't need the function at all. Just use GREATEST with a subselect:
WITH all_the_trimmings AS (
SELECT ui.id, ui.name, ui.time, row_number() OVER (ORDER BY name) AS rn
FROM user_infos ui
)
, my_row AS (
SELECT * FROM all_the_trimmings WHERE id=1
)
SELECT * FROM my_row
UNION ALL
SELECT * FROM all_the_trimmings LIMIT 5
OFFSET GREATEST ((SELECT rn - 3 FROM my_row), 0);
Or simpler:
WITH cte AS (
SELECT ui.id, ui.name, ui.time, row_number() OVER (ORDER BY name) AS rn
FROM user_infos ui
)
SELECT * FROM cte WHERE id = 1
UNION ALL
SELECT * FROM cte LIMIT 5
OFFSET GREATEST ((SELECT rn - 3 FROM cte WHERE id = 1), 0);
SQL Fiddle showing both.
The row parameter is a reserved word. Try replacing it with another name. You were also missing an end if;
CREATE OR REPLACE FUNCTION
calc_offset(p_row integer, padding integer) RETURNS integer AS $$
BEGIN
if (p_row-1-padding) < 0 then
return 0;
else
return (p_row-1-padding);
end if;
END;
$$ LANGUAGE plpgsql;