optimizing insert query - sql

I am trying to insert comma separated values in a global table. When data is large it's taking long time to process the data. I need to optimize my insert query, is there any other ways to achieve below insert statement for better optimization? Please check code below for more info. Appreciated for any help.
//my proc
emp_id in CLOB;
//insert statement
insert into Global_Emp_Tbl
with inputs(str) as(
select to_clob(emp_id)
from dual
),
temp_table(s, n, empid, st_pos, end_pos) as (
select ',' || str || ',', -1, null, null, 1
from inputs
union all
selct s, n+1, substr(s, st_pos, end_pos - st_pos),
end_pos + 1, instr(s, ',', 1, n+3)
from temp_table
where end_pos != 0
)
select empid from temp_table where empid is not null;
commit;
//using insert table in where clause
exists( select 1 from Global_Emp_Tbl gt where e.id =gt.emp_id ) //joining with main table

You can use simple REGEXP_SUBSTR to achieve the same
insert into Global_Emp_Tbl
SELECT Regexp_substr(empid, '[^,]+', 1, LEVEL) AS empid
FROM (SELECT To_clob(emp_id) empid
FROM dual)
CONNECT BY LEVEL <= Regexp_count(emp_id, '[^,]+');
commit;
Also, one more suggestion try changing the below in your function
select empid from temp_table where n > 0;

Well I can't reporduce your problem in Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
This procedure provides the sample data, a CLOB csv list of the first N interger
create or replace function get_list(N NUMBER) return CLOB
as
v_lst CLOB;
i PLS_INTEGER;
BEGIN
v_lst :='1';
for i in 2 .. n loop
v_lst :=v_lst||','||to_char(i);
end loop;
return(v_lst);
END;
/
Note that the 5K parameter give approximately 20K long list.
select length(get_list(5000)) from dual;
23892
The parsing of this list in a global temporary table is done in seconds not in minutes. Here an example using your SELECT
SQL> set timi on;
SQL> create global temporary table csv_tbl
2 ON COMMIT PRESERVE ROWS
3 as
4 with inputs(str) as(
5 select get_list(5000)
6 from dual
7 ),
8 temp_table(s, n, empid, st_pos, end_pos) as (
9 select ',' || str || ',', -1, null, null, 1
10 from inputs
11 union all
12 select s, n+1, substr(s, st_pos, end_pos - st_pos),
13 end_pos + 1, instr(s, ',', 1, n+3)
14 from temp_table
15 where end_pos != 0
16 )
17 select empid from temp_table where empid is not null
18 ;
Table created.
Elapsed: 00:00:01.35
So the most probably explanation is, that the most elapsed time is spend in the query with the EXISTS clause
exists( select 1 from Global_Emp_Tbl gt where e.id =gt.emp_id )
Two problems come to my mind
1) The datatypes of the EMP_ID in the temporary table and in the EMP table differs, e.g. in temporary table it is VARCHAR2, in the EMP table NUMBER - this will prohibit the use of the index on the EMP table.
2) The missing object statistics on the global temporary table leads the CBO to an wrong execution plan and you use the index (in a large nested loops) where you should use a full table scan.

Related

How to get the value for column reading as '=4*10*2' as 80 in sql - implement the same using select query without creating the function

