Let's say I have this function sp_func(startdate, enddate).
To call the function itself I use CALL sp_func(startdate, enddate);.
The function will return a table consisting of:
id name time
1 smith 3.2
2 neo 8.2
3 morpheus 2.4
What I want to do is to average the time from that function.
I tried with SELECT AVG(time) FROM sp_func(startdate, enddate) but it didn't work. Syntax error occurred. I'm not sure what's the exact syntax for this. Any help is greatly appreciated.
The magic keyword is TABLE. Here's a script demonstrating the solution:
Dummy procedure
CREATE PROCEDURE sp_func(begin_date DATE, end_date DATE)
RETURNING INTEGER AS id, VARCHAR(10) AS NAME, DECIMAL(5,2) AS TIME;
RETURN 1, "smith", 3.2 WITH RESUME;
RETURN 2, "neo", 8.2 WITH RESUME;
RETURN 3, "morpheus", 2.4;
END PROCEDURE;
EXECUTE PROCEDURE sp_func('2012-01-01', '2012-12-31');
Output
1 smith 3.20
2 neo 8.20
3 morpheus 2.40
Main SELECT statement
SELECT AVG(a.TIME)
FROM TABLE(PROCEDURE sp_func('2012-01-01', '2012-12-31')) AS A(id, name, time)
;
Output
4.6
The AS clause with the column names was necessary, even though the procedure names its return values.
This syntax is documented in the Informix Guide to SQL: Syntax manual, under the SELECT statement in chapter 2, and the sub-heading is 'Iterator Functions (IDS)' (p2-638 in the copy of ids-sqs_bookmap.pdf for 11.70 that I have).
Related
I have a select that return a single column, using as where clause 4 columns, and I created a specific index for this query. When I do this select using Dbeaver, the result return in 30~50 milliseconds.
SELECT
column1
FROM
myTable
WHERE
column2 = a
AND column3 = b
AND (column4 = c AND column5 = d )
FOR READ ONLY WITH UR;
Now I created a procedure with this same simple select. The proc declare a P1, a 'cursor with return', do the select, open the cursor and close the P1. When I use the same dbeaver connection the result is returning between 1.2 ~ 1.6 seconds.
CREATE PROCEDURE myProc (
IN a DECIMAL(10),
IN b DECIMAL(10),
IN c DECIMAL(10),
IN d DECIMAL(10)
)
LANGUAGE SQL
DYNAMIC RESULT SETS 1
SPECIFIC myProc
P1: BEGIN
DECLARE cursor1 CURSOR WITH RETURN for
SELECT
column1
FROM
myTable
WHERE
column2 = a
AND column3 = b
AND (column4 = c AND column5 = d )
FOR READ ONLY WITH UR;
OPEN cursor1;
END P1
Is this huge return difference correct? If wrong, Is there something wrong in my procedure that justifies this return time difference? Or could be something wrong in the DB configuration or the server that justifies this difference, something like few resources in the DB server or something like the proc not using the index( I don't know if procs in the DB2 use the index by default like the queries )?
I am new in DB2 and new in procedures creation. Thank you in advance.
Best regards,
Luis
I don't know if is the best way, but I solved the problem using a solution that I read for SQL Server. The solution is create local variables that will receive the parameters values, and use the variable in the queries, to guarantee always the best execution plan. I didn't knew if this was valid to DB2, but my proc now has almost the same response time compared to query. Worked!
Link of SQL Server post: SQL Server: Query fast, but slow from procedure
In this link above a user called #Jake give this explanation:
"The reason this happens is because the procedures query plan is being cached, along with the parameters that were passed to it. On subsequent calls, this query plan generated will be reused with new parameters. This can cause problems because if the data is unevenly distributed, one parameter can generate a sub-optimal plan vs. another. Using local variables essentially does the same as OPTIMIZE FOR UNKNOWN because local variables cannot be sniffed."
I think that is the same for DB2, because worked. After I change these old procedures to use local variables my execution plan begun to use the indexes recently created
I am trying to write a BigQuery SQL function / stored procedure / table function that accepts as input:
a INT64 filter for the WHERE clause,
a table name (STRING type) as fully qualified name e.g. project_id.dataset_name.table_name
The idea is to dynamically figure out the table name and provide a filter to slice the data to return as a table.
However if try to write a Table Function (TVF) and I use SET to start dynamically writing the SQL to execute, then I see this error:
Syntax error: Expected "(" or keyword SELECT or keyword WITH but got keyword SET at [4:5]
If I try to write a stored procedure, then it expects BEGIN and END and throws this error:
Syntax error: Expected keyword BEGIN or keyword LANGUAGE but got keyword AS at [3:1]
If I try to add those, then I get various validation errors basically because I need to remove the WITH using CTEs (Common Table Expression), and semicolons ; etc.
But what I am really trying to do is using a table function:
to combine some CTEs dynamically with those inputs above (e.g. the input table name),
to PIVOT that data,
to then eventually return a table as a result of a SELECT.
A bit like producing a View that could be used in other SQL queries, but without creating the view (because the slice of data can be decided dynamically with the other INT64 input filter).
Once I dynamically build the SQL string I would like to EXECUTE IMMEDIATE that SQL and provide a SELECT as a final step of the table function to return the "dynamic table".
The thing is that:
I don't know before runtime the name of this table.
But I have all these tables with the same structure, so the SQL should apply to all of them.
Is this possible at all?
This is the not-so-working SQL I am trying to work around. See what I am trying to inject with %s and num_days:
CREATE OR REPLACE TABLE FUNCTION `my_dataset.my_table_func_name`(num_days INT64, fqn_org_table STRING)
AS (
-- this SET breaks !!!
SET f_query = """
WITH report_cst_t AS (
SELECT
DATE(start) as day,
entity_id,
conn_sub_type,
FROM `%s` AS oa
CROSS JOIN UNNEST(oa.connection_sub_type) AS conn_sub_type
WHERE
DATE(start) > DATE_SUB(CURRENT_DATE(), INTERVAL num_days DAY)
AND oa.entity_id IN ('my-very-long-id')
ORDER BY 1, 2 ASC
),
cst AS (
SELECT * FROM
(SELECT day, entity_id, report_cst_t FROM report_cst_t)
PIVOT (COUNT(*) AS connection_sub_type FOR report_cst_t.conn_sub_type IN ('cat1', 'cat2','cat3' ))
)
""";
-- here I would like to EXECUTE IMMEDIATE !!!
SELECT
cst.day,
cst.entity_id,
cst.connection_sub_type_cat1 AS cst_cat1,
cst.connection_sub_type_cat2 AS cst_cat2,
cst.connection_sub_type_cat3 AS cst_cat3,
FROM cst
ORDER BY 1, 2 ASC
);
This might not be satisfying but since Procedural language or DDL are not allowed inside Table functions currently, one possible way around would be simply using PROCEDURE like below.
CREATE OR REPLACE PROCEDURE my_dataset.temp_procedure(filter_value INT64, table_name STRING)
BEGIN
EXECUTE IMMEDIATE FORMAT(CONCAT(
"SELECT year, COUNT(1) as record_count, ",
"FROM %s ",
"WHERE year = %d ",
"GROUP BY year ",
"; "
), table_name, filter_value);
END;
CALL my_dataset.temp_procedure(2002, 'bigquery-public-data.usa_names.usa_1910_current');
My Agenda is to store the counts of the 2 tables ( being passed in the parameter ) and then do some more operations upon comparing the both.
PROBLEM -
Stored Procedure throwing Error :
CREATE OR REPLACE PROCEDURE dev.gp_count_matching_20191204(actual_tablename character varying(256), bkp_tablename character varying(256))
LANGUAGE plpgsql
AS $$
DECLARE
actual_table_name varchar(256);
backup_table_name varchar(256);
actual_count_query varchar(1024);
actual_count int;
backup_count_query varchar(1024);
backup_count int;
BEGIN
call dev.gp_test_error_handling_tablename_format(actual_tablename);
call dev.gp_test_error_handling_tablename_format(bkp_tablename);
actual_count:=(select count(*) as counts from actual_tablename);
--raise info 'Actual Table Name - %, Actual Table Count - %',actual_tablename,actual_count;
end;
$$
This throws the following Error while creating the stored procedure-
An error occurred when executing the SQL command:
CREATE OR REPLACE PROCEDURE dev.gp_count_matching_20191204(actual_tablename character varying(256), bkp_tablename character varying(256))
LANGUAGE pl...
[Amazon](500310) Invalid operation: syntax error at or near "$1";
1 statement failed.
Execution time: 0.99s
If I comment out the actual_count:=(select count(*) as counts from actual_tablename);
then the Stored Procedure gets created Successfully.
I guess it has something to do with me using the parameter ( since $1 points the first parameter ) in the query.
Since I am pretty new with Stored procedure, I unable to figure out the exact problem.
Thanks in Advance.
You need to use EXECUTE when running dynamic SQL. In your example the query is in parentheses but nothing is making it execute. To execute the query into a variable you using the INTO syntax
sql := 'SELECT …'
EXECUTE sql_var INTO result_var;
Please see the example Stored Procedures in our GitHub repo "Amazon Redshift Utils". https://github.com/awslabs/amazon-redshift-utils/tree/master/src/StoredProcedures
There are several examples that use dynamic SQL, such as https://github.com/awslabs/amazon-redshift-utils/blob/master/src/StoredProcedures/sp_split_table_by_range.sql
You can also use the below-given syntax:
SELECT INTO Count count(*) from table_name;
By doing this you are inserting the value of count(*) into variable Count. It works.
I made a procedure that uses a cursor to generate a report. It is designed to return products with p_qoh > avg(p_qoh).
When i run the cursor on its own outside of the procedure, APEX tells me
PLS-00204: function or pseudo-column 'AVG' may be used inside a SQL
statement only
How is this not inside a SQL statement? Im new to SQL just a required class as a comp sci major.
Heres the whole block. If you run just the cursor you will see what I mean.
CREATE OR REPLACE PROCEDURE prod_rep
IS
CURSOR cur_qoh IS
SELECT p_qoh, p_descript, p_code
FROM xx_product;
TYPE type_prod IS RECORD(
prod_qoh xx_product.p_qoh%TYPE,
prod_code xx_product.p_code%TYPE,
prod_descr xx_product.p_descript%TYPE);
rec_prod type_prod;
BEGIN
OPEN cur_qoh;
LOOP
FETCH cur_qoh INTO rec_prod;
EXIT WHEN cur_qoh%NOTFOUND;
IF rec_prod.prod_qoh > avg(rec_prod.prod_qoh) THEN
DBMS_OUTPUT.PUT_LINE(rec_prod.prod_code||' -> '||rec_prod.prod_desc);
END IF;
END LOOP;
CLOSE cur_qoh;
END;
UPDATE: working block
BEGIN
FOR cur_r IN (SELECT p_qoh, p_descript, p_code FROM xx_product
WHERE p_qoh > (SELECT avg(p_qoh) FROM xx_product))
LOOP
DBMS_OUTPUT.PUT_LINE(cur_r.p_code||' -> '|| cur_r.p_descript);
END LOOP;
END;
Well, yes - this is (PL/)SQL, but you just can't use AVG that way. Have a look: this is an example based on Scott's schema.
Average salary is
SQL> select avg(sal) from emp;
AVG(SAL)
----------
2077,08333
In order to select salaries higher than the average one, you'd use a subquery. Let's call this query the "A" query (for future reference):
SQL> select ename, sal
2 from emp
3 where sal > (select avg(sal) from emp);
ENAME SAL
---------- ----------
JONES 2975
BLAKE 2850
CLARK 2450
KING 5000
FORD 3000
SQL>
Also, no need to declare that much things - a simple cursor FOR loop is easier to maintain:
SQL> create or replace procedure prod_rep as
2 begin
3 for cur_r in (select ename, sal from emp
4 where sal > (select avg(sal) from emp))
5 loop
6 dbms_output.put_line(cur_r.ename ||' '|| to_char(cur_r.sal, '9990'));
7 end loop;
8 end;
9 /
Procedure created.
SQL> begin prod_rep; end;
2 /
JONES 2975
BLAKE 2850
CLARK 2450
KING 5000
FORD 3000
PL/SQL procedure successfully completed.
SQL>
See? No need to declare a cursor (OK, you do use that SELECT in a loop), type & record of that type, open the cursor, worry when to exit a loop, close the cursor.
Your code, however, doesn't make much sense in Apex environment. There's no DBMS_OUTPUT there, and it is rather unusual to create a report (either a classic or interactive one) using a procedure; I've never done that. I've used a function (which is a PL/SQL code) that returns a SQL query and based reports on that.
Your problem is a simple one, so - use the Wizard, create a report and - as its source - use the "A" query (I mentioned earlier). That's all you should do.
At that point you're into PL/SQL, not SQL.
You could use the analytic function AVG into your SQL query to get the average:
CURSOR cur_qoh IS
SELECT p_qoh, p_descript, p_code, AVG(p_goh) OVER () as p_goh_avg
FROM xx_product;
You can add your new field to type_prod:
TYPE type_prod IS RECORD(
prod_qoh xx_product.p_qoh%TYPE,
prod_code xx_product.p_code%TYPE,
prod_descr xx_product.p_descript%TYPE,
prod_avg xx_product.p_qoh%TYPE)
;
And then you can use your average into the LOOP:
IF rec_prod.prod_qoh > rec_prod.prod_avg THEN
It doesn't make sense to use AVG inside a PL/SQL loop. PL/SQL is procedural, at that point you're not dealing with the whole set of data: you manage only one row at a time. Whereas SQL deals with sets of data.
Using the analytic function, you get the average of the column on every row, so you can manage it inside the PL/SQL loop.
Is there a way to create postgres stored function (using plpgsql to be able to set input parameters) that returns a custom data set?
I've tried to do something like this according to official manual:
CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
RETURN QUERY SELECT quantity, quantity * price FROM sales
WHERE itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;
but result is an array with only one column which contains type (quantity, total), but I need to get two column array with 'quantity' column and 'total' column.
At a guess you're running:
SELECT extended_sales(1);
This will return a composite type column. If you want it expanded, you must instead run:
SELECT * FROM extended_sales(1);
Also, as #a_horse_with_no_name notes, a PL/pgSQL function is completely unnecessary here. Presumably this is a simplified example?
In future please include:
Your PostgreSQL version; and
The exact SQL you ran and the exact output you got