I have a table where the column value I want to show as a new column in select. Next images show what I want.
I tried to use "pivot" but it not worked. Is it possible to do with pivot function?
My query:
select m.tmarti as line, dsmdcp as column, vlmdmn as valuee
from TABLE01 m
--pivot (count(vlmdmn for dsmdcp in ('Cintura', 'Quadril', 'Busto'))
join TABLE02 t on t.tmarti = m.tmarti
where cdarti = 2026397
order by t.seqtam, dsmdcp;
You can use Conditional Aggregation in such a way that to create a select statement string to pivot those columns dynamically within a stored function which returns a value in SYS_REFCURSOR type such as
CREATE OR REPLACE FUNCTION Get_Pivoted_Values RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( 'MAX( CASE WHEN column = '''||column||''' THEN valuee END )
AS "'||column||'"' , ',' ) WITHIN GROUP ( ORDER BY dsmdcp )
INTO v_cols
FROM ( SELECT DISTINCT dsmdcp
FROM table02 );
v_sql :=
'SELECT tmarti,'|| v_cols ||
' FROM
(
SELECT t1.tmarti AS line, dsmdcp AS column, vlmdmn AS valuee
FROM table01 t1
JOIN table02 t2
ON t2.tmarti = t1.tmarti
)
GROUP BY tmarti';
OPEN v_recordset FOR v_sql;
RETURN v_recordset;
END;
/
The function might be invoked from the SQL Developer's console as
SQL> DECLARE
result SYS_REFCURSOR;
BEGIN
:result := Get_Pivoted_Values;
END;
/
SQL> PRINT result;
I have JSON array inside varchar
DECLARE
JsonArray varchar2(1000);
arrayCars varchar2(1000);
BEGIN
JsonArray :={"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }
SELECT JSON_QUERY(JsonArray, '$.Cars') into arrayCars FROM dual;
END;
/
Now if I print out arrayCars i will get
[{"name":"Honda","color":"red"},{"name":"Toyota","color":"green"}]
But how can I loop through this Cars array and print out it's components seperately (get access to them)?
You can directly use SQL with JSON_TABLE() function which is available starting with Oracle DB 12.1.0.2 version such as
WITH t(arrayCars) AS
(
SELECT JSON_QUERY('{"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }', '$.Cars')
FROM dual
)
SELECT name, color
FROM t
CROSS JOIN JSON_TABLE(arrayCars,
'$' COLUMNS(NESTED PATH '$[*]'
COLUMNS(
name VARCHAR2(100) PATH '$.name',
color VARCHAR2(100) PATH '$.color'
)
)
);
Demo
If you really need to use PL/SQL, then consider creating a function with SYS_REFCURSOR return type such as
CREATE OR REPLACE FUNCTION Get_Cars RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
JsonArray VARCHAR2(1000);
arrayCars VARCHAR2(1000);
v_sql VARCHAR2(32767);
BEGIN
JsonArray :='{"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }';
arrayCars := JSON_QUERY(JsonArray, '$.Cars');
DBMS_OUTPUT.PUT_LINE(arrayCars);
v_sql :=
'SELECT name,color
FROM dual
CROSS JOIN JSON_TABLE(:Cars,
''$'' COLUMNS(NESTED PATH ''$[*]''
COLUMNS(
name VARCHAR2(100) PATH ''$.name'',
color VARCHAR2(100) PATH ''$.color''
)
)
)';
OPEN v_recordset FOR v_sql USING arrayCars;
RETURN v_recordset;
END;
/
and then call from SQL Developer's console as
SQL> DECLARE
result SYS_REFCURSOR;
BEGIN
:result := Get_Cars;
END;
/
SQL> PRINT result ;
Edit(for your last comment):
Alternatively, you can use a simple implicit loop such as
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
v_name VARCHAR2(1000);
v_color VARCHAR2(1000);
BEGIN
FOR c IN (
SELECT name,color
FROM JSON_TABLE('{"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }',
'$' COLUMNS(NESTED PATH '$.Cars[*]'
COLUMNS(
name VARCHAR2(100) PATH '$.name',
color VARCHAR2(100) PATH '$.color'
)
)
)
)
LOOP
v_name := c.name;
v_color := c.color;
DBMS_OUTPUT.PUT_LINE(v_name||' '||v_color);
END LOOP;
END;
/
Demo2
This is undoubtedly a very basic Dynamic PL/SQL question, but I am stuck.
I am looking to write a dynamic PL/SQL which will function the same as the code below, returning values of field1, field2, field3 from all rows of tablename:
SELECT field1, field2, field3 FROM databasename.tablename;
If I write the following Dynamic SQL, I can get it to successfully execute. However, I can't get it to return anything:
declare
Query VARCHAR2(200) := 'SELECT field1, field2, field3 FROM databasename.tablename';
begin
EXECUTE IMMEDIATE Query;
end;
How does one return the results of the Dynamic SQL to match the one-line SELECT statement above, please?
Your first select statement is sql query and it can return the result as an tabular format.
But your second code is anonymous block and you can not execute any select query in anonumous block without INTO clause.
If you know that your select query is going to give you single record then use the INTO clause as follows:
EXECUTE IMMEDIATE Query into var1, var2, var3;
Note: declare the variables var1, var2 and var3 in declare section with proper data type.
A straightforward method to return the dataset from a Query string for your case would be using a cursor within a Stored Function or Procedure with return type SYS_REFCURSOR such as
CREATE OR REPLACE FUNCTION Get_Result_Of_TheTable RETURN SYS_REFCURSOR IS
Query VARCHAR2(200) := 'SELECT field1, field2, field3 FROM tablename';
v_recordset SYS_REFCURSOR;
BEGIN
OPEN v_recordset FOR Query;
RETURN v_recordset;
END;
/
and then call from SQL Developer's console as
DECLARE
result SYS_REFCURSOR;
BEGIN
:result := Get_Result_Of_TheTable;
END;
/
PRINT result;
There are several possibilities. If you know which fields are returned, then you can solve it this way:
DECLARE
TYPE cursor_type IS REF CURSOR;
l_cursor cursor_type;
l_record tablename%ROWTYPE;
l_query VARCHAR2(32767) := 'SELECT field1, field2, field3 FROM tablename';
BEGIN
OPEN l_cursor FOR l_query;
LOOP
FETCH l_cursor INTO l_record;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (l_record.field1 ||' '|| l_record.field2 ||' '|| l_record.field3);
END LOOP;
CLOSE l_cursor;
END;
/
Or you can write a small pipelined function:
CREATE OR REPLACE PACKAGE pkg_test
AS
TYPE test_type IS TABLE
OF tablename%ROWTYPE;
FUNCTION get_fields
RETURN test_type PIPELINED;
END pkg_test;
/
CREATE OR REPLACE PACKAGE BODY pkg_test
AS
FUNCTION get_fields
RETURN test_type PIPELINED
AS
TYPE cursor_type IS REF CURSOR;
l_cursor cursor_type;
l_record tablename%ROWTYPE;
l_query VARCHAR2(32767) := 'SELECT field1, field2, field3 FROM tablename';
BEGIN
OPEN l_cursor FOR l_query;
LOOP
FETCH l_cursor INTO l_record;
EXIT WHEN l_cursor%NOTFOUND;
PIPE ROW (l_record);
END LOOP;
CLOSE l_cursor;
END;
END pkg_test;
/
SELECT * FROM TABLE(pkg_test.get_fields);
This was for my test:
CREATE TABLE tablename
( field1 VARCHAR2(10),
field2 VARCHAR2(10),
field3 VARCHAR2(10)
);
INSERT INTO tablename VALUES ('A1', 'A2', 'A3');
INSERT INTO tablename VALUES ('B1', 'B2', 'B3');
INSERT INTO tablename VALUES ('C1', 'C2', 'C3');
COMMIT;
You can use the bulk collect into clause, like for static SQL in PL/SQL block.
Here is a simple example:
drop table users_tb;
create table users_tb (
id integer generated always as identity,
--
username varchar2(30) not null,
is_active char(1) default 1 not null,
created_date date default sysdate not null ,
edited_date date,
--
constraint user_id_pk primary key (id),
constraint user_is_active_ch check (is_active in (1,0))
);
insert into users_tb (username) values ('john.wick');
insert into users_tb (username) values ('constantine');
insert into users_tb (username) values ('neo');
commit;
declare
-- types
type tr_list is record (
username varchar2(30),
is_active integer
);
type tt_usernamelist is table of tr_list
index by pls_integer;
-- variables
lt_users tt_usernamelist;
l_stmt varchar2(2000);
begin
-- select statement
l_stmt := 'select username, is_active from users_tb';
-- execution of dynamic code
execute immediate l_stmt
bulk collect into lt_users;
-- loop over retrived data
for i in 1..lt_users.count loop
dbms_output.put_line('User: '||lt_users(i).username||' - Is active: '||lt_users(i).is_active);
end loop;
end;
The important part is the execution of dynamic code where I am using bulk collect into. Here is a link to DBFiddle to try out.
The following query needs to convert to dynamic SQL without hard code cursor SQL,
using l_query, I do not know the l_query it will come as a parameter.
Inside the loop, I need to execute another insert query ( l_insert_query) that also comes as a parameter.
Your counsel would be much appreciated
DECLARE
CURSOR cust
IS
SELECT *
FROM customer
WHERE id < 500;
BEGIN
l_query := 'SELECT * FROM customer WHERE id < 5';
l_insert_query :=
'insert into data ( name, mobile) values ( cust.name,cust.mobile)';
FOR r_cust IN cust
LOOP
EXECUTE IMMEDIATE l_insert_query;
END LOOP;
END;
You could do this with a dynamic PL/SQL block:
declare
l_query varchar2(100) := 'SELECT * FROM customer WHERE id < 5';
l_insert varchar2(100) := 'insert into data ( name, mobile) values ( cust.name,cust.mobile)';
l_plsql varchar2(4000);
begin
l_plsql := '
begin
for cust in (' || l_query || ') loop
' || l_insert || ';
end loop;
end;
';
dbms_output.put_line(l_plsql);
execute immediate l_plsql;
end;
/
The l_plsql statement ends up as a generated PL/SQL block using the cursor query and insert statement:
begin
for cust in (SELECT * FROM customer WHERE id < 5) loop
insert into data ( name, mobile) values ( cust.name,cust.mobile);
end loop;
end;
db<>fiddle
But that you can do this doesn't mean you should. This is vulnerable to SQL injection, and doesn't seem like a very safe, sensible or efficient way to handle data manipulation in your system.
I need to write a procedure, which should return insert script for any given table dynamically. Below is the signature of the procedure.
P_export_data_dml(table_name IN, column_name IN, column_value IN, data_dml OUT)
Ex:
table T1
C1 | C2
---------- | ----------
10 | 20
10 | 21
table T2
C1 | C2 | C3
------- | ------- | --------
10 | 20 | 30
10 | 21 | 31
case1
Input:
P_export_data_dml(T1, C1, 10, data_dml)
Expected output:
Insert into T1(C1, C2) values(10,20);
Insert into T1(C1, C2) values(10,21);
case2
Input:
P_export_data_dml(T2, C1, 10, data_dml)
Expected output:
Insert into T2(C1, C2, C3) values(10, 20, 30);
Insert into T2(C1, C2, C3) values(10, 21, 31);
I am able to generate an insert statement when there is only one record in a table for given input. (Using all_tab_columns and then fetching given table for each column to form parts of the Insert statement, and then finally concatenating all the strings to form final INSERT statement).
But I am facing a challenge to form an insert statement when there are multiple records. Can you please help me with the logic to loop through the records and to form INSERT statement for all the records.
Try this procedure. Note that it is good for NUMBER, VARCHAR, DATE (when has a default format) data types
set serveroutput on
create or replace
procedure p_export_data_dml(p_table in varchar2, p_filter_column in varchar2, p_filter_value in varchar2, p_dmls in out varchar2)
is
cursor c_statements(p_table varchar2, p_filter_column varchar2, p_filter_value varchar2) is
select 'select ''insert into '||p_table||' ('||
listagg(column_name, ', ') within group (order by column_id) ||') values(''''''||'||
listagg(column_name, '||'''''', ''''''||') within group (order by column_id)||'||'''''');'' from '||p_table||' where '||p_filter_column||' = '''||p_filter_value||'''' insert_statement
from user_tab_columns
where table_name = upper(p_table);
v_output varchar2(4000);
v_sql varchar2(4000);
type t_cursor is ref cursor;
c_cur t_cursor;
begin
for r_statements in c_statements(p_table, p_filter_column, p_filter_value) loop
v_sql := r_statements.insert_statement;
dbms_output.put_line(v_sql);
open c_cur for v_sql;
loop
fetch c_cur into v_output;
exit when c_cur%notfound;
if p_dmls = null then
p_dmls := v_output;
else
p_dmls := p_dmls || '
'||v_output;
end if;
end loop;
close c_cur;
end loop;
end;
/
Then you can try executing using the below
declare
v_text varchar2(32000);
begin
p_export_data_dml('t1', 'c1', 10, v_text);
dbms_output.put_line(v_text);
end;
/
The output can be something like this:
insert into t1 (C1, C2, C3, C4) values('10', '1', '11', '12-SEP-2017 07:54:38');
insert into t1 (C1, C2, C3, C4) values('10', '2', '12', '12-SEP-2017 07:54:38');
insert into t1 (C1, C2, C3, C4) values('10', '3', '13', '12-SEP-2017 07:54:38');
It isn't clear from your question what data-type you are planning on using for data_dml OUT.
This could be a collection, or a clob, etc.
Please note, that most Oracle-supporting editors like SQL Developer, Intellij IDEA, TOAD, etc. already have this kind of thing built right in, with robust implementation to transform result sets into INSERTS.
That being said, this kind of thing can be achieved using Dynamic SQL.
Here are a couple light-weight examples, built on the premise you plan on using only NUMBERs for column_value params. Overloads could be added for others, or ANYDATA, as needed.
Example 1: This example will return multiple INSERT statements in a CLOB (a collection return type could be used instead). It will only generate INSERTs for NUMBER DATA_TYPE columns. Note no error handling here.
CREATE OR REPLACE PROCEDURE P_EXPORT_DATA_DML(P_TABLE_NAME IN VARCHAR2, P_COLUMN_NAME IN VARCHAR2, P_COLUMN_VALUE IN NUMBER, V_DATA_DML OUT CLOB)
IS
P_COLUMN_LIST CLOB;
V_VALUES_LIST CLOB;
TYPE USER_TAB_COL_TABLE IS TABLE OF USER_TAB_COLUMNS%ROWTYPE;
V_USER_TAB_COLS USER_TAB_COL_TABLE;
V_SELECTER_SQL_TEXT CLOB := '';
BEGIN
SELECT *
BULK COLLECT INTO V_USER_TAB_COLS
FROM USER_TAB_COLUMNS
WHERE USER_TAB_COLUMNS.TABLE_NAME = P_TABLE_NAME
AND USER_TAB_COLUMNS.DATA_TYPE IN ('NUMBER');
P_COLUMN_LIST := P_COLUMN_LIST || V_USER_TAB_COLS(1).COLUMN_NAME;
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || V_USER_TAB_COLS(1).COLUMN_NAME;
FOR POINTER IN 2..V_USER_TAB_COLS.COUNT
LOOP
P_COLUMN_LIST := P_COLUMN_LIST || ',' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
END LOOP;
V_SELECTER_SQL_TEXT := UTL_LMS.FORMAT_MESSAGE(Q'!SELECT LISTAGG('INSERT INTO %s !', P_TABLE_NAME) || '(' || P_COLUMN_LIST || Q'!) VALUES ( '||!' || V_SELECTER_SQL_TEXT || UTL_LMS.FORMAT_MESSAGE(Q'!||');'||CHR(10)||CHR(13) ) WITHIN GROUP (ORDER BY %s ASC) FROM !', P_COLUMN_NAME) || P_TABLE_NAME || ' WHERE ' || P_COLUMN_NAME || ' = ' || P_COLUMN_VALUE;
EXECUTE IMMEDIATE V_SELECTER_SQL_TEXT INTO V_DATA_DML;
END;
/
Then try it out for T1/C1:
DECLARE
V_RESULT CLOB;
BEGIN
P_EXPORT_DATA_DML('T1','C1',10,V_RESULT);
DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/
INSERT INTO T1 (C1,C2) VALUES ( 10,20);
INSERT INTO T1 (C1,C2) VALUES ( 10,21);
PL/SQL procedure successfully completed.
Or for T2/C1:
DECLARE
V_RESULT CLOB;
BEGIN
P_EXPORT_DATA_DML('T2','C1',10,V_RESULT);
DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,20,30);
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,21,31);
PL/SQL procedure successfully completed.
Or for T2/C2:
...
P_EXPORT_DATA_DML('T2','C2',20,V_RESULT);
...
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,20,30);
PL/SQL procedure successfully completed.
To support other DATA_TYPEs, you'll need to handle DATE/TIMESTAMP -> CHAR conversions, quoting CHARs, etc.
Here's an example that supports NUMBER + VARCHAR2
CREATE OR REPLACE PROCEDURE P_EXPORT_DATA_DML(P_TABLE_NAME IN VARCHAR2, P_COLUMN_NAME IN VARCHAR2, P_COLUMN_VALUE IN NUMBER, V_DATA_DML OUT CLOB)
IS
P_COLUMN_LIST CLOB;
V_VALUES_LIST CLOB;
TYPE USER_TAB_COL_TABLE IS TABLE OF USER_TAB_COLUMNS%ROWTYPE;
V_USER_TAB_COLS USER_TAB_COL_TABLE;
V_SELECTER_SQL_TEXT CLOB := '';
BEGIN
SELECT *
BULK COLLECT INTO V_USER_TAB_COLS
FROM USER_TAB_COLUMNS
WHERE USER_TAB_COLUMNS.TABLE_NAME = P_TABLE_NAME
AND USER_TAB_COLUMNS.DATA_TYPE IN ('NUMBER', 'VARCHAR2');
P_COLUMN_LIST := P_COLUMN_LIST || V_USER_TAB_COLS(1).COLUMN_NAME;
CASE WHEN V_USER_TAB_COLS(1).DATA_TYPE = 'NUMBER'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || V_USER_TAB_COLS(1).COLUMN_NAME;
WHEN V_USER_TAB_COLS(1).DATA_TYPE = 'VARCHAR2'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!''''||!' || V_USER_TAB_COLS(1).COLUMN_NAME || Q'!||''''!';
END CASE;
FOR POINTER IN 2..V_USER_TAB_COLS.COUNT
LOOP
P_COLUMN_LIST := P_COLUMN_LIST || ',' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
CASE WHEN V_USER_TAB_COLS(POINTER).DATA_TYPE = 'NUMBER'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
WHEN V_USER_TAB_COLS(POINTER).DATA_TYPE = 'VARCHAR2'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || Q'!''''||!' || V_USER_TAB_COLS(POINTER).COLUMN_NAME || Q'!||''''!';
END CASE;
END LOOP;
V_SELECTER_SQL_TEXT := UTL_LMS.FORMAT_MESSAGE(Q'!SELECT LISTAGG('INSERT INTO %s !', P_TABLE_NAME) || '(' || P_COLUMN_LIST || Q'!) VALUES ( '||!' || V_SELECTER_SQL_TEXT || UTL_LMS.FORMAT_MESSAGE(Q'!||');'||CHR(10)||CHR(13) ) WITHIN GROUP (ORDER BY %s ASC) FROM !', P_COLUMN_NAME) || P_TABLE_NAME || ' WHERE ' || P_COLUMN_NAME || ' = ' || P_COLUMN_VALUE;
EXECUTE IMMEDIATE V_SELECTER_SQL_TEXT INTO V_DATA_DML;
END;
/
Test it out with a VARCHAR2 table:
CREATE TABLE T3 (C1 VARCHAR2(64), C2 NUMBER, C3 VARCHAR2(64));
INSERT INTO T3 VALUES ('XX',10,'AA');
INSERT INTO T3 VALUES ('XQ',10,'AQ');
INSERT INTO T3 VALUES ('XX',20,'AA');
Getting multiple rows:
DECLARE
V_RESULT CLOB;
BEGIN
P_EXPORT_DATA_DML('T3','C2',10,V_RESULT);
DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/
INSERT INTO T3 (C1,C2,C3) VALUES ( 'XQ',10,'AQ');
INSERT INTO T3 (C1,C2,C3) VALUES ( 'XX',10,'AA');
PL/SQL procedure successfully completed.