I have read only access to this particular database, hence I am not allowed to create any functions.
I am trying to achieve the below one using select statement.
How to get the value for column reading as '=4*10*2' as 80 in sql - implement the same using select query without creating the function.
I used the below query:
qty
----
10*4*2
4*3*1
5*1*1
select case when length=1 then substr(qty,1,1)
when length=2 then substr(qty,1,1)*substr(qty,2,1)
when length=3 then substr(qty,1,1)*substr(qty,2,1)*substr(qty,3,1)
else qty
end
from (select replace(qty,'*','') as qty from table_quants);
The above query works fine until and unless the value does not contain 10s or zeroes.
i.e,
qty
10*4*2 0 ------> which is not correct, I should get 80 instead of zero
4*3*1 12
5*1*1 5
Can someone pls help me out.
If it were Oracle, then
SQL> with table_quants (id, qty) as
2 -- sample data
3 (select 1, '10*4*2' from dual union all
4 select 2, '4*3*1' from dual union all
5 select 3, '5*1*1' from dual
6 ),
7 split_qty as
8 -- split QTY column to rows
9 (select id,
10 qty,
11 regexp_substr(qty, '[^*]+', 1, column_value) val
12 from table_quants cross join
13 table(cast(multiset(select level from dual
14 connect by level <= regexp_count(qty, '\*') + 1
15 ) as sys.odcinumberlist))
16 )
17 -- compute the result
18 select id,
19 qty,
20 round(exp(sum(ln(val)))) result
21 from split_qty
22 group by id, qty
23 order by id;
ID QTY RESULT
---------- ------ ----------
1 10*4*2 80
2 4*3*1 12
3 5*1*1 5
SQL>
XMLTABLE is often a shortcut for simple expressions, eg
SQL> create table t ( expr varchar2(20));
Table created.
SQL> insert into t values ('1+2');
1 row created.
SQL> insert into t values ('1+2*7-3+11');
1 row created.
SQL> select * from t, xmltable(t.expr);
EXPR COLUMN_VALUE
-------------------- ------------------------------
1+2 3
1+2*7-3+11 23
Same idea with SQL Server, just a bit shorter:
with table_quants (id, qty) as
-- sample data
(select 1, '10*4*2' union all
select 2, '4*3*1' union all
select 3, '5*1*1'
)
select id, exp( (select sum(log(value)) from string_split(qty,'*')) ) result
from table_quants
outputs
id result
----------- ----------------------
1 80
2 12
3 5
(3 rows affected)
In Oracle, you can use a recursive sub-query factoring clause and simple string functions (which, in this testing, was faster than CROSS JOINing with a correlated TABLE collection expression generated by CAST and MULTISET):
WITH multiplied_values ( qty, value, start_pos, end_pos ) AS (
SELECT qty,
1,
1,
INSTR( qty, '*', 1 )
FROM table_name
UNION ALL
SELECT qty,
value * SUBSTR( qty, start_pos, end_pos - start_pos ),
end_pos + 1,
INSTR( qty, '*', end_pos + 1 )
FROM multiplied_values
WHERE end_pos > 0
)
SELECT qty,
value * SUBSTR( qty, start_pos ) AS value
FROM multiplied_values
WHERE end_pos = 0;
Which, for your sample data:
CREATE TABLE table_name ( qty ) AS
SELECT '10*4*2' FROM DUAL UNION ALL
SELECT '4*3*1' FROM DUAL UNION ALL
SELECT '5*1*1' FROM DUAL;
Outputs:
QTY | VALUE
:----- | ----:
10*4*2 | 80
4*3*1 | 12
5*1*1 | 5
db<>fiddle here
The equivalent in SQL Server is:
WITH multiplied_values ( qty, value, start_pos, end_pos ) AS (
SELECT qty,
1,
1,
CHARINDEX( '*', qty, 1 )
FROM table_name
UNION ALL
SELECT qty,
value * CAST( SUBSTRING( qty, start_pos, end_pos - start_pos ) AS INT ),
end_pos + 1,
CHARINDEX( '*', qty, end_pos + 1 )
FROM multiplied_values
WHERE end_pos > 0
)
SELECT qty,
value * CAST( SUBSTRING( qty, start_pos, LEN( qty ) - start_pos + 1 ) AS INT )
AS value
FROM multiplied_values
WHERE end_pos = 0;
db<>fiddle here
In Oracle you can do this:
with function evaluate_expression(p_expression in varchar2)
return number
is
l_cursor integer default dbms_sql.open_cursor;
l_feedback integer default 0;
l_retval number; /* with divisions we might get a NUMBER */
begin
dbms_sql.parse(l_cursor,'begin :ret_val := ' || p_expression ||'; end;', dbms_sql.native );
dbms_sql.bind_variable(l_cursor,':ret_val',l_retval);
l_feedback := dbms_sql.execute(l_cursor);
dbms_sql.variable_value(l_cursor, ':ret_val', l_retval);
dbms_sql.close_cursor(l_cursor);
return l_retval;
exception
when others then
dbms_sql.close_cursor(l_cursor);
if (sqlcode=-1476) then
return 0;
else
raise;
end if;
end;
select evaluate_expression('(3*(2+3)+10-1)/2') from dual;
EVALUATE_EXPRESSION('(3*(2+3)+10-1)/2')
---------------------------------------
12
Or if you have many expressions to evaluate you can create a view:
create view exprs as
select '(3*(2+3)+10-1)/2' expr from dual union all
select '1+2+3+4+5+6' from dual union all
select '1*2*3*4*5*6' from dual
;
and use the above to resolve the expressions:
with function evaluate_expression(p_expression in varchar2)
return number
is
l_cursor integer default dbms_sql.open_cursor;
l_feedback integer default 0;
l_retval number; /* with divisions we might get a NUMBER */
begin
dbms_sql.parse(l_cursor,'begin :ret_val := ' || p_expression ||'; end;', dbms_sql.native );
dbms_sql.bind_variable(l_cursor,':ret_val',l_retval);
l_feedback := dbms_sql.execute(l_cursor);
dbms_sql.variable_value(l_cursor, ':ret_val', l_retval);
dbms_sql.close_cursor(l_cursor);
return l_retval;
exception
when others then
dbms_sql.close_cursor(l_cursor);
if (sqlcode=-1476) then
return 0;
else
raise;
end if;
end;
select expr||'='||evaluate_expression(expr) expr
from (select expr from exprs)
;
EXPR
---------------------------------------------------------
(3*(2+3)+10-1)/2=12
1+2+3+4+5+6=21
1*2*3*4*5*6=720

