How to convert sql query with in clauses into string - sql

Hi I want to convert the sql statement into string for dynamic use. But I’m having trouble while concatenation
select
col1, col2
from
Table1
Where
Col3 in ( ‘cat’ , ‘dog’ );
I’m unable to figure out how to put quotes and || for the cat and dog
‘select
col1, col2
from
Table1
Where
Col3 in ( ‘||’‘cat’’ ||’, ‘’dog’ ‘||’)’ || Strcond;

One option is to use the q-quoting mechanism (see line #6), as it lets you use single quotes normally (also, your single quotes look fancy; if you planned to copy/paste from e.g. MS Word into your SQL client, that won't work).
Here's an example:
SQL> declare
2 l_str varchar2(500);
3 strcond varchar2(100) := ' and 1 = 1';
4 begin
5 l_str := 'select col1, col2 from table1 where col3 in ' ||
6 q'[('cat', 'dog')]' || strcond;
7 dbms_output.put_line(l_str);
8 end;
9 /
select col1, col2 from table1 where col3 in ('cat', 'dog') and 1 = 1
PL/SQL procedure successfully completed.
SQL>

One option it to use bind variables and put in as many bind variables as the maximum size of the IN list:
select col1, col2 from Table1 Where Col3 in ( :a001, :a002, :a003, :a004 )
Then:
you can use the same statement repeatedly and the SQL engine will not have to reparse it (after the first time).
you do not need to escape quotes in your list values in the dynamic SQL string.
your code is less vulnerable to SQL injection.
If you want to pass fewer values than the maximum then you can repeat values:
DECLARE
sql VARCHAR2(2000) := 'select col1, col2 from Table1 Where Col3 in ( :a001, :a002, :a003, :a004 )';
BEGIN
EXECUTE IMMEDIATE sql USING 'cat', 'dog', 'cat', 'cat';
END;
/
However, if you are going to do this then you can consider if you can eliminate dynamic SQL entirely:
DECLARE
a001 Table1.Col3%TYPE := 'cat';
a002 Table1.Col3%TYPE := 'dog';
a003 Table1.Col3%TYPE := a001;
a004 Table1.Col3%TYPE := a001;
col1_values SYS.ODCIVARCHAR2LIST;
col2_values SYS.ODCIVARCHAR2LIST;
BEGIN
select col1, col2
BULK COLLECT INTO col1_values, col2_values
from Table1
Where Col3 in ( a001, a002, a003, a004 );
END;
/

Related

oracle sql dynamically select columns using a parameter table

I have a table in oracle with 10 columns, say Table A with col_1, col_2, col_3 etc. I have another table, Table B with rows that has column names from table A co1_1, col_2, col_3. The rows in Table B can vary.
TABLE A
COL1 COL2 COL3 COL4 COL5 COL6 COL7 COL8 COL9 COL10
TABLE B
COL1
COL2
COL3
I want to write an oracle sql query which dynamically gets the select columns names based on the column names(rows) in table B.
If table B has 3 rows with corresponding column names then my query should look like this
select col_1, col_2, col_3 from A
If table B has 4 rows then my query should dynamically change to below
select col_1, col_2, col_3, col_4 from A
You need to use the dynamic query.
'SELECT '
|| (SELECT LISTAGG(COLNAME, ',') WITHIN GROUP (ORDER BY COLNAME) FROM TABLEB)
|| ' FROM TABLEA'
I think we should go to ALL_TAB_COLUMNS table for finding column names.
Like this.
SELECT
'SELECT '
||
(SELECT LISTAGG( y.COLNAME, ',') WITHIN GROUP (ORDER BY Y.COLNAME)
FROM TABLE_B x,ALL_TAB_COLUMNS y
where x.COLNAME=Y.COLUMN_NAME )
||
' FROM Table_A' script
FROM DUAL;
A ref cursor can be used to created dynamic columns. Many languages and applications support ref cursors, and if you add some details about your system someone may know exactly how to integrate them in your environment.
Below is a simple example of how to create a function that returns a ref cursor. How to call it depends on your system.
Sample Schema
--drop table a;
--drop table b;
create table a as
select 1 col1, 2 col2, 3 col3, 4 col4, 5 col5, 6 col6, 7 col7, 8 col8, 9 col9, 10 col10
from dual;
create table b as
select 'COL1' column_name from dual union all
select 'COL2' column_name from dual union all
select 'COL3' column_name from dual;
Function
create or replace function get_dynamic_results return sys_refcursor is
v_cursor sys_refcursor;
v_column_list varchar2(4000);
begin
--Split this into multiple SELECTS if you get an error like:
-- ORA-01489: result of string concatenation is too long error
select listagg(column_name, ',') within group (order by column_name) columns
into v_column_list
from b;
open v_cursor for 'select '||v_column_list||' from a';
return v_cursor;
end;
/

How to dynamically pass column names to a query?

Below is my problem and desired solution.
Query1:
Select colnames from table1;
Query1 Result:
col1
col2
col3
col4
Query2:
Select a1.*
from table2 a1;
-- should translate to
select a1.col1, a1.col2, a1.col3, a1.col4 from table2 a1;
My first query will give the list of column names, I need to replace the .* with those column names in my second query. How can I achieve this?
You are looking for dynamic SQL. The idea is to generate the query string from the results of a SQL query. You can then run it with execute immediate.
In your use case, that would look like:
declare
p_sql varchar2(100);
begin
select
'select '
|| listagg('a1.' || colnames, ', ') within group(order by colnames)
|| ' from table2 a1'
into p_sql
from table1;
dbms_output.put_line('sql: ' || p_sql); -- debug
execute immediate p_sql; -- execute
end;
/
For your sample data, this generates:
dbms_output:
sql: select a1.col1, a1.col2, a1.col3, a1.col4 from table2 a1

How do I write a cursor to return only one column that contains digit values

For example I had a table named car with two column(col1,col2)
For now I would like insert some values inside the column like:
('Super car','Yellow car')
('BMW5','XL')
('Benz','AGM')
so I would like write a cursor to return ('BMW5','XL') in one single column, how do I do that?(I'm using sql developer)
I would appreciate any suggestion! Thank you!
declare
cursor mycursor is select concat(col1,col2) from car where REGEXP_LIKE(left, '^[[:digit:]]+$')
begin
for counter in mycursor
loop
dbms_output.put_line(counter.concat);
endloop;
end
You can use Concat() function in your query inside the cursor i.e.
CONCAT returns Col1 concatenated with Col2
select concat(col1,col2)
from car where <<conditions if any>>
You can write above query in Cursor like
DECLARE
CURSOR car_cursor IS select concat(col1,col2)
from car WHERE REGEXP_LIKE(col1, '[[:digit:]]');
cv_col1_col2 VARCHAR2 ;
BEGIN
OPEN car_cursor;
LOOP
FETCH car_cursor INTO cv_col1_col2;
Dbms_output.put_line('Concated Name' || cv_col1_col2)
END LOOP
CLOSE car_cursor;
END;
This is how I understood the question (though, more through by description than the title as they aren't related much).
Sample data:
SQL> create table car (col1 varchar2(10), col2 varchar2(10));
Table created.
SQL> insert into car
2 select 'Super car', 'Yellow car' from dual union all
3 select 'BMW5', 'XL' from dual union all
4 select 'Benz', 'AGM' from dual;
3 rows created.
PL/SQL code that uses a cursor FOR loop, returning concatenated col1 and col2 values for rows in which either of those columns contains a digit:
SQL> set serveroutput on;
SQL> begin
2 for cur_r in (select col1 ||' '|| col2 result
3 from car
4 where regexp_like(col1 || col2, '\d') -- any column contains
5 ) -- a digit
6 loop
7 dbms_output.put_line(cur_r.result);
8 end loop;
9 end;
10 /
BMW5 XL
PL/SQL procedure successfully completed.
SQL>

How to return result of many select statements as one custom table

I have a table (let's name it source_tab) where I store list of all database tables that meet some criteria.
tab_name: description:
table1 some_desc1
table2 some_desc2
Now I need to execute a select statement on each of these tables and return a result as a table (I created custom TYPE). However I have a problem - when using bulk collect, only the last select statement is returned. The same issue was with open cursor. Is there any possibility to achieve this goal, another then concatenating all select statements using union all and executing it as one statement? And because I'm the begginer in sql, my second question is, is it ok to use this dynamic sql in terms of sql injection issues? Below is simplified version of my code:
CREATE OR REPLACE FUNCTION my_function RETURN newly_created_table_type IS
ret_tab_type newly_created_table_type;
BEGIN
for r in (select * from source_tab)
loop
execute immediate 'select value1, value2,''' || r.tab_name || ''' from ' || r.tab_name bulk collect into ret_tab_type;
end loop;
return ret_tab_type;
END;
I'm using Oracle 11.
In your case you are trying to populate a collection dynamically and wanted result in a single collection. In your case its not possible to do that in a single loop. Also as mentioned by #OldProgrammer, piperow would be a better solution from performance point. See below demo:
--Tables and Values:
CREATE TABLE SOURCE_TAB(TAB_NAME VARCHAR2(100), DESCRIPTION VARCHAR2(100));
/
SELECT * FROM SOURCE_TAB;
/
INSERT INTO SOURCE_TAB VALUES('table1','some_desc1');
INSERT INTO SOURCE_TAB VALUES('table2','some_desc2');
/
CREATE TABLE TABLE1(COL1 NUMBER, COL2 NUMBER);
/
INSERT INTO TABLE1 VALUES(1,2);
INSERT INTO TABLE1 VALUES(3,4);
INSERT INTO TABLE1 VALUES(5,6);
/
Select * from TABLE1;
/
CREATE TABLE TABLE2(COL1 NUMBER, COL2 NUMBER);
/
INSERT INTO TABLE2 VALUES(7,8);
INSERT INTO TABLE2 VALUES(9,10);
INSERT INTO TABLE2 VALUES(11,12);
/
Select * from TABLE2;
/
--Object Created
--UDT
CREATE OR REPLACE TYPE NEWLY_CREATED_TABLE_TYPE IS OBJECT (
VALUE1 NUMBER,
VALUE2 NUMBER
);
/
--Type of UDT
CREATE OR TYPE NEWLY_CRTD_TYP AS TABLE OF NEWLY_CREATED_TABLE_TYPE;
/
--Function:
--Function
CREATE OR REPLACE FUNCTION MY_FUNCTION
RETURN NEWLY_CRTD_TYP PIPELINED
AS
CURSOR CUR_TAB
IS
SELECT *
FROM SOURCE_TAB;
RET_TAB_TYPE NEWLY_CRTD_TYP;
BEGIN
FOR I IN CUR_TAB
LOOP
--Here i made sure that all the tables have col1 & col2 columns since you are using dynamic sql.
EXECUTE IMMEDIATE 'select NEWLY_CREATED_TABLE_TYPE(COL1, COL2) from '|| I.TAB_NAME
BULK COLLECT INTO RET_TAB_TYPE;
EXIT WHEN CUR_TAB%NOTFOUND;
FOR REC IN 1 .. RET_TAB_TYPE.COUNT
LOOP
PIPE ROW (RET_TAB_TYPE (REC) );
END LOOP;
END LOOP;
RETURN;
END;
/
Output:
SQL> Select * from table(MY_FUNCTION);
VALUE1 VALUE2
---------- ----------
1 2
3 4
5 6
7 8
9 10
11 12
6 rows selected.
May be you can combine all the queries into one using UNION ALL before execution, if the number and type of columns to be retrieved from all the tables are identical.
CREATE OR REPLACE FUNCTION my_function
RETURN newly_created_table_type
IS
ret_tab_type newly_created_table_type;
v_query VARCHAR2 (4000);
BEGIN
SELECT LISTAGG (' select VALUE1,VALUE2 FROM ' || tab_name, ' UNION ALL ')
WITHIN GROUP (ORDER BY tab_name)
INTO v_query
FROM source_tab;
EXECUTE IMMEDIATE v_query BULK COLLECT INTO ret_tab_type;
RETURN ret_tab_type;
END;
You could then use a single select statement to get all the values.
select * FROM TABLE ( my_function );

Customize SQL query

I have requirement where in I am creating a table dynamically(number of columns may change based on input parameter to my procedure through which I am creating this table) with data in the table like below.
PK col1 col2 col3
A null 1-2 3-4
B null null 4-5
C null 5-6 null
Now the requirement is I want to extract only the columns where at least there should be 1 record without null and spool the whole data into a file. My output should be like below (col1 exlluded from output as it has all nulls).
PK col2 col3
A 1-2 3-4
B null 4-5
C 5-6 null
Can anybody provide any hints to achieve this. Thanks in advance.
This won't be very efficient I suspect, but you can use COUNT() to determine if there are any only NULLS in a column because COUNT(column_here) will only add 1 for each non-null value. Hence if the count is zero that column is only NULLs.
This can then be combined into a query to generate a valid select statement and then that executed immediate (being careful of course to avoid sql injection).
Anyway, here's an example:
select
'select '
|| substr((
select
case when count(COL1) > 0 then ',col1' else '' end
|| case when count(COL2) > 0 then ',col2' else '' end
|| case when count(COL3) > 0 then ',col3' else '' end
from a_table
),2,8000)
|| ' from '
|| ' a_table'
as sql_string
from dual
;
see this sqlfiddle
result by the above is:
| SQL_STRING |
|--------------------------------|
| select col2,col3 from a_table |
Here is a try. First, create a function to generate your query, returning a REF CURSOR:
create or replace function select_non_nulls() return sys_refcursor as
myQuery varchar2(500);
myCur sys_refcursor;
begin
select 'select ' || listagg(col, ', ') within group (order by col) || ' from test'
into myQuery
from
(
select case when max(col1) is null then null else 'col1' end col from test
union all
select case when max(col2) is null then null else 'col2' end col from test
union all
select case when max(col3) is null then null else 'col3' end col from test
)
;
open myCur for myQuery;
return myCur;
end;
/
Then use it in SQL*Plus:
SQL> var rc refcursor
SQL> exec :rc := select_non_nulls;
SQL> print rc;
I used num_nulls from all_tab_cols and achieved result as per my requirement. Thank you.