PL/SQL developer export query result as merge statements - oracle

I just started to use PL/SQL Developers tools. Before I used to use Toad for Oracle.
In Toad, I can generate Merge Statement from the Query result for every records and it give me following example result. (for single result)
MERGE user T
USING (
select
1 as id,
'nam' as name,
'sur' as surname
from
dual
) S
ON (S.id = T.id)
WHEN MATCHED
THEN UPDATE
SET T.name = S.name,
T.surname = S.surname
WHEN NOT MATCHED BY TARGET
THEN INSERT (id, name, surname)
VALUES (t.id, t.name, t.surname);
How can I generate "Merge Statement" in PL/SQL developer? If don't, is anyway to generate this statement? Thanks for your help!

Had the same problem here today and I just created an small script what generate it for me and put the output to DBMS Output. Just enable it an set the Buffer high enough.
Then run following script, adjust table and column names:
BEGIN
FOR r_cur IN ( SELECT column_one,
column_two,
column_three,
column_value
FROM some_table
WHERE column_one LIKE 'something%'
ORDER BY column_one, column_two, column_three)
LOOP
DBMS_OUTPUT.put_line (
'MERGE INTO some_table A USING
(SELECT\n
''' || r_cur.column_one || ''' as column_one,
''' || r_cur.column_two || ''' as column_two,
''' || r_cur.column_three || ''' as column_three,
''' || r_cur.column_value || ''' as column_value
FROM DUAL) B
ON (A.column_one = B.column_one and A.column_two = B.column_two and A.column_three = B.column_three)
WHEN NOT MATCHED THEN
INSERT (
column_one, column_two, column_three, column_value)
VALUES (
B.column_one, B.column_two, B.column_three, B.column_value)
WHEN MATCHED THEN
UPDATE SET
A.column_value = B.column_value;
' );
END LOOP;
END;
/

Related

Solving PL/SQL for loop variable string not resolving completely

I am trying to loop through multiple tables in a data mart and see how my TABLE_A joins to a TABLE_B based on a specific field (that changes with every loop). The variables for fields and tables resolve correctly in the DBMS_Output, but the query itself does not resovle to the full length. Instead, parts of the query are being cut off with each loop. How can I fix this?
My loop looks like this:
DECLARE
CURSOR c_tables is
SELECT
COLUMN_NAME
,JOIN_TABLE_NAME
FROM meself.test_loop ;
v_string1 varchar2(32767) := '';
BEGIN
FOR i IN c_tables LOOP
v_string1 := '
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.'||i.COLUMN_NAME||'
,''' || i.JOIN_TABLE_NAME || ''' AS JOIN_TABLE_NAME
,''' || i.COLUMN_NAME || ''' AS HASH_KEY_NAME
, ''TABLE_A'' AS Table_Name
,(CASE
WHEN
a.'|| i.COLUMN_NAME || ' = z.' || i.COLUMN_NAME || ' THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT '|| i.COLUMN_NAME ||'
FROM DATAMART.'|| i.JOIN_TABLE_NAME ||') z
ON a.'|| i.COLUMN_NAME ||' = z.'|| i.COLUMN_NAME ||'
) x
GROUP BY x.TABLE_NAME, x.JOIN_TABLE_NAME, x.HASH_KEY_NAME, x.MATCH_TYPE'
;
dbms_output.put_line( v_string1 );
--execute immediate v_string1;
END LOOP;
END;
**Which resolves to this (just showing 2 loops for simplicity). You will see the JOIN ON section is missing or incomplete before moving on to the next loop. **
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.LINE_SCTGRY_D_SK
,'TABLE_B' AS JOIN_TABLE_NAME
,'LINE_SCTGRY_D_SK' AS HASH_KEY_NAME
,'TABLE_A' AS Table_Name
,(CASE
WHEN
a.LINE_SCTGRY_D_SK = z.LINE_SCTGRY_D_SK THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT LINE_SCTGRY_D_SK
FROM DATAMART.TABLE_B
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.MEMBER_D_SK
,'TABLE_B' AS JOIN_TABLE_NAME
,'MEMBER_D_SK' AS HASH_KEY_NAME
, 'TABLE_A' AS Table_Name
,(CASE
WHEN
a.MEMBER_D_SK = z.MEMBER_D_SK THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT MEMBER_D_SK
FROM DATAMART.TABLE_B) z
ON a.MEMBER_D_SK = z.ME

Select data from all tables which contain these data-column

( I think it could be a simple question for the most users here ..)
Short description:
I need a way (maybe with PL/SQL which I don't know ..) to "select defined data from all tables which contain this type of data"
Long description (example):
I have a different number of different tables. An often changing part of them - I don't know the number and the names - contains the column "FID". Now I need two steps:
a) Select all tables which contain the column "FID". ( I know how to do this as single step)
b) Select from all found tables the value FID and show it.
For me the problem is the step from a) to b). With known tables I would use UNION, but with a dynamic result of tables I have no idea ..
You could use a variation on an XML magic trick, by using dbms_xmlgen to get all the values into XML documents based on a query against user_tab_columns:
select dbms_xmlgen.getxmltype(
'select "' || column_name || '" from "' || table_name || '"')
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER';
... where I'm assuming FID is expected to be a numeric ID, so limiting only to numeric columns (and also allowing for mixed case/quoted identifiers for table and columns names, just in case). That gives one row per table, with an XML document listing the FID values in that table.
Then from that XML you can extract the individual values, again as numbers:
with cte (xml) as (
select dbms_xmlgen.getxmltype(
'select "' || column_name || '" as fid from "' || table_name || '"')
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER'
)
select x.fid
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns fid number path 'FID'
) x;
Or if you want to see the table/column each value came from, just include those in the CTE and select list:
with cte (table_name, column_name, xml) as (
select table_name, column_name, dbms_xmlgen.getxmltype(
'select "' || column_name || '" as fid from "' || table_name || '"')
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER'
)
select cte.table_name, cte.column_name, x.fid
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns fid number path 'FID'
) x;
If you want to search other schemas, then use all_tab_columns instead, and optionally include each table's owner:
with cte (owner, table_name, column_name, xml) as (
select owner, table_name, column_name, dbms_xmlgen.getxmltype(
'select "' || column_name || '" as fid from "' || owner || '"."' || table_name || '"')
from all_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER'
)
select cte.owner, cte.table_name, cte.column_name, x.fid
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns fid number path 'FID'
) x;
db<>fiddle
The basis for this trick goes back to at least 2007 but may be even older, from before getxmltype() existed (it seems to have been added in 10g); I'd originally used xmltype(getxml()):
select xmltype(dbms_xmlgen.getxml(
'select "' || column_name || '" from "' || table_name || '"'))
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER';
which works most of the time, but if any of the tables are empty throws "ORA-06502: PL/SQL: numeric or value error".
If you want to use pl/sql I really love pipelined functions:
create type result_type as Object ( text varchar2(2000) );
create type result_type_table as table of result_type;
create or replace function select_all( p_column_name in varchar2 )
return result_type_table
deterministic
pipelined
as
v_table_name varchar2(40);
v_result result_type := result_type('');
v_table_name_cursor sys_refcursor;
v_inner_cursor sys_refcursor;
begin
open v_table_name_cursor
for 'select a.table_name
from user_tab_cols a
, user_tables b
where a.column_name = :1
and a.table_name = b.table_name'
using upper(p_column_name);
loop
fetch v_table_name_cursor into v_table_name;
exit when v_table_name_cursor%notfound;
open v_inner_cursor
for 'select '||p_column_name||' from '||v_table_name;
loop
fetch v_inner_cursor into v_result.text;
exit when v_inner_cursor%notfound;
pipe row (v_result );
end loop;
close v_inner_cursor;
end loop;
close v_table_name_cursor;
end;
/
Using this function is simple:
select * from table( select_all('your_column_name') );
db<>fiddle