Oracle - PLSQL to check the difference in number of records after a truncate/load procedure

Our IT team loads couple of tables every month. The new load should have more records than the previous load, with at least 2% more records.
It's a truncate and load process, I'm collecting the num of records from each table before the truncate, and I'm checking the difference in excel every month to make sure the data load is correct.
Is there anyway to automate this in Oracle.
eg:
Table_name Before_cnt After_cnt
XX_TEST1 4,606,619,326 4,983,759,822
XX_TEST2 121,973,005 123,161,581
You can apply the steps just like below :
SQL> create table XX_TEST1( id int primary key );
SQL> insert into XX_TEST1 select level from dual connect by level <= 100;
SQL> begin -- if table exists, then drop it!
for c in (select table_name from cat where table_name = 'XX_TEST1_OLD' )
loop
execute immediate 'drop table '||c.table_name;
end loop;
end;
/
SQL> create table XX_TEST1_old as select count(*) as cnt from XX_TEST1;
SQL> begin
execute immediate 'truncate table XX_TEST1';
end;
/
SQL> insert into XX_TEST1 select level from dual connect by level <= 103;
SQL> with xt1_new(cnt_new) as
(
select count(id) from XX_TEST1
)
select case when sign( (100 * ( cnt_new - cnt) / cnt)-2 ) = 1 then 1
else 0 end as "Rate Satisfaction"
from XX_TEST1_old
cross join xt1_new;
If this SELECT statement retuns 1, then we're successful to reach the target, else returns 0 and means we're unsuccessful.
Demo

my table contain data in below format and does not contain any automated primary key [duplicate]

I have column in my database where the values are coming like the following:
3862,3654,3828
In dummy column any no. of comma separated values can come. I tried with following query but it is creating duplicate results.
select regexp_substr(dummy,'[^,]+',1,Level) as dummycol
from (select * from dummy_table)
connect by level <= length(REGEXP_REPLACE(dummy,'[^,]+'))+1
I am not understanding the problem. Can anyone can help?
Works perfectly for me -
SQL> WITH dummy_table AS(
2 SELECT '3862,3654,3828' dummy FROM dual
3 )
4 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
5 FROM dummy_table
6 CONNECT BY level <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
7 /
DUMMYCOL
--------------
3862
3654
3828
SQL>
There are many other ways of achieving it. Read Split single comma delimited string into rows.
Update Regarding the duplicates when the column is used instead of a single string value. Saw the use of DBMS_RANDOM in the PRIOR clause to get rid of the cyclic loop here
Try the following,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
7 FROM dummy_table
8 CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
9 AND prior rn = rn
10 AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
11 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
SQL>
Update 2
Another way,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678,xyz' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(t.dummy, '[^,]+', 1, levels.column_value)) AS dummycol
7 FROM dummy_table t,
8 TABLE(CAST(MULTISET
9 (SELECT LEVEL
10 FROM dual
11 CONNECT BY LEVEL <= LENGTH (regexp_replace(t.dummy, '[^,]+')) + 1
12 ) AS sys.OdciNumberList)) LEVELS
13 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
xyz
6 rows selected.
SQL>
Giving a PL/SQL example where parsing over a table with an ID and column name. This will parse and print out each ID and the parsed value which could then be inserted into a new table or used in some other way.
Input
Column_ID Column_Name
123 (3862,3654,3828)
Output
Column_ID Column_Name
123 3862
123 3654
123 3828
PL/SQL Code
declare
table_name1 varchar2(1000);
string_to_parse varchar2(2000); -- assign string to table name
string_length number := 0; -- string length for loop
string_value varchar2(2000); -- string value to store value in
column_id number;
begin
--some table in the format '123' as column_id, '(3862,3654,3828)' as column_name
--remove the parenthesis or other special characters if needed
update some_table t
set t.column_name = regexp_replace(t.column_name,'\(|\)','');
commit;
for i in (
select * from some_table
) loop
column_id := i.column_id; --assign the id of the colors
string_to_parse := i.column_name; -- assign string to be parsed
if string_to_parse is null then
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
else
--String to parse is the comma
string_to_parse := string_to_parse||',';
string_length := length(string_to_parse) - length(replace(string_to_parse,',',''));
-- Loop through string from parameter
for i in 1 .. string_length loop
-- [^,] matches any character except for the ,
select regexp_substr(string_to_parse,'[^,]+',1,i)
into string_value -- stores value into string_value
from dual; -- dual is a dummy table to work around
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
--clear out the string value
string_value := null;
end loop;
end if;
end loop;
end;

