Looking for multiple strings in variable stored procedure - sql

I'm trying to write a stored procedure for cloned databases. I want to check the database_name variable for a specific string. Currently, this is what I've got:
IF ((CONTAINS(:database_name, 'STRING1'))=TRUE) THEN
RAISE clone_exception;
END IF;
IF ((CONTAINS(:database_name, 'STRING2'))=TRUE) THEN
RAISE clone_exception;
END IF;
IF ((CONTAINS(:database_name, 'STRING3'))=TRUE) THEN
RAISE clone_exception;
END IF;
I don't want to have to write 3 CONTAIN blocks I'd rather keep it compact and clean, is there a way I could get all 3 STRING checks in the same block of code? I've looked into using the IN operator but I can't find documentation that doesn't have examples using only a table query which doesn't really help me since this conditional is comparing only a variable and not a table column.

It is easy to achieve with LIKE ANY:
DECLARE
database_name TEXT := '...';
BEGIN
IF (:database_name ILIKE ANY ('%STRING1%','%STRING2%','%STRING3%') THEN
RAISE clone_exception:
END IF;
END;

Related

Table variable doesn't exist even though it clearly defined in PostgreSQL

I tried to create loop from table variable.
do
$$
DECLARE
modified IDType;
BEGIN
INSERT into modified (id)
SELECT i.id FROM item i WHERE i.id in ('55D6F516-7D8F-4DF3-A4E5-1E3F505837A1', 'FFE2A4D3-267C-465F-B4B4-C7BB2582F1BC');
for p in select id from modified
loop
raise notice (p.id);
end loop;
end;
$$ LANGUAGE plpgsql;
the problem is there has an error that says:
SQL Error [42P01]: ERROR: relation "modified" does not exist
Where: PL/pgSQL function inline_code_block line 5 at SQL statement
what I expected is the variable can be used to loop and runs normally.
this is the full query that you can try: http://sqlfiddle.com/#!17/9caba/3
I made the query in DBEAVER app, it will have some different error message.
I suggest you can experiment with it outside sqlfiddle.
There is no such thing as a "table variable" in Postgres. You can define a record that has a the type of a table but that is something completely different.
Even if idtype is the name of a table or a record type, it still holds a single value (in case of a record, it would be a single record with multiple fields)
Why would you expect a scalar variable to be usable in a loop? Are you looking for an
array?
To loop over an array use foreach
The SELECT also seems quite strange. You can assign a value to an array directly.
The parameter for raise notice needs to be a string. If you want to "print" a variable, you need to use a placeholder in the string.
So maybe you are looking for:
do
$$
DECLARE
modified idtype[];
id idtype;
BEGIN
modified := array['55D6F516-7D8F-4DF3-A4E5-1E3F505837A1', 'FFE2A4D3-267C-465F-B4B4-C7BB2582F1BC'];
foreach id in array modified
loop
raise notice '%', id;
end loop;
end;
$$ LANGUAGE plpgsql;

Raise notice to print a table's data

I'd like to be able to print some debug information from sql script / function. Of course, I can do this by
RAISE NOTICE 'hello!'
But I also need to print a whole table's data. This doesn't work:
RAISE NOTICE '%' (SELECT * FROM table1)
Is it possible and how?
The most straightforward way would be to iterate over the rows in a for loop and use RAISE NOTICE containing each column you're interested in interpolated in it.
i.e. something like:
FOR items IN SELECT * FROM table1 LOOP
RAISE NOTICE 'col1: %, col2: %', quote_ident(items.col1), quote_ident(items.col2);
END LOOP;
where items is declared as RECORD.
Since postgres 9.3 you can use to_json() to convert record into text suitable for notice,
RAISE NOTICE '%', to_json(record1);
RAISE NOTICE will print table data without alignment, so it will be hard to read. More flexible way is to use refcursor:
DECLARE
_temp_cur1 refcursor = 'unique_name_of_temp_cursor_1';
...
BEGIN
...
OPEN _temp_cur1 FOR
SELECT *
FROM table1;
...
END
Then run function in transaction:
BEGIN;
SELECT my_func();
FETCH ALL FROM "unique_name_of_temp_cursor_1"; --here is double-quotes ""!
ROLLBACK; --do not save any changes to DB during tests (or use COMMIT;)
Such refcursor will be available for reading during the same transaction. If you do not wrap your test with BEGIN and ROLLBACK (or COMMIT), PostgreSQL will not be able to find this one.

using comma separated values inside IN clause for NUMBER column

I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/

Solution to "cannot perform a DML operation inside a query"?

I am using a Data Analysis tool and the requirement I have was to accept a value from the user, pass that as a parameter and store it in a table. Pretty straighforward so I sat to write this
create or replace
procedure complex(datainput in VARCHAR2)
is
begin
insert into dumtab values (datainput);
end complex;
I executed this in SQL Developer using the following statement
begin
complex('SomeValue');
end;
It worked fine, and the value was inserted into the table. However, the above statements are not supported in the Data Analysis tool, so I resorted to use a function instead. The following is the code of the function, it compiles.
create or replace
function supercomplex(datainput in VARCHAR2)
return varchar2
is
begin
insert into dumtab values (datainput);
return 'done';
end supercomplex;
Once again I tried executing it in SQL Developer, but I got cannot perform a DML operation inside a query upon executing the following code
select supercomplex('somevalue') from dual;
My question is
- I need a statement that can run the mentioned function in SQL Developer or
- A function that can perform what I am looking for which can be executed by the select statement.
- If it is not possible to do what I'm asking, I would like a reason so I can inform my manager as I am very new (like a week old?) to PL/SQL so I am not aware of the rules and syntaxes.
P.S. How I wish this was C++ or even Java :(
EDIT
I need to run the function on SQL Developer because before running it in DMine (which is the tool) in order to test if it is valid or not. Anything invalid in SQL is also invalid in DMine, but not the other way around.
Thanks for the help, I understood the situation and as to why it is illegal/not recommended
You could use the directive pragma autonomous_transaction. This will run the function into an independant transaction that will be able to perform DML without raising the ORA-14551.
Be aware that since the autonomous transaction is independent, the results of the DML will be commited outside of the scope of the parent transaction. In most cases that would not be an acceptable workaround.
SQL> CREATE OR REPLACE FUNCTION supercomplex(datainput IN VARCHAR2)
2 RETURN VARCHAR2 IS
3 PRAGMA AUTONOMOUS_TRANSACTION;
4 BEGIN
5 INSERT INTO dumtab VALUES (datainput);
6 COMMIT;
7 RETURN 'done';
8 END supercomplex;
9 /
Function created
SQL> SELECT supercomplex('somevalue') FROM dual;
SUPERCOMPLEX('SOMEVALUE')
--------------------------------------------------------------------------------
done
SQL> select * from dumtab;
A
--------------------------------------------------------------------------------
somevalue
Tom Kyte has a nice explanation about why the error is raised in the first place. It is not safe because it may depend upon the order in which the rows are processed. Furthermore, Oracle doesn't guarantee that the function will be executed at least once and at most once per row.
Just declare a variable to accept the return value, for example:
declare
retvar varchar2(4);
begin
retvar := supercomplex('somevalue');
end;
The select doesn't work because the function is performing an insert, if all it did was return a value then it would work.
Just execute the function in a dummy if ... end if; statement to ignore the return value:
exec if supercomplex('somevalue') then null; end if;
Or execute it as a parameter for put_line procedure to output the return value:
exec dbms_ouput ('result of supercomplex='||supercomplex('somevalue'));
result of supercomplex=done

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.