How to get last query id in a Snowflake transaction - sql

This is about Snowflake database.
I want to know if there is a way to get the last query id in current transaction.
I have a SQL stored procedure with a couple of delete and insert steps. In the beginning of the procedure, I start a transaction and I commit it at the end if no errors occur.
But to be certain that the procedure calculated the result and inserted some new rows in results table, I want to use the query id with result_scan to get the actual number of rows inserted.
I create a Snowflake connection from Python code and parallelly call stored proc with different input parameters.
The function last_query_id works at the session level, so it might return the query id from other parallel stored proc execution. For sequential execution, it works fine. But for multiple parallel executions this results in unexpected behavior.
I have also looked at the QUERY_HISTORY* functions but I don't see a relation between query history based on transaction id. There are only options to find queries for user/session etc.

In SQL Script stored procedures, there is a global variable named SQLID that holds the query ID of the last executed query in the procedure.
https://docs.snowflake.com/en/developer-guide/snowflake-scripting/query-id.html#getting-the-query-id-of-the-last-query
execute immediate $$
declare
query_id_1 varchar;
query_id_2 varchar;
begin
select 1;
query_id_1 := sqlid;
select 2;
query_id_2 := sqlid;
return [query_id_1, query_id_2];
end;
$$
;

Related

How do I creating a table with stored procedure results on Bigquery