Splitting comma separated values in Oracle

I have column in my database where the values are coming like the following:
3862,3654,3828
In dummy column any no. of comma separated values can come. I tried with following query but it is creating duplicate results.
select regexp_substr(dummy,'[^,]+',1,Level) as dummycol
from (select * from dummy_table)
connect by level <= length(REGEXP_REPLACE(dummy,'[^,]+'))+1
I am not understanding the problem. Can anyone can help?
Works perfectly for me -
SQL> WITH dummy_table AS(
2 SELECT '3862,3654,3828' dummy FROM dual
3 )
4 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
5 FROM dummy_table
6 CONNECT BY level <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
7 /
DUMMYCOL
--------------
3862
3654
3828
SQL>
There are many other ways of achieving it. Read Split single comma delimited string into rows.
Update Regarding the duplicates when the column is used instead of a single string value. Saw the use of DBMS_RANDOM in the PRIOR clause to get rid of the cyclic loop here
Try the following,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
7 FROM dummy_table
8 CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
9 AND prior rn = rn
10 AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
11 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
SQL>
Update 2
Another way,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678,xyz' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(t.dummy, '[^,]+', 1, levels.column_value)) AS dummycol
7 FROM dummy_table t,
8 TABLE(CAST(MULTISET
9 (SELECT LEVEL
10 FROM dual
11 CONNECT BY LEVEL <= LENGTH (regexp_replace(t.dummy, '[^,]+')) + 1
12 ) AS sys.OdciNumberList)) LEVELS
13 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
xyz
6 rows selected.
SQL>
Giving a PL/SQL example where parsing over a table with an ID and column name. This will parse and print out each ID and the parsed value which could then be inserted into a new table or used in some other way.
Input
Column_ID Column_Name
123 (3862,3654,3828)
Output
Column_ID Column_Name
123 3862
123 3654
123 3828
PL/SQL Code
declare
table_name1 varchar2(1000);
string_to_parse varchar2(2000); -- assign string to table name
string_length number := 0; -- string length for loop
string_value varchar2(2000); -- string value to store value in
column_id number;
begin
--some table in the format '123' as column_id, '(3862,3654,3828)' as column_name
--remove the parenthesis or other special characters if needed
update some_table t
set t.column_name = regexp_replace(t.column_name,'\(|\)','');
commit;
for i in (
select * from some_table
) loop
column_id := i.column_id; --assign the id of the colors
string_to_parse := i.column_name; -- assign string to be parsed
if string_to_parse is null then
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
else
--String to parse is the comma
string_to_parse := string_to_parse||',';
string_length := length(string_to_parse) - length(replace(string_to_parse,',',''));
-- Loop through string from parameter
for i in 1 .. string_length loop
-- [^,] matches any character except for the ,
select regexp_substr(string_to_parse,'[^,]+',1,i)
into string_value -- stores value into string_value
from dual; -- dual is a dummy table to work around
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
--clear out the string value
string_value := null;
end loop;
end if;
end loop;
end;

Incrementing values in table using PL/SQL?

In my query I'm using a for loop, which displays 1000 three times. I have to increment 1000 for each iteration of the loop, i.e. 1001, 1002,.. same number three times i.e. I want to add into my table 1000,1000,1000,1001,1001,1001 and 1002,1002,1002,
declare
CPName varchar(20) :=1000;
a number;
begin
for a in 1 .. 3 loop
insert into clients values (CPName,null,null);
end loop;
end;
How can I do this?
CPName is a VARCHAR; I assume you want this to be a number, in which case you just add it on.
There's no need to define the variable a either, it's implicitly declared by the LOOP. I would call this i as it's a more common name for an index variable.
declare
CPName integer := 1000;
begin
for i in 1 .. 3 loop
insert into clients values (CPName + i, null, null);
end loop;
end;
You can do this all in a single SQL statement though; there's no need to use PL/SQL.
insert into clients
select 1000 + i, null, null
from dual
cross join ( select level as i
from dual
connect by level <= 3 )
Based on your comments you actually want something like this:
insert into clients
with multiply as (
select level - 1 as i
from dual
connect by level <= 3
)
select 1000 + m.i, null, null
from dual
cross join multiply m
cross join multiply
This will only work if you want the same number of records as you want to increase so maybe you'd prefer to do it this way, which will give you a lot more flexibility:
insert into clients
with increments as (
select level - 1 as i
from dual
connect by level <= 5
)
, iterations as (
select level as j
from dual
connect by level <= 3
)
select 1000 + m.i, null, null
from dual
cross join increments m
cross join iterations
Using your LOOP methodology this would involve a second, interior loop:
declare
CPName integer := 1000;
begin
for i in 1 .. 3 loop
for j in 1 .. 3 loop
insert into clients values (CPName + i, null, null);
end loop;
end loop;
end;