Using a query in a conditional statement in PLSQL? - database

Can I do something like the following in PLSQL?
if (some_query) then
dbms_output.put_line('Your query returned at least 1 row.');
else
dbms_output.put_line('Your query returned no rows.');
end if;
My use case is I want to check if a value already exists in my database. If the value already exists then I will do something different than if the value doesn't exist at all.

If you a checking for existence of a record, then you can select COUNT(*) from the source table based on the key as it will always return a value you can check:
SQL> set serverout on
SQL> DECLARE
2 v_check NUMBER;
3 BEGIN
4 SELECT COUNT(*)
5 INTO v_check
6 FROM DUAL
7 WHERE DUMMY = 'X'
8 AND ROWNUM = 1;
9
10 if (v_check = 0) then
11 dbms_output.put_line('Your query returned no rows.');
12 else
13 dbms_output.put_line('Your query returned at least 1 row.');
14 end if;
15 END;
16 /
Your query returned at least 1 row.
PL/SQL procedure successfully completed.
SQL>

You'd have to do something like
BEGIN
SELECT 1
INTO l_foo
FROM dual
WHERE EXISTS (<<some query>>);
dbms_output.put_line( 'Your query returned at least 1 row' );
EXCEPTION
WHEN no_data_found
THEN
dbms_output.put_line( 'Your query returned 0 rows' );
END;
If the query isn't too expensive, it will be almost as efficient and probably a bit easier to maintain if you do something simpler
SELECT COUNT(*)
INTO l_foo
FROM (<<some query>>)
WHERE rownum = 1;
IF( l_foo = 1 )
THEN
dbms_output.put_line( 'Your query returned at least row.' );
ELSE
dbms_output.put_line( 'Your query returned 0 rows.' );
END IF;

Exactly the same, no. But there are several ways you can fake it:
If you only need to do one thing do it inside an implicit cursor that confirms whether your value exists.
for i in ( some_query ) loop
do_something;
end loop;
You can also set a value in here and use it in an if
for i in ( some_query ) loop
result := True;
end loop;
if result then
do_something;
else
do_something_else;
end if;
Or you can use an explicit cursor and catch the no_data_found error that'll be raised
declare
cursor c_blah is
select my_value
from my_table
where id = my_id
;
my_value varchar2(4000);
begin
open c_blah(my_id);
fetch c_blah into my_value;
close c_blah;
do_something;
exception when no_data_found then
do_something_else;
end;

Since your use case behaves identically whether the query returns one row or a thousand, use EXISTS:
DECLARE
l_dummy VARCHAR2(1);
BEGIN
SELECT NULL
INTO l_dummy
FROM DUAL
WHERE EXISTS (SELECT NULL
FROM <some_query>);
DBMS_OUTPUT.PUT_LINE('Your query returned at least one row.');
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Your query returned no rows.');
END;

Related

If exists (select top 1 1 from table) in Snowflake

How to write the T-SQL below in SnowFlake
if exists (select top 1 1 from tableName)
This returns true or false
Update
I tried to run the if in the screenshot below in the Snowflake browser, but get the error:
https://docs.snowflake.com/en/sql-reference/snowflake-scripting/if.html
It depends on where you will use it:
create table tableName( id number);
SELECT exists (select top 1 1 from tableName);
-- returns false
insert into tablename values (1 );
SELECT exists (select top 1 1 from tableName);
-- returns true
The direct equivalent of IF:
-- SQL Server
if exists (select top 1 1 from tableName)
-- come code
is an anonymous block with branch construct(Snwoflake Scripting):
BEGIN
IF (EXISTS(select top 1 1 from tableName)) THEN
-- some code
END IF;
END;
If Classic WebUI is used then Using Snowflake Scripting in SnowSQL and the Classic Web Interface:
EXECUTE IMMEDIATE $$
BEGIN
IF (EXISTS(select top 1 1 from tableName)) THEN
RETURN 1;
END IF;
END;
$$;
This expression evaluates true if (and only if) the table tablename contains any data (that is 1 or more rows).
IF EXISTS (
SELECT TOP 1
1
FROM tablename
)
It should have the same effect as
IF EXISTS (
SELECT
*
FROM tablename
)
I don't know but I would expect Snowflake to be smart enough to do no more than determine if there are results in either case.
The second form is the common SQL idiom in my experience.

Oracle PL/SQL Assign each value from a cursor(from function) to another cursor one by one

