I need to generate multiple columns from dual table. Number of columns to be generated gets decided by user input. If input is 3 then three times 'ABC'. If it is 4 then 4 times 'ABC' should be selected from dual.
I tried union all but I am trying to find out more efficient way of doing it.
DECLARE
v_value varchar2(10):='ABC'
v_count number:=3;
VAR varchar2(4000)
BEGIN
select 'ABC','ABC','ABC' INTO VAR FROM DUAL;
END;
Try this. Here you can input the number of times 'ABC' needed as column at runtime:
Code:
declare
user_input number := #
var varchar2(10) := '''ABC''';
var1 varchar2(2000);
v_sql varchar2(1000);
v_res varchar2(4000);
begin
var1 := var;
for i in 1 .. user_input - 1 loop
var1 := var1 || ',' || var;
end loop;
var1 := LTRIM(RTRIM(var1, ','), ',');
--dbms_output.put_line(var1);
v_sql := 'select :var1 from dual';
--dbms_output.put_line(v_sql);
Execute immediate v_sql
into v_res
using var1;
dbms_output.put_line(v_res);
end;
Demo:
SQL> declare
2
3 user_input number := #
4
5 var varchar2(10) := '''ABC''';
6 var1 varchar2(2000);
7 v_sql varchar2(1000);
8 v_res varchar2(4000);
9 begin
10
11 var1 := var;
12
13 for i in 1 .. user_input - 1 loop
14 var1 := var1 || ',' || var;
15 end loop;
16
17 var1 := LTRIM(RTRIM(var1, ','), ',');
18
19 --dbms_output.put_line(var1);
20
21 v_sql := 'select :var1 from dual';
22
23 --dbms_output.put_line(v_sql);
24
25 Execute immediate v_sql
26 into v_res
27 using var1;
28
29 dbms_output.put_line(v_res);
30
31 end;
32 /
Enter value for num: 2
old 3: user_input number := #
new 3: user_input number := 2;
'ABC','ABC'
PL/SQL procedure successfully completed.
SQL> /
Enter value for num: 5
old 3: user_input number := #
new 3: user_input number := 5;
'ABC','ABC','ABC','ABC','ABC'
PL/SQL procedure successfully completed.
SQL> /
Enter value for num: 7
old 3: user_input number := #
new 3: user_input number := 7;
'ABC','ABC','ABC','ABC','ABC','ABC','ABC'
PL/SQL procedure successfully completed.
SQL> /
Enter value for num: 6
old 3: user_input number := #
new 3: user_input number := 6;
'ABC','ABC','ABC','ABC','ABC','ABC'
PL/SQL procedure successfully completed.
SQL> /
Enter value for num: 9
old 3: user_input number := #
new 3: user_input number := 9;
'ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC'
PL/SQL procedure successfully completed.
Is that what you mean?
select RPAD('ABC',length('ABC')*3,'ABC') from dual
ABCABCABC
DECLARE
v_value varchar2(10):='ABC'
v_count number:=3;
VAR varchar2(4000)
BEGIN
select RPAD(v_value,length(v_value)*v_count,v_value) INTO VAR FROM DUAL;
END;
Your question is not that clear.
The fact that you are using a VARCHAR2(4000) variable for your result value makes me think that you need to get a single string composed by the concatenation of a string n times; if this is the case, you don't need a select ... from DUAL and can simply do:
DECLARE
v_value varchar2(10):='ABC';
v_count number:=4;
VAR varchar2(4000);
BEGIN
VAR := rpad(v_value, length(v_value) * v_count, v_value);
dbms_output.put_line(VAR);
END;
/
ABCABCABCABC
But you say you tried UNION, and this makes me think you need to get n rows with the same value; in this case, you may try:
DECLARE
type yourResultType is table of varchar2(10);
v_value varchar2(10):='ABC';
v_count number:=4;
VAR yourResultType;
BEGIN
select v_value
bulk collect into VAR
from dual
connect by level <= v_count;
--
for i in VAR.first .. VAR.last loop
dbms_output.put_line(VAR(i));
end loop;
END;
/
ABC
ABC
ABC
ABC
Related
I've got a table named "F_ParqueInfra", that I'd like to count all values in it where the value is equal to -1.
So, this table has 11 columns and 833 rows = 9.163 number of data in this table.
I'd like to know, how many "-1" values has in the whole table (all columns), in the simplest way.
Also I'll do that with a lot of tables in my Data Warehouse.
Really thanks!
One option is to use dynamic SQL. For example:
SQL> select * from f_parqueinfra;
ID_USUARIO ID_EMPRESA ID_DEPARTAMENTO
---------- ---------- ---------------
250 32 12
-1 -1 -1
0 -1 1
5 2 -1
SQL> set serveroutput on;
SQL> declare
2 l_table_name varchar2(30) := 'F_PARQUEINFRA';
3 l_value number := -1; -- search value
4 l_str varchar2(200); -- to compose SELECT statement
5 l_cnt number := 0; -- number of values in one column
6 l_sum number := 0; -- total sum of values
7 begin
8 for cur_r in (select table_name, column_name
9 from user_tab_columns
10 where table_name = l_table_name
11 and data_type = 'NUMBER'
12 )
13 loop
14 l_str := 'select count(*) from ' ||cur_r.table_name ||
15 ' where ' || cur_r.column_name || ' = ' || l_value;
16 execute immediate l_str into l_cnt;
17 l_sum := l_sum + l_cnt;
18 end loop;
19 dbms_output.put_line('Number of ' || l_value ||' values = ' || l_sum);
20 end;
21 /
Number of -1 values = 5
PL/SQL procedure successfully completed.
SQL>
If you change l_value (line #3), you can search for some other value, e.g.
SQL> l3
3* l_value number := -1; -- search value
SQL> c/-1/250
3* l_value number := 250; -- search value
SQL> /
Number of 250 values = 1
PL/SQL procedure successfully completed.
SQL>
Or, you can change table name (line #2) and search some other table.
Basically, you'd probably want to turn that anonymous PL/SQL code into a function which would accept table name and search value and return number of appearances. That shouldn't be too difficult so I'll leave it to you, for practice.
[EDIT: converting it into a function]
Although far from being perfect, something like this will let you search for some numeric and string values in tables in current schema. Dates are more complex, depending on formats etc. but - for simple cases - this code might be OK for you. Have a look:
SQL> create or replace function f_cnt (par_table_name in varchar2,
2 par_data_type in varchar2,
3 par_value in varchar2)
4 return sys.odcivarchar2list
5 is
6 l_data_type varchar2(20) := upper(par_data_type);
7 l_retval sys.odcivarchar2list := sys.odcivarchar2list();
8 l_str varchar2(200); -- to compose SELECT statement
9 l_out varchar2(200); -- return value
10 l_cnt number := 0; -- number of values in one column
11 l_sum number := 0; -- total sum of values
12 begin
13 -- loop through all tables in current schema
14 for cur_t in (select table_name
15 from user_tables
16 where table_name = upper(par_table_name)
17 or par_table_name is null
18 )
19 loop
20 -- reset the counter
21 l_sum := 0;
22 -- loop through all columns in that table
23 for cur_c in (select column_name
24 from user_tab_columns
25 where table_name = cur_t.table_name
26 and data_type = l_data_type
27 )
28 loop
29 -- pay attention to search value's data type
30 if l_data_type = 'VARCHAR2' then
31 l_str := 'select count(*) from ' ||cur_t.table_name ||
32 ' where ' || cur_c.column_name || ' = ' ||
33 chr(39) || par_value ||chr(39);
34 elsif l_data_type = 'NUMBER' then
35 l_str := 'select count(*) from ' ||cur_t.table_name ||
36 ' where ' || cur_c.column_name || ' = ' || par_value;
37 end if;
38
39 execute immediate l_str into l_cnt;
40 l_sum := l_sum + l_cnt;
41 end loop;
42
43 if l_sum > 0 then
44 l_out := cur_t.table_name ||' has ' || l_sum ||' search values';
45 l_retval.extend;
46 l_retval(l_retval.count) := l_out;
47 end if;
48 end loop;
49 return l_retval;
50 end;
51 /
Function created.
Testing:
SQL> select * From table(f_cnt(null, 'number', -1));
COLUMN_VALUE
-----------------------------------------------------------------
F_PARQUEINFRA has 5 search values
SQL> select * From table(f_cnt(null, 'varchar2', 'KING'));
COLUMN_VALUE
-----------------------------------------------------------------
EMP has 1 search values
SQL>
This might be a good place to use the unpivot syntax. This still requires you to type all the column names once - but not more.
Here is an example for 4 columns:
select count(*) cnt
from mytable
unpivot(myval for mycol in (col1, col2, col3, col4))
where myval = -1
As a bonus, you can easily modify the query to get the number of -1 per column:
select mycol, count(*) cnt
from mytable
unpivot(myval for mycol in (col1, col2, col3, col4))
where myval = -1
group by mycol
This should give you what you need.
Notes:
performs true numeric comparison (for example would match -1.00 also) using an inline function to prevent error should the value in a compared cell be non-numeric. (if all your compared values are guaranteed to be numeric the inline function can be simplified dramatically)
searches only varchar2 and number column types (this can be changed if desired).
The code follows:
set serveroutput on size 10000
declare
vMyTableName varchar2(200) := 'F_ParqueInfra';
vMyValue number := -1;
vSQL varchar2(4000);
vTotal pls_integer;
vGrandTotal number(18) := 0;
cursor c1 is
select *
from user_tab_columns
where table_name = vMyTableName;
vInlineFn varchar2(4000) := 'with
function matchesMyValue(value varchar2) return pls_integer
is
begin
return case
when to_number(value) = '||vMyValue||' then
1
else
0
end;
exception
when value_error then
return 0;
end;
';
begin
for x in c1 loop
if x.data_type in ('VARCHAR2','NUMBER') then -- only looking in these column data types for -1 but you can flex this to suit your column type definitions
vSQL := 'select sum(matchesMyValue('||x.column_name||')) from '||vMyTableName;
execute immediate vInlineFn||vSQL into vTotal;
vGrandTotal := vGrandTotal + vTotal;
end if;
end loop;
dbms_output.put_line('Total cells containing -1 = '||vGrandTotal);
end;
/
I got procedure where I want to insert value to variable from Col1:
Procedure insertX
IS
var1 varchar2(100) := '';
check_s1 : = 'select Col1 into '||var1||' from table1';
begin
EXECUTE IMMEDIATE check_s1;
if var1 is null then
...
else
...
end if;
end;
But when I execute it something goes wrong.
As I see select into had error.
How to insert the value to my Var1 and then use it in IF condition?
Wrong syntax. Should be
SQL> set serveroutput on
SQL> declare
2 var1 varchar2(100);
3 check_s1 varchar2(100) := 'select dummy from dual';
4 begin
5 execute immediate check_s1 into var1;
6
7 if var1 is null then
8 dbms_output.put_line('var1 is null');
9 else
10 dbms_output.put_line('var1 = ' || var1);
11 end if;
12 end;
13 /
var1 = X
PL/SQL procedure successfully completed.
SQL>
I'm try to loop some tables and run select as below:
set serveroutput on
declare
type tables_names is table of varchar2(30);
type selectTable is table of varchar2(30);
tName tables_names;
sTableName selectTable;
begin;
tName := tables_names('PERIOD','SETTING','RAP','LOG');
sTableName := selectTable('m_table1','m_table2','m_table3','m_table4','m_table5');
for i in 1..tName.count loop
for j in 1..sTableName.count loop
select col10, count(*) from user.sTableName(j)
where table_name = tName(i) group by col10;
end loop;
end loop;
end;
I got error:PL/SQL: ORA-00933.
Can you please tell me how can I correctly run PL/SQL procedure to have displayed result from my select?
UPDATE: looking result
Normally, to get this I need to run below select's:
select column_name,
count(*) as countColumn
from user.m_table1 where table_name = 'PERIOD' group by column_name;
select column_name,
count(*) as countColumn
from user.m_table2 where table_name = 'PERIOD' group by column_name;
Oracle complains (ORA-00933) that command isn't properly ended. That's probably because of a semi-colon behind the BEGIN; also, you lack the INTO clause.
I'm not sure what PERIOD, SETTING, ... are opposed to m_table1, m_table2, ... Which ones of those are table names? What are those other values, then?
Anyway: here's an example which shows how to do something like that - counting rows from tables. Try to adjust it to your situation, or - possibly - add some more info so that we'd know what you are doing.
SQL> set serveroutput on
SQL> declare
2 tname sys.odcivarchar2list := sys.odcivarchar2list();
3 l_cnt number;
4 l_str varchar2(200);
5 begin
6 tname := sys.odcivarchar2list('EMP', 'DEPT');
7
8 for i in 1 .. tname.count loop
9 l_str := 'select count(*) from ' || tname(i);
10 execute immediate l_str into l_cnt;
11 dbms_output.put_line(tname(i) ||': '|| l_cnt);
12 end loop;
13 end;
14 /
EMP: 14
DEPT: 4
PL/SQL procedure successfully completed.
SQL>
[EDIT: added GROUP BY option]
Here you go; as EMP and DEPT share the DEPTNO column, I chose it for a GROUP BY column.
SQL> declare
2 tname sys.odcivarchar2list := sys.odcivarchar2list();
3 type t_job is record (deptno varchar2(20), cnt number);
4 type t_tjob is table of t_job;
5 l_tjob t_tjob := t_tjob();
6 l_str varchar2(200);
7 begin
8 tname := sys.odcivarchar2list('EMP', 'DEPT');
9
10 for i in 1 .. tname.count loop
11 l_str := 'select deptno, count(*) from ' || tname(i) ||' group by deptno';
12 execute immediate l_str bulk collect into l_tjob;
13
14 for j in l_tjob.first .. l_tjob.last loop
15 dbms_output.put_Line('Table ' || tname(i) || ': Deptno ' || l_tjob(j).deptno||
16 ': number of rows = '|| l_tjob(j).cnt);
17 end loop;
18
19 end loop;
20 end;
21 /
Table EMP: Deptno 30: number of rows = 6
Table EMP: Deptno 20: number of rows = 5
Table EMP: Deptno 10: number of rows = 3
Table DEPT: Deptno 10: number of rows = 1
Table DEPT: Deptno 20: number of rows = 1
Table DEPT: Deptno 30: number of rows = 1
Table DEPT: Deptno 40: number of rows = 1
PL/SQL procedure successfully completed.
SQL>
You are probably looking for something like this. Note that you can't run a simple select statement inside a PL/SQL without INTO clause. use a refcursor and DBMS_SQL.RETURN_RESULT
DECLARE
TYPE tables_names IS TABLE OF VARCHAR2 (30);
TYPE selectTable IS TABLE OF VARCHAR2 (30);
tName tables_names;
sTableName selectTable;
rc SYS_REFCURSOR;
BEGIN
tName :=
tables_names ('PERIOD',
'SETTING',
'RAP',
'LOG');
sTableName :=
selectTable ('m_table1',
'm_table2',
'm_table3',
'm_table4',
'm_table5');
FOR i IN 1 .. tName.COUNT
LOOP
FOR j IN 1 .. sTableName.COUNT
LOOP
OPEN rc FOR
'select col10, count(*) from '||USER||'.'
|| sTableName (j)
|| ' where table_name = '''
|| tName (i)
|| ''' group by col10';
DBMS_SQL.RETURN_RESULT (rc);
END LOOP;
END LOOP;
END;
/
I am trying to run plsql anonymous block using execute immediate and the plsql block contains a bind variable for which the value is a sql select statement. But it seems this does not work. Is there any solution for to solve this.
E.g.
BEGIN
V_SQL:='SELECT emp_id FROM emp WHERE dept_id=10;
PLSQL_BLOCK:='DECLARE
type emp_type
IS
TABLE OF NUMBER;
emp_id emp_type;
BEGIN
EXECUTE IMMEDIATE :1 BULK COLLECT INTO emp_id;
END';
EXECUTE IMMEDIATE PLSQL_BLOCK USING V_SQL;
If I understand well, you need to run an entire dynamic PLSQL block by using as SQL query as a bind variable; if so, you can try this way:
SQL> declare
2 vPlSqlBlock varchar2(10000);
3 vSQL varchar2(1000);
4 BEGIN
5 vSQL:='SELECT 1 from dual';
6 --
7 vPlSqlBlock:='DECLARE
8 type emp_type IS TABLE OF NUMBER;
9 emp_id emp_type;
10 vSQLDyn varchar2(1000) := :1;
11 BEGIN
12 EXECUTE IMMEDIATE vSQLDyn BULK COLLECT INTO emp_id;
13 --
14 /* whatever you need to do in your block */
15 for i in emp_id.first .. emp_id.last loop
16 dbms_output.put_line(emp_id(i));
17 end loop;
18 END;';
19
20 EXECUTE IMMEDIATE vPlSqlBlock USING vSQL;
21 end;
22 /
1
PL/SQL procedure successfully completed.
SQL> declare
2 vPlSqlBlock varchar2(10000);
3 vSQL varchar2(1000);
4 BEGIN
5 vSQL:='SELECT 1 from dual';
6 --
7 vPlSqlBlock:='DECLARE
8 type emp_type IS TABLE OF NUMBER;
9 emp_id emp_type;
10 vSQLDyn varchar2(1000) := :1;
11 BEGIN
12 EXECUTE IMMEDIATE vSQLDyn BULK COLLECT INTO emp_id;
13 /* this does nothing */
14 END;';
15
16 EXECUTE IMMEDIATE vPlSqlBlock USING vSQL;
17 end;
18 /
PL/SQL procedure successfully completed.
SQL>
Plz try this.Hope this helps.
set serveroutput on;
set define on;
DECLARE
V_SQL varchar2(1000):='SELECT emp_id from emp where deptno = 10';
PLSQL_BLOCK varchar2(1000):='DECLARE
type emp_type
IS
TABLE OF NUMBER;
emp_id emp_type;
BEGIN
EXECUTE IMMEDIATE :1 BULK COLLECT INTO emp_id;
END;';
BEGIN
EXECUTE IMMEDIATE PLSQL_BLOCK USING V_SQL;
END;
You have to first declare the variables you want to use in your execution block.
Try this:
DECLARE
V_SQL VARCHAR2(1000) := 'SELECT emp_id FROM emp WHERE dept_id=10';
PLSQL_BLOCK varchar2(1000) :='DECLARE type emp_type IS TABLE OF integer; emp_id emp_type;'
|| ' BEGIN EXECUTE IMMEDIATE :1 BULK COLLECT INTO emp_id; END;';
BEGIN
EXECUTE IMMEDIATE PLSQL_BLOCK USING V_SQL;
END;
/
I have string like this: str:='ac_Abc.88,ac_Abc.99,ac_Abc.77'. I need to get first element after splitting with comma(,). So im using using like this:
str VARCHAR2(500);
dbms_utility.comma_to_table
( list => regexp_replace(str,'(^|,)','\1')
, tablen => l_count
, tab => l_array
);
I'm getting following error:
ORA-20001: comma-separated list invalid near bc.88
ORA-06512: at "SYS.DBMS_UTILITY", line 239
ORA-06512: at "SYS.DBMS_UTILITY", line 272
But if i have string like this, str:='ac_Abc88,ac_Abc99,ac_Abc77', the same method working fine and giving me expected results.
So i guess there is something need to be corrected to consider "." as regular character. Can you please suggest how can i solve this.
See How to split comma delimited string into rows
1. REGEXP_SUBSTR approach
SQL> WITH DATA AS(
2 SELECT 'ac_Abc.88,ac_Abc.99,ac_Abc.77' str FROM dual)
3 SELECT regexp_substr(str,'[^,]+',1,level) str
4 FROM DATA
5 CONNECT BY regexp_substr(str, '[^,]+', 1, level) IS NOT NULL
6 /
STR
-----------------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
SQL>
2. XML approach
SQL> SELECT EXTRACT (VALUE (d), '//row/text()').getstringval () str
2 FROM
3 (SELECT XMLTYPE ( '<rows><row>'
4 || REPLACE ('ac_Abc.88,ac_Abc.99,ac_Abc.77', ',', '</row><row>')
5 || '</row></rows>' ) AS xmlval
6 FROM DUAL
7 ) x,
8 TABLE (XMLSEQUENCE (EXTRACT (x.xmlval, '/rows/row'))) d
9 /
STR
--------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
3. Table function
SQL> CREATE TYPE test_type
2 AS
3 TABLE OF VARCHAR2(100)
4 /
Type created.
SQL>
SQL> CREATE OR REPLACE
2 FUNCTION comma_to_table(
3 p_list IN VARCHAR2)
4 RETURN test_type
5 AS
6 l_string VARCHAR2(32767) := p_list || ',';
7 l_comma_index PLS_INTEGER;
8 l_index PLS_INTEGER := 1;
9 l_tab test_type := test_type();
10 BEGIN
11 LOOP
12 l_comma_index := INSTR(l_string, ',', l_index);
13 EXIT
14 WHEN l_comma_index = 0;
15 l_tab.EXTEND;
16 l_tab(l_tab.COUNT) := SUBSTR(l_string, l_index, l_comma_index - l_index);
17 l_index := l_comma_index + 1;
18 END LOOP;
19 RETURN l_tab;
20 END comma_to_table;
21 /
Function created.
SQL> sho err
No errors.
SQL>
SQL> SELECT * FROM TABLE(comma_to_table('ac_Abc.88,ac_Abc.99,ac_Abc.77'))
2 /
COLUMN_VALUE
--------------------------------------------------------------------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
SQL>
4. Pipelined Function
SQL> CREATE OR REPLACE
2 FUNCTION comma_to_table(
3 p_list IN VARCHAR2)
4 RETURN test_type PIPELINED
5 AS
6 l_string LONG := p_list || ',';
7 l_comma_index PLS_INTEGER;
8 l_index PLS_INTEGER := 1;
9 BEGIN
10 LOOP
11 l_comma_index := INSTR(l_string, ',', l_index);
12 EXIT
13 WHEN l_comma_index = 0;
14 PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
15 l_index := l_comma_index + 1;
16 END LOOP;
17 RETURN;
18 END comma_to_table;
19 /
Function created.
SQL> sho err
No errors.
SQL>
SQL> SELECT * FROM TABLE(comma_to_table('ac_Abc.88,ac_Abc.99,ac_Abc.77'))
2 /
COLUMN_VALUE
--------------------------------------------------------------------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
It is because (Oracle doc reference)
COMMA_TO_TABLE Procedures
These procedures converts a comma-delimited list of names into a
PL/SQL table of names. The second version supports fully-qualified
attribute names.
A "name" referred to here is a valid Oracle (DB object) identifier, for which all naming rules apply. ac_Abc.88 is not a valid name, because in Oracle you can't have an identifier starting with a digit.
To resolve your problem with parsing strings of comma-delimited values, use the solution of Lalit Kumar B's.