Im trying to schedule my stored procedure to run every day and save the results into a table.
I got the stored procedure to work but im not able to create a table with the results.
Creating a table backed by the SP to run daily
You can use DDL statements where you're executing queries in your stored procedure, before the SQL of your output query.
CREATE OR REPLACE TABLE `datasetId.tableId`
CREATE TABLE statement
Whatever your result query is: try to use DDL of create/replace table to save the results.
something like :
BEGIN
EXECUTE IMMEDIATE CONCAT("create or replace table `",YOUR_TABLE_NAME_VARIABLE,"` as YOUR_QUERY_HERE");
EXCEPTION WHEN ERROR THEN
RAISE USING MESSAGE = CONCAT('StoredProc Error: ',##error.message);
END;

User defined function can only have select statements

One of the main differences between UDF and SP is that UDF can only have select statements inside it and not insert/update/delete statements. Can someone please explain the reason behind this?The below function:
create function test(..)
...
BEGIN
insert into EMPLOYEE('22',12000,'john');
return 0;
END
is not valid. But why is this so?
The insert statement inside your function is missing the values keyword;
insert into EMPLOYEE('22',12000,'john');
should be
insert into EMPLOYEE values ('22',12000,'john');
though it's better to include the list of column names too. From the small part of the code you showed that is the only thing that is invalid. There could be other errors in the bits you have omitted. (If the first column in your table is numeric then you shouldn't be passing a string - it works but does implicit conversion and is best avoided. And if the column is a string, should it be really?)
UDF can only have select statements inside it and not insert/update/delete statements
That is not correct. You can have DML (insert/update/delete) in a function, but you can only call it from a PL/SQL context (though even in PL/SQL, it's often said that functions should query data with no side effects and only procedures should modify data; but that is not restricted by the language itself):
create table employee (id varchar2(3), salary number, name varchar2(10));
Table EMPLOYEE created.
create function test(unused number)
return number as
BEGIN
insert into EMPLOYEE (id, salary, name)
values ('22',12000,'john');
return 0;
END;
/
Function TEST compiled
declare
rc number;
begin
rc := test(42);
end;
/
PL/SQL procedure successfully completed.
select * from employee;
ID SALARY NAME
--- ---------- ----------
22 12000 john
But you cannot call it from a SQL context:
select test(42) from dual;
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "MYSCHEMA.TEST", line 4
The documentation lists restrictions on functions called from SQL, and goes into more detail in this warning:
Because SQL is a declarative language, rather than an imperative (or procedural) one, you cannot know how many times a function invoked by a SQL statement will run—even if the function is written in PL/SQL, an imperative language.
If the function was allowed to do DML then you would have no control over how many times that DML was performed. If it was doing an insert, for instance, it might try to insert the same row twice and either duplicate data or get a constraint violation.
Just to summarise the comments, you can have DML inside a PL/SQL function.
What you can't do is call that function from SQL, because a select statement shouldn't also apply updates and deletes and so on as a hidden side effect.
For one thing, the SQL language reserves the right to execute the query any way it chooses, in any order, and with any caching it decides make use of. (It might even stop and restart during execution. That's up to the SQL engine.) Therefore your function might get called once or a hundred times, in any order, depending on the execution plan, and so the results would be unpredictable.

Oracle - Output number of rows affected from dynamic merge statement

I have a dynamic sql in a stored procedure with a MERGE statement and and executing it using EXECUTE IMMEDIATE <dynamic_sql>. When I run the merge query through the sql worksheet, it tells me the number of rows merged. How do I retrieve the same information through a dynamic sql?
I would appreciate any efforts towards this question.
After running any SQL statement (static or dynamic), the SQL%ROWCOUNT variable will tell you how many rows were affected.
EXECUTE IMMEDIATE l_sql_stmt;
l_rows_affected := SQL%ROWCOUNT;

Sorting an Oracle table inside a stored procedure

I have a stored procedure which I am running from a .sql file which takes in inputs from the users and runs the procedure. The procedure runs many queries and inserts a row of values into the table. In the .sql file I have I would like to order the table by a certain column after I finish running the queries. Currently I have:
DECLARE
NAMEPARAM VARCHAR2(200);
VERSIONPARAM VARCHAR2(200);
STARTDATE DATE;
ENDDATE DATE;
BEGIN
NAMEPARAM := '&1';
VERSIONPARAM := '&2';
STARTDATE := '&3';
ENDDATE := '&4';
PROCEDURE(NAMEPARAM, VERSIONPARAM, STARTDATE, ENDDATE);
COMMIT;
Select * from TABLE_NAME
ORDER BY COLUMN_NAME ASC;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20101,SQLERRM);
END;
/
However It throws the error:
PLS-00428: an INTO clause is expected in this SELECT statement
I dont know why it needs an into clause. Help?
Edit:
To clarify I don't want to see an output of a sorted table I would like the table itself to be updated and sorted by the column in ascending order.
If you put a SELECT statement in a PL/SQL block, you need to do something with the results. If you're expecting exactly one row, you'd do a SELECT INTO a local variable. Assuming that you are expecting multiple rows, you could write a FOR loop that iterates over the rows or you could BULK COLLECT the rows into a PL/SQL collection. You could also open a SYS_REFCURSOR but since there is no way to return that from an anonymous PL/SQL block, that seems unlikely to be what you want.
My guess is that you want the SELECT statement to be outside the PL/SQL block and you want whatever tool you are using to execute the .sql script to run the PL/SQL block to populate the table and then run the SELECT statement, writing the results to whatever file/ console you are spooling output to.

Dynamic Cursors

I use a cursor for the statement:
SELECT NAME FROM STUDENT WHERE ROLL = 1;
I used:
CURSOR C IS SELECT NAME FROM STUDENT WHERE ROLL = roll;
--roll is a variable I receive via a procedure, and the procedure works fine for the received parameter.
Upon executing this, I am able to retrieve all records with roll = 1.
Now, I need to retrieve the records of a group (possibly via a cursor), just like:
SELECT NAME FROM STUDENT WHERE ROLL IN (2, 4, 6);
But the values in the IN clause are known only at run time. How should I do this? That is, is there any way I could assign parameters to the WHERE clause of the cursor?
I tried using an array in the declaration of the cursor, but an error pops up telling something like: standard types cannot be used.
I used:
CURSOR C IS SELECT NAME FROM STUDENT WHERE ROLL IN (rolls);
--rolls is an array initialized with the required roll numbers.
First, I assume that the parameter to your procedure doesn't actually match the name of a column in the STUDENT table. If you actually coded the statement you posted, roll would be resolved as the name of the column, not the parameter or local variable so this statement would return every row in the STUDENT table where the ROLL column was NOT NULL.
CURSOR C
IS SELECT NAME
FROM STUDENT
WHERE ROLL = roll;
Second, while it is possible to use dynamic SQL as #Gaurav Soni suggests, doing so generates a bunch of non-sharable SQL statements. That's going to flood the shared pool, probably aging other statements out of cache, and use a lot of CPU hard-parsing the statement every time. Oracle is built on the premise that you are going to parse a SQL statement once, generally using bind variables, and then execute the statement many times with different values for the bind variables. Oracle can go through the process of parsing the query, generating the query plan, placing the query in the shared pool, etc. only once and then reuse all that when you execute the query again. If you generate a bunch of SQL statements that will never be used again because you're using dynamic SQL without bind variables, Oracle is going to end up spending a lot of time caching SQL statements that will never be executed again, pushing useful cached statements that will be used again out of the shared pool meaning that you're going to have to re-parse those queries the next time they're encountered.
Additionally, you've opened yourself up to SQL injection attacks. An attacker can exploit the procedure to read any data from any table or execute any function that the owner of the stored procedure has access to. That is going to be a major security hole even if your application isn't particularly security conscious.
You would be better off using a collection. That prevents SQL injection attacks and it generates a single sharable SQL statement so you don't have to do constant hard parses.
SQL> create type empno_tbl is table of number;
2 /
Type created.
SQL> create or replace procedure get_emps( p_empno_arr in empno_tbl )
2 is
3 begin
4 for e in (select *
5 from emp
6 where empno in (select column_value
7 from table( p_empno_arr )))
8 loop
9 dbms_output.put_line( e.ename );
10 end loop;
11 end;
12 /
Procedure created.
SQL> set serveroutput on;
SQL> begin
2 get_emps( empno_tbl( 7369,7499,7934 ));
3 end;
4 /
SMITH
ALLEN
MILLER
PL/SQL procedure successfully completed.
create or replace procedure dynamic_cur(p_empno VARCHAR2) IS
cur sys_refcursor;
v_ename emp.ename%type;
begin
open cur for 'select ename from emp where empno in (' || p_empno || ')';
loop
fetch cur into v_ename;
exit when cur%notfound;
dbms_output.put_line(v_ename);
end loop;
close cur;
end dynamic_cur;
Procedure created
Run the procedure dynamic_cur
declare
v_empno varchar2(200) := '7499,7521,7566';
begin
dynamic_cur(v_empno);
end;
Output
ALLEN
WARD
JONES
Note:As mentioned by XQbert,dynamic cursor leads to SQL injection ,but if you're not working on any critical requirement ,where security is not involved then you can use this .
Maybe you can pass rolls as a set of quoted comma separated values.
e.g. '1', '2' etc
If this value is passes into the procedure in a varchar input variable, the it can be used to get multiple rows as per the table match.
Hence the cursor
SELECT NAME FROM STUDENT WHERE ROLL IN (rolls);
will be evaluated as
SELECT NAME FROM STUDENT WHERE ROLL IN ('1','2');
Hope it helps