Constructed dynamically Sql with checking table for data presence

I am trying to convert such SQL to Oracle which is constructed dynamically and executed with Sql Server:
DECLARE #dynamicQuery varchar(8000)
DECLARE #criteriaMet BIT
SET #dynamicQuery = ''
IF #criteriaMet = 1
BEGIN
SET #dynamicQuery = 'IF NOT EXISTS(SELECT TOP 1 1 FROM DATATABLE) '
END
SET #dynamicQuery = #dynamicQuery + 'INSERT INTO DATATABLE (...) VALUES (...)'
EXEC #dynamicQuery
But with Oracle I cannot use EXISTS in IF statement and have to declare variables and select count into the variable, but doing that inside dynamic SQL drastically reduces readability and increases complexity. Is there more elegant way of doing building dynamic SQL which checks for table data presence in Oracle based on some criteria?
Your example doesn't need to be dynamic, so you can do a static count, logic to check the count, and then a static insert if suitable:
declare
cnt pls_integer;
begin
select count(*)
into cnt
from dual
where exists (select null from your_table);
if cnt = 0 then
insert into your_table (id, foo) values (1, 'bar');
end if;
end;
/
You don't need PL/SQL at all though, you can do insert ... select ... and make the exists check part of that:
insert into your_table (id, foo)
select 1, 'bar'
from dual
where not exists (select null from your_table);
db<>fiddle
Either can be converted to be dynamic if there is actually a reason to do that, such as a run-time table name; the second option is probably still going to be more readable - the first would a dynamic query followed by the logic to do a dynamic insert.
I am trying to attach additional check to existing Merge statement only if some criteria is met, so it is less changes to the original
You could change TBL2 to a subquery, which apply the conditions you want; when those aren't met there is nothing for the merge to match. Something like:
'MERGE INTO' || TBL || ' USING ( SELECT * FROM ' || TBL2 ||
' WHERE NOT EXISTS (SELECT NULL FROM ' || TBL || ')' ||
' )' ON (' || COLS || ' ) WHEN NOT MATCHED THEN INSERT ( ' || COLS || ' VALUES (' || COLS || ')'
... where the middle line
' WHERE NOT EXISTS (SELECT NULL FROM ' || TBL || ')' ||
can apply whatever conditions you want.

Find MAX(PK_COLUMN) and display it with corresponding Table Name

I am having 20 tables ( Each table has a PK and data ), i want to find out what is the current MAX(PK) Value for each table.
I Want the result as follows :
TABLE_NAME MAX_VAL
-------------------- ----------
TABELE_A 114
TABELE_B 55
TABELE_C 14
TABELE_D 866
TABELE_3 4552
is there any way to accomplish this or else i have to write 20 times SELECT MAX(PK_COL) FROM TABLE ?
Assuming your currently connected schema is composed of those twenty tables, and each have identical primary key column name(pk_col), then consider the following code block containing an implicit cursor :
declare
v_max pls_integer;
begin
dbms_output.put_line('table_name max_val');
for c in ( select * from user_tables )
loop
execute immediate 'select max(pk_col) from '||c.table_name into v_max;
dbms_output.put_line(c.table_name||' '||v_max);
end loop;
end;
/
i have found another method which will bring TABLE_NAME,PK_COLUMN and MAX( PK_COLUMN ).
SELECT CASE
WHEN RN = 1 THEN
FORMATTED_QUERY_SET
ELSE
FORMATTED_QUERY_SET || ' UNION ALL '
END AS FORMATTED_QUERY_SET
FROM (SELECT ' SELECT NVL(MAX( ' || COL.COLUMN_NAME ||
' ),0) CURR_MAX_VAL, ''' || TAB.TABLE_NAME ||
''' TABLE_NAME,''' || COL.COLUMN_NAME ||
''' COLUMN_NAME FROM ' || TAB.TABLE_NAME AS FORMATTED_QUERY_SET,
TAB.TABLE_NAME,
ROW_NUMBER() OVER(ORDER BY TAB.TABLE_NAME DESC) AS RN
FROM USER_CONSTRAINTS TAB
JOIN USER_CONS_COLUMNS COL
ON TAB.TABLE_NAME = COL.TABLE_NAME
JOIN USER_TAB_COLUMNS COL2
ON COL.COLUMN_NAME = COL2.COLUMN_NAME
AND COL.TABLE_NAME = COL2.TABLE_NAME
WHERE TAB.CONSTRAINT_TYPE = 'P'
AND COL.CONSTRAINT_NAME LIKE '%_PK'
AND REGEXP_LIKE(COL2.DATA_TYPE, ('NUMB|INTE')))
ORDER BY TABLE_NAME;
Copy the output returned by the above query and execute.
Note : Remove the last ' UNION ALL ' operator from the query string.
Note : Please correct me if i am doing anything wrong .

How to trim all columns in all rows in all tables of type string?

In Oracle 10g, is there a way to do the following in PL/SQL?
for each table in database
for each row in table
for each column in row
if column is of type 'varchar2'
column = trim(column)
Thanks!
Of course, doing large-scale dynamic updates is potentially dangerous and time-consuming. But here's how you can generate the commands you want. This is for a single schema, and will just build the commands and output them. You could copy them into a script and review them before running. Or, you could change dbms_output.put_line( ... ) to EXECUTE IMMEDIATE ... to have this script execute all the statements as they are generated.
SET SERVEROUTPUT ON
BEGIN
FOR c IN
(SELECT t.table_name, c.column_name
FROM user_tables t, user_tab_columns c
WHERE c.table_name = t.table_name
AND data_type='VARCHAR2')
LOOP
dbms_output.put_line(
'UPDATE '||c.table_name||
' SET '||c.column_name||' = TRIM('||c.column_name||') WHERE '||
c.column_name||' <> TRIM('||c.column_name||') OR ('||
c.column_name||' IS NOT NULL AND TRIM('||c.column_name||') IS NULL)'
);
END LOOP;
END;
Presumably you want to do this for every column in a schema, not in the database. Trying to do this to the dictionary tables would be a bad idea...
declare
v_schema varchar2(30) := 'YOUR_SCHEMA_NAME';
cursor cur_tables (p_schema_name varchar2) is
select owner, table_name, column_name
from all_tables at,
inner join all_tab_columns atc
on at.owner = atc.owner
and at.table_name = atc.table_name
where atc.data_type = 'VARCHAR2'
and at.owner = p_schema;
begin
for r_table in cur_tables loop
execute immediate 'update ' || r.owner || '.' || r.table_name
|| ' set ' || r.column_name || ' = trim(' || r.column_name ||');';
end loop;
end;
This will only work for fields that are VARCHAR2s in the first place. If your database contains CHAR fields, then you're out of luck, because CHAR fields are always padded to their maximum length.

Resources