Sorting an Oracle table inside a stored procedure - sql

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.

Related

How to get last query id in a Snowflake transaction

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;
$$
;

How to write a procedure to display the contents of a table in sql

I have a created a procedure as
create or replace procedure availability(num in number) as
begin
delete from vehicle_count;
insert into vehicle_count from select engine_no,count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
end;
/
The procedure was created successfully but now i have to write a separate query to view the contents of vehicle_count as
select * from vehicle_count;
I tried inserting the select statement into the procedure after insertion but it showed a error stating "an INTO clause is expected in the select statement".
How can I create procedure to select the required contents and display it in a single execute statement?
Table schema
vehicle(vehicle_no,engine_no,offence_count,license_status,owner_id);
vehicle_count(engine_no,engine_count);
Check this (MS SQL SERVER)-
create or alter procedure availability(#num as int) as
begin
delete from vehicle_count;
insert into vehicle_count
output inserted.engine_no,inserted.count_engine_no
select engine_no,count(engine_no) as count_engine_no
from vehicle
where engine_no=#num
group by engine_no;
end;
If you want to use a SELECT into a PL/SQL block you should use either a SELECT INTO or a loop (if you want to print more rows).
You could use something like this:
BEGIN
SELECT engine_no, engine_count
INTO v_engine, v_count
FROM vehicle_count
WHERE engine_no = num;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_engine := NULL;
v_count := NULL;
END;
v_engine and v_count are two variables. You can declare them in your procedure, and they will contain the values you want to print.
You said that the procedure you wrote (actually, you posted here) compiled successfully. Well, sorry to inform you - that's not true. This is not a valid syntax:
insert into vehicle_count from select engine_no,count(engine_no)
----
from? Here?
Consider posting true information.
As of your question (if we suppose that that INSERT actually inserted something into a table):
at the beginning, you delete everything from the table
as SELECT counts number of rows that share the same ENGINE_NO (which is equal to the parameter NUM value), INSERT inserts none (if there's no such NUM value in the table) or maximum 1 row (because of aggregation)
therefore, if you want to display what's in the table, all you need is a single SELECT ... INTO statement whose result is displayed with a simple DBMS_OUTPUT.PUT_LINE which will be OK if you're doing it interactively (in SQL*Plus, SQL Developer, TOAD and smilar tools). Regarding table description, I'd say that ENGINE_NO should be a primary key (i.e. that not more than a single row with that ENGINE_NO value can exist in a table).
create or replace procedure availability (num in number) as
l_engine_no vehicle_count.engine_no%type;
l_engine_count vehicle_count.engine_count%type;
begin
delete from vehicle_count;
insert into vehicle_count (engine_no, engine_count)
select engine_no, count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
-- This query shouldn't return TOO-MANY-ROWS if ENGINE_NO is a primary key.
-- However, it might return NO-DATA-FOUND if there's no such NUM there, so you should handle it
select engine_no, engine_count
into l_engine_no, l_engine_count
from vehicle_count
where engine_no = num;
dbms_output.put_line(l_engine_no ||': '|| l_engine_count);
exception
when no_data_found then
dbms_output.put_line('Nothing found for ENGINE_NO = ' || num);
end;
/
There are numerous alternatives to that (people who posted their answers/comments before this one mentioned some of those), and the final result you'd be satisfied with depends on where you want to display that information.

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

Viewing query results with a parameters in Oracle

I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.

Declaring & Setting Variables in a Select Statement

I'm attempting to write a simple query where I declare some variables and then use them in a select statement in Oracle. I've been able to do this before in SQL Server with the following:
DECLARE #date1 DATETIME
SET #date1 = '03-AUG-2010'
SELECT U.VisualID
FROM Usage u WITH(NOLOCK)
WHERE U.UseTime > #Date1
From the searching I've done it appears you can not declare and set variables like this in Select statements. Is this right or am I mssing something?
From the searching I've done it appears you can not declare and set variables like this in Select statements. Is this right or am I missing something?
Within Oracle PL/SQL and SQL are two separate languages with two separate engines. You can embed SQL DML within PL/SQL, and that will get you variables. Such as the following anonymous PL/SQL block. Note the / at the end is not part of PL/SQL, but tells SQL*Plus to send the preceding block.
declare
v_Date1 date := to_date('03-AUG-2010', 'DD-Mon-YYYY');
v_Count number;
begin
select count(*) into v_Count
from Usage
where UseTime > v_Date1;
dbms_output.put_line(v_Count);
end;
/
The problem is that a block that is equivalent to your T-SQL code will not work:
SQL> declare
2 v_Date1 date := to_date('03-AUG-2010', 'DD-Mon-YYYY');
3 begin
4 select VisualId
5 from Usage
6 where UseTime > v_Date1;
7 end;
8 /
select VisualId
*
ERROR at line 4:
ORA-06550: line 4, column 5:
PLS-00428: an INTO clause is expected in this SELECT statement
To pass the results of a query out of an PL/SQL, either an anonymous block, stored procedure or stored function, a cursor must be declared, opened and then returned to the calling program. (Beyond the scope of answering this question. EDIT: see Get resultset from oracle stored procedure)
The client tool that connects to the database may have it's own bind variables. In SQL*Plus:
SQL> -- SQL*Plus does not all date type in this context
SQL> -- So using varchar2 to hold text
SQL> variable v_Date1 varchar2(20)
SQL>
SQL> -- use PL/SQL to set the value of the bind variable
SQL> exec :v_Date1 := '02-Aug-2010';
PL/SQL procedure successfully completed.
SQL> -- Converting to a date, since the variable is not yet a date.
SQL> -- Note the use of colon, this tells SQL*Plus that v_Date1
SQL> -- is a bind variable.
SQL> select VisualId
2 from Usage
3 where UseTime > to_char(:v_Date1, 'DD-Mon-YYYY');
no rows selected
Note the above is in SQLPlus, may not (probably won't) work in Toad PL/SQL developer, etc. The lines starting with variable and exec are SQLPlus commands. They are not SQL or PL/SQL commands. No rows selected because the table is empty.
I have tried this and it worked:
define PROPp_START_DT = TO_DATE('01-SEP-1999')
select * from proposal where prop_start_dt = &PROPp_START_DT
The SET command is TSQL specific - here's the PLSQL equivalent to what you posted:
v_date1 DATE := TO_DATE('03-AUG-2010', 'DD-MON-YYYY');
SELECT u.visualid
FROM USAGE u
WHERE u.usetime > v_date1;
There's also no need for prefixing variables with "#"; I tend to prefix variables with "v_" to distinguish between variables & columns/etc.
See this thread about the Oracle equivalent of NOLOCK...
Try the to_date function.
Coming from SQL Server as well, and this really bugged me. For those using Toad Data Point or Toad for Oracle, it's extremely simple. Just putting a colon in front of your variable name will prompt Toad to open a dialog where you enter the value on execute.
SELECT * FROM some_table WHERE some_column = :var_name;