I have a function called GET_CLIENT_IN_SED(return sys_refcursor), it gives me a list of id numbers(single column). Now, in a procedure, I am trying to loop through each (one by one) of that values and use it for calling a second procedure (it needs a client id parameter).
PROCEDURE GET_ORDINARY_CLIENT;
PROCEDURE GET_ORDINARY_CLIENT_BY_SED
( sed_in IN varchar2, client_sed OUT SYS_REFCURSOR )
IS
ordinary_clients sys_refcursor;
BEGIN
ordinary_clients := GET_CLIENT_IN_SED(sed_in);
for item in ordinary_clients loop
client_sed := client_sed + ordinary_clients(i);
end loop;
END;
As far i could understand you need to do something like :
Function:
This function would take input as number and return a refcursor. Similar to your requirement.
CREATE OR REPLACE FUNCTION get_num_sysrefcur (num IN NUMBER)
RETURN SYS_REFCURSOR
AS
my_cursor SYS_REFCURSOR;
BEGIN
--
OPEN my_cursor FOR
WITH ntable
AS (SELECT 1 ID, 111 AGT, 'ABC' DESCRIP FROM DUAL
UNION ALL
SELECT 2 ID, 222 AGT, 'ABC' DESCRIP FROM DUAL
UNION ALL
SELECT 1 ID, 333 AGT, 'ABC' DESCRIP FROM DUAL)
SELECT AGT FROM ntable WHERE ID = num;
RETURN my_cursor;
END;
/
Block ( In your case Procedure )
-- This anonymous block will loop through the records return from the sys_refcursor. Similiar to you need where you want the second procedure to use the value of sys_refcursor and loop it(You can create procedure in place of this anonymous block).
DECLARE
a NUMBER := 1;
TYPE ta IS TABLE OF NUMBER
INDEX BY PLS_INTEGER;
b ta;
x SYS_REFCURSOR;
BEGIN
x := get_num_sysrefcur (a);
fetch x bulk collect into b;
for i in 1..b.count
loop
-- Displaying the result of the ref_cursor.
DBMS_OUTPUT.put_line (b(i));
end loop;
END;
To loop through a ref cursor is not like looping through an array or table which explains why your FOR...LOOP is not working.
In short, instead of a collection, the ref_cursor is more of a "pointer" or an "iterator" over a collection. In this other question you will find a quite clear example of iterating through a ref_cursor using FETCH.
How to use record to loop a ref cursor?
An example with your data would look like this :
PROCEDURE GET_ORDINARY_CLIENT_BY_SED(sed_in IN VARCHAR2,
client_sed OUT SYS_REFCURSOR) IS
ordinary_clients SYS_REFCURSOR;
clt NUMBER; -- assuming your cursor contains strictly numbers
BEGIN
ordinary_clients := GET_CLIENT_IN_SED(sed_in);
LOOP
FETCH ordinary_clients
INTO clt;
EXIT WHEN ordinary_clients%NOTFOUND;
dbms_output.put_line(clt);
-- do some other things here with your number
END LOOP;
END;

Check if there are any records in a table which may not exist

I have to check if some records with specific conditions exist in a table which may not exist and I must do this in a scalar function.
Here is my code:
CREATE FUNCTION CheckIfRecordsExistInTestTable()
RETURNS INT
BEGIN
DECLARE #Result INT
SELECT #Result =
CASE WHEN OBJECT_ID('TestTable') IS NULL THEN 0
ELSE
CASE WHEN EXISTS(SELECT * FROM TestTable) THEN
1
ELSE
0
END
END
RETURN #Result
END
While trying it in SQL Server executing the following statement:
SELECT dbo.CheckIfRecordsExistInTestTable()
Whenever TestTable exists it returns my expected result. But whenever it does not, SQL Server raises an exception (Invalid object name 'TestTable') and I cannot get what I expect (I want a zero return value in this situation).
So what do you propose to do for this problem which can be coded to a scalar function?
The other answer gives a correct workaround.
As to why you are getting the problem...
This is a compile time error.
If the statement references a non existent object compilation is deferred until just before execution, but still eventually the whole statement needs to be compiled into an execution plan before it is executed.
This fails when the table doesn't exist and execution of that statement doesn't even begin.
(Execution plan that it tries to create - using a passthru predicate to avoid evaluation of the condition if the CASE not met)
In the workaround the SELECT against testtable is moved into a different statement. Compilation of this statement is still deferred and as the statement is never executed all works fine.
Try changing the function like this
CREATE FUNCTION Checkifrecordsexistintesttable()
returns INT
BEGIN
DECLARE #Result INT
IF Object_id('TestTable') IS NULL
SET #Result = 0
ELSE
SELECT #Result = CASE
WHEN EXISTS(SELECT 1 FROM testtable) THEN 1
ELSE 0
END
RETURN #Result
END;
To know more about the reason behind the error you are getting check Martin's answer.
update function like this:
CREATE FUNCTION CheckIfRecordsExistInTestTable()
RETURNS INT
BEGIN
DECLARE #Result INT
SELECT #Result = case when count(1) = 0 then 0 else 1 end from sys.tables where name = 'TestTable'
RETURN #result
END

PLSQL loop for checking data availability in table

There should be script that checks count() in one table and if count() is NULL then Exit, else sleep for some time and then proceed again with checking table while count(*) is NULL. My efforts were in vain:
declare
v_cnt pls_integer;
begin
while v_cnt >0
loop
select count(*) into v_cnt from TestTable;
if v_cnt is Null
then
exit;
else
dbms_lock.sleep(6);
dbms_output.put_line('Count is greater then Null. Current values: ' || v_cnt);
end loop;
dbms_output.put_line('TestTable does not have any data');
end;
Count always returns a number, not null. Change the if to zero instead of null and it should work. And I changed your logic around so it does not kick off if there are zero records. Unless you are deleting records from the table I have assumed that not having any records will only happen once. If you are deleting records and table could have zero records many times then kick this procedure off with a job that runs as often as you like and remove the while loop.
declare
v_cnt pls_integer;
begin
select count(*) into v_cnt from TestTable;
if v_cnt > 0 then
while v_cnt > 0
loop
select count(*) into v_cnt from TestTable;
dbms_lock.sleep(6);
dbms_output.put_line('Count is greater than zero . Current values: ' || v_cnt);
end loop;
else
dbms_output.put_line('TestTable does not have any data');
end if;
end;

Prevent concurrent access to stored procedure in sql server

i have sequences table that consists of three columns:
Number,Year,Type
and for each new year a three new records gets created and updated all along this year.
my stored procedure for generating the sequence is used inside other stored procedures, and my issue is that i want to block concurrent access to this stored procedure and make the access as queue so if concurrent access occur one has to wait for another to finish so that two users don't get same sequence number, the code is as follows:
ALTER PROCEDURE [dbo].[GETSEQUENECENO]
#p_hijricYear INT ,
#p_typeId INT ,
#return_val INT OUTPUT
AS
BEGIN
DECLARE #newSequence INT
BEGIN TRY
SELECT #return_val = 0
SELECT #newSequence = ISNULL( max(correspondencenumber) ,0 )
FROM io_sequencenumbers with (XLOCK)
WHERE
hijricyear = #p_hijricyear
AND
typeid = #p_typeid
END TRY
BEGIN CATCH
SELECT #newSequence = -1
END CATCH
IF #newSequence != -1
BEGIN
IF #newSequence = 0
BEGIN
SELECT #newSequence = 1
INSERT INTO io_sequencenumbers
VALUES
( #newSequence ,
#p_hijricYear ,
#p_typeId )
END
ELSE
BEGIN
SELECT #newSequence = #newSequence + 1
UPDATE io_sequencenumbers
SET
correspondencenumber = #newSequence
WHERE hijricyear = #p_hijricyear
AND
typeid = #p_typeid
END
END -- end of #newSequence!= -1 --
SELECT #return_val = #newSequence
END
i read that setting isolation level to serializable may solve it, is that enough or i have to use also begin and end transaction in stored procedure and manually handling rollback and commit ?
One approach could be the use of SQL Server application locks, see sp_getapplock and sp_releaseapplock. This will let you serialise your sequence generation through the SP without the need for serialisable transactions but won't prevent access to the io_sequecenumbers table by other code so you'll need to be sure that this SP is the only place that updates this table.
I was able to optimize the sequence generation this way:
ALTER PROCEDURE [dbo].[GETSEQUENECENO]
#p_hijricYear INT ,
#p_typeId INT ,
#return_val INT OUTPUT
AS
BEGIN
DECLARE #newSequence numeric(18,0)
BEGIN TRY
UPDATE IO_SEQUENCENUMBERS WITH (READCOMMITTEDLOCK)
SET #newSequence = correspondencenumber = correspondencenumber + 1
WHERE
hijricyear = #p_hijricyear
AND
typeid = #p_typeid
END TRY
BEGIN CATCH
SELECT #newSequence = -1
END CATCH
SELECT #return_val = #newSequence
END

Resources