loop based on query result get error - oracle

I have the result of a first query, and then based on that result I want to join with other tables so I can select the fields in the query result and other tables.
I construct the following
BEGIN
FOR a_rec in (select col1, min(col2) as col2,col3 from a_tbl group by (col1, col3))
LOOP
select a_rec.col2, d_tbl.col4
from b_tbl b, c_tbl c, d_tbl d
where b.col1 = a_rec.col1 and c.col5 = b.col5 and d.col6 = c.col6;
END LOOP;
END;
then it reminds me that PLS-00428: an INTO clause is expected in this SELECT statement, so I create a temp table and the code becomes:
BEGIN
FOR a_rec in (select col1, min(col2) as col2,col3 from a_tbl group by (col1, col3))
LOOP
select a_rec.col2, d_tbl.col4 into tmp_tbl
from b_tbl b, c_tbl c, d_tbl d
where b.col1 = a_rec.col1 and c.col5 = b.col5 and d.col6 = c.col6;
END LOOP;
END;
then I get error like this PLS-00403: expression 'TMP_TBL' cannot be used as an INTO-target of a SELECT/FETCH statement
Maybe I should put the first query result into a temp table and then join it with other tables, although I wonder whether I can do it in such a procedure?

A SELECT INTO throws an error if the SELECT statement returns anything other than a single row. So that's clearly not what you want here. Since tmp_tbl is a global temporary table, you could do an INSERT SELECT in your loop
BEGIN
FOR a_rec in (select col1, min(col2) as col2,col3
from a_tbl
group by (col1, col3))
LOOP
INSERT INTO tmp_tbl( col2, col4 )
select a_rec.col2, d_tbl.col4
from b_tbl b,
c_tbl c,
d_tbl d
where b.col1 = a_rec.col1
and c.col5 = b.col5
and d.col6 = c.col6;
END LOOP;
END;
Since you say that you are just turning around and exporting the resulting data in SQL Developer, however, the temporary table doesn't seem particularly useful. You could remove all the PL/SQL and just write a single SELECT statement
select a.col2, d.col4
from (select col1, min(col2) as col2, col3
from a_tbl
group by col1, col3) a,
b_tbl b,
c_tbl c,
d_tbl d
where b.col1 = a.col1
and c.col5 = b.col5
and d.col6 = c.col6;

Related

How can I count the amount of values in different columns in oracle plsql

For example, I have a table with these values:
ID
Date
Col1
Col2
Col3
Col4
1
01/11/2021
A
A
B
2
01/11/2021
B
B
The A and B values are dynamic, they can be other characters as well.
Now I need somehow to get to the result that id 1 has 2 occurences of A and one of B. Id 2 has 0 occurences of A and 2 occurences of B.
I'm using dynamic SQL to do this:
for v_record in table_cursor
loop
for i in 1 .. 4
loop
v_query := 'select col'||i||' from table where id = '||v_record.id;
execute immediate v_query into v_char;
if v_char = "any letter I'm checking" then
amount := amount + 1;
end if;
end loop;
-- do somehting with the amount
end loop;
But there has to be a better much more efficient way to do this.
I don't have that much knowledge of plsql and I really don't know how to formulate this question in google. I've looked into pivot, but I don't think that will help me out in this case.
I'd appreciate it if someone could help me out.
Assuming the number of columns would be fixed at four, you could use a union aggregation approach here:
WITH cte AS (
SELECT ID, Col1 AS val FROM yourTable UNION ALL
SELECT ID, Col2 FROM yourTable UNION ALL
SELECT ID, Col3 FROM yourTable UNION ALL
SELECT ID, Col4 FROM yourTable
)
SELECT
t1.ID,
t2.val,
COUNT(c.ID) AS cnt
FROM (SELECT DISTINCT ID FROM yourTable) t1
CROSS JOIN (SELECT DISTINCT val FROM cte) t2
LEFT JOIN cte c
ON c.ID = t1.ID AND
c.val = t2.val
WHERE
t2.val IS NOT NULL
GROUP BY
t1.ID,
t2.val;
This produces:
Demo

Oracle delete from tableA where a duplicate row is in tableB

As the title says, I am looking for a way to remove all rows from TableA where there is a matching row in TableB.
the Tables A & B have about 30 columns in them so a WHERE A.col1 = B.col1 etc would be a little problematical. Ideally I was hoping for something like
DELETE FROM tableA WHERE IN TableB
(overly simplified by this type of thing)
IN clause can compare all columns returned from select
DELETE FROM tableA WHERE ( col1,col2,col3,.. ) IN ( select col1,col2,col3... FROM TableB );
The brute force way to establish if two records from each table are the same is to just compare every column:
DELETE
FROM tableA a
WHERE EXISTS (SELECT 1 FROM tableB b WHERE a.col1 = b.col1 AND a.col2 = b.col2 AND ...
a.col30 = b.col30);
You could create function which checks structures of tables and, if they are the same, creates string containing correct conditions to compare.
For example here are two tables:
create table t1 (id, name, age) as (
select 1, 'Tom', 67 from dual union all
select 2, 'Tia', 42 from dual union all
select 3, 'Bob', 16 from dual );
create table t2 (id, name, age) as (
select 1, 'Tom', 51 from dual union all
select 3, 'Bob', 16 from dual );
Now use function:
select generate_condition('T1', 'T2') from dual;
result:
T1.ID = T2.ID and T1.NAME = T2.NAME and T1.AGE = T2.AGE
Copy this, paste and run delete query:
delete from t1 where exists (select 1 from t2 where <<PASTE_HERE>>)
Here is the function, adjust it if needed. I used user_tab_columns so if tables are on different schemas you need all_tab_columns and compare owners too. If you have Oracle 11g you can replace loop with listagg(). Second table has to contain all columns of first table and they have to be same type and length.
create or replace function generate_condition(i_t1 in varchar2, i_t2 in varchar2)
return varchar2 is
v varchar2(1000) := '';
begin
for rec in (select column_name, u2.column_id
from user_tab_cols u1
left join (select * from user_tab_cols where table_name = i_t2) u2
using (column_name, data_type, data_length)
where u1.table_name = i_t1 order by u1.column_id)
loop
if rec.column_id is null then
v := 'ERR: incompatible structures';
goto end_loop;
end if;
v := v||' and '||i_t1||'.'||rec.column_name
||' = '||i_t2||'.'||rec.column_name;
end loop;
<< end_loop >>
return(ltrim(v, ' and '));
end;
If you want to avoid running process manually you need dynamic PL/SQL.
create table tableA (a NUMBER, b VARCHAR2(5), c INTEGER);
create table tableB (a NUMBER, b VARCHAR2(5), c INTEGER);
As you said
WHERE A.col1 = B.col1 etc would be a little problematical
you could intersect the tables and mention all columns from tableA one time, like this:
delete tableA
where (a,b,c) in (select * from tableA
intersect
select * from tableB);

subquery inside INSERT statement

I just recently found out that subqueries are not allowed in INSERT statements that are inside stored procedures. This is my script:
begin
execute immediate 'truncate table itcustadm.GL_DTPJ_TEST2';
insert into GL_DTPJ_TEST2
(rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select
tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
(select ent.bank_desc from crmuser.end ent where ent.bank_id = gam.bank_id);
But since the (select ent.bank_desc from crmuser.end ent where ent.bank_id = gam.bank_id) at the bottom of the SELECT statement is not allowed by Oracle, what's the best way to accomplish this?
I actually have this code right before the INSERT statement, but I don't know how to exactly use it:
get_bank_desc := '(select ent.bank_desc from crmuser.end ent ' ||
'where ent.bank_id = gam.bank_id)';
I am not sure what you are exactly trying for, but below code may be useful for you, you can achieve inserting a SubQuery output into a table using below query sample, but make sure output of the SubQuery is a single row o/p, so that you can escape from "ORA-01427: single-row SubQuery returns more than one row" ERROR.
insert into test_ins1
values(1,(SELECT COL2 FROM TEST_INS WHERE COL1=1 ));
Even then you can use rownum in where condition and take the single value.
Please let me know in case of any doubts
declare
bank_desc_temp bank_desk_type; /* the type defined in crmuser.ent for bank_desc*/
begin
select ent.bank_desc into bank_desc_temp from crmuser.end ent where ent.bank_id = gam.bank_id;
execute immediate 'truncate table itcustadm.GL_DTPJ_TEST2';
insert into GL_DTPJ_TEST2
(rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select
tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
bank_desc_temp;
end;
When you say "not allowed" what do you mean? Did you get an error?
I ask, because subqueries are definitely allowed inside an insert as select statement, providing you have the syntax correct (and the subquery returns at most one row), e.g.:
create table test_tab (col1 number, col2 varchar2(10));
begin
insert into test_tab
select 1,
(select 'Yes' from dual d2 where d.dummy = d2.dummy)
from dual d;
commit;
end;
/
select * from test_tab;
COL1 COL2
---------- ----------
1 Yes
There are some syntax issues with the code you provided - where is the from clause, and where are the tq and gam aliases defined?
There are two syntax you can use in your insert statement:
(I)
INSERT INTO table_name( column1, column2....columnN)
VALUES ( value1, value2....valueN);
(II)
INSERT INTO table (column1, column2, ... )
SELECT expression1, expression2, ...
FROM source_table(s)
WHERE conditions;
In your example, you should choose the second approach:
insert into GL_DTPJ_TEST2 (rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
ent.bank_desc
from crmuser.gam
join crmuser.end ent
on ent.bank_id = gam.bank_id
;
basically, if you want to add records using an insert statement, you should use a full select statement first. Here is how I would do it:
(1)
select *
from table1;
(2)
select column1
,column2
,column3
from table1;
(3)
select t1.column1
,t1.column2
,t1.column3
,t2.column4
,t2.column5
from table1 t1
join table2 t2
on t2.id = t1.id
;
(4)
insert into table3 (col1
,col2
,col3
,col4
,col5)
select t1.column1
,t1.column2
,t1.column3
,t2.column4
,t2.column5
from table1 t1
join table2 t2
on t2.id = t1.id
;

Insert into select query with cursor value

I want to write a SQK script with insert query in Oracle where one of the value will be fetched from cursor and rest all will be retrieved from table.
For example, consider Employee table:
Emp_No | Emp_Name
1 | AAA
...........
I am reading the table into a cursor.
Cursor c1 is select emp_no, emp_name from employee;
I am iterating the cursor and adding to a table along with information from another table.
for empCur in c1
loop
insert into employee_info(emp_no, emp_name, address, age, ... ) values (empCur.emp_no, empCur.emp_name, select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no)
end loop;
Is my script valid? If not is there any other way to achieve it? Since few values are in cursor and few are in another table I am not sure how to handle this.
Your script isn't correct because this
select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no
is not a valid expression. You may use a scalar subquery (one-row, one-column subquery) only, like this:
insert into t1(col1, col2, col3) values (1, (select col1 from t2), (select col2 from t2));
Or you may try insert from select:
for empCur in c1 loop
insert into employee_info(emp_no, emp_name, address, age, ... )
select empCur.emp_no, empCur.emp_name, t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no;
end loop;
If you want to use the cursor, why not just join the tables inside the cursor?
for empCur in ( select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no )
) loop
insert into employee_info(...) values ( empCur.emp_no, ...);
end loop;
Or with a sql insert: (if you can choose sql over pl/sql - T Kyte says do it)
insert into employee_info
select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no );

Improve performance of stored procedure where only select query is used

In our environment one procedure is taking long time to execute. I have checked the procedure, and below is the summary -
The procedure contains only select block (around 24). Before each select we are checking if data exists. If yes select the data, else do something else. For example :
-- Select block 1 --
IF EXISTS (SELECT 1 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
)
BEGIN
SELECT t1.col1,t2.col2,t2.col3 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
END
ELSE
BEGIN
SELECT 'DEFAULT1', 'DEFAULT2', 'DEFAULT3'
END
-- Select block 2 --
IF EXISTS (SELECT 1 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col5='someValue' AND t2.col5='someValue'
)
BEGIN
SELECT t1.col5,t2.col6,t2.col7 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col5='someValue' AND t2.col5='someValue'
END
ELSE
BEGIN
SELECT 'DEFAULT1', 'DEFAULT2', 'DEFAULT3'
END
I have come to an conclution that, somehow if we can combine the query that is used within IF EXISTS block into one query, and set some value to some variables so that we can identify which where condition returns true, that can reduce the execution time and improve the performance.
Is my thought correct? Is there any option to do that? Can you suggest any other options?
We are using Microsoft SQL Server 2005.
[Editted : Added] - All select statement doesn't return same column types they are different. And all select statements are required. If there are 24 if block, procedure should return 24 result-set.
[Added]
I would like to ask one more thing, which one of the below runs faster -
SELECT 1 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
SELECT COUNT(1) FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
SELECT TOP 1 1 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
Thanks.
Kartic
To enhance the performance of select query...create "index" on columns which you are using in where clause
like you are using the
WHERE t1.col2='someValue' AND t2.col2='someValue'
WHERE t1.col5='someValue' AND t2.col5='someValue'
so create database index on col2 and col5
Temp table
you can use the temp table to store the result. since you are using same query 24 time so first store the result of below query into the temp table (correct the syntax as require)
insert into temp_table (col2, col5)
SELECT col1, col5 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
Now use the temp table for checking
-- Select block 1 --
IF EXISTS (SELECT 1 FROM temp_table
WHERE t1.col2='someValue' AND t2.col2='someValue'
)
BEGIN
SELECT t1.col1,t2.col2,t2.col3 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
END
-- Select block 2 --
IF EXISTS (SELECT 1 FROM temp_table1
WHERE t1.col5='someValue' AND t2.col5='someValue'
)
BEGIN
SELECT t1.col5,t2.col6,t2.col7 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col5='someValue' AND t2.col5='someValue'
END
The current structure is not very efficient - you effectively have to execute each "if" statement (which will be expensive), and then repeat the same where clause (the expensive bit) if the "if" returns true. And you do this 24 times. Worst case (all the queries return data), you're doubling the time for the query.
You say you've checked for indexing - given that each query appears to be subtly different, it would be worth double checking this.
The obvious thing is to refactor the application to execute the 24 select statements, and deal with the fact that sometimes, they don't return any data. That's a fairly large refactoring, and I assume you've considered that...
If you can't do that, consider a less ambitious (though nastier) refactoring. Instead of checking whether data exists, and either returning it or an equivalent default result set, write it as a union:
SELECT t1.col1,t2.col2,t2.col3 FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
UNION
SELECT 'DEFAULT1', 'DEFAULT2', 'DEFAULT3'
This reduces the number of times you're hitting the where clause, but means your client application must filter out the "default" data.
To answer your final question, I'd run it through the query optimizer and look at the execution plan - but I'd imagine that the first version is fastest - the query can complete as soon as it finds the first record that matches the where criteria. The second version must find all records that match and count them; the final version must find all records and select the first one.
You could outer-join the results of a query to a row of default values, then fall back to the defaults when the query's results are empty:
SELECT
col1 = COALESCE(query.col1, defaults.col1),
col2 = COALESCE(query.col2, defaults.col2),
col3 = COALESCE(query.col3, defaults.col3)
FROM
(SELECT 'DEFAULT1', 'DEFAULT2', 'DEFAULT3') AS defaults (col1, col2, col3)
LEFT JOIN
(
SELECT t1.col1, t2.col2, t2.col3
FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
) query
ON 1=1 -- i.e. join all the rows unconditionally
;
The method may not suit you in exactly that form you if the subquery may actually return NULLs and those must not be replaced with default values. In that case, have the subqueries return a flag column (just any value). If that column evaluates to NULL in the final query, that can only mean that the subquery hasn't returned rows. You can use that fact in a CASE expression like this:
SELECT
col1 = CASE WHEN query.HasRows IS NULL THEN defaults.col1 ELSE query.col2 END,
col2 = CASE WHEN query.HasRows IS NULL THEN defaults.col2 ELSE query.col2 END,
col3 = CASE WHEN query.HasRows IS NULL THEN defaults.col3 ELSE query.col2 END
FROM
(SELECT 'DEFAULT1', 'DEFAULT2', 'DEFAULT3') AS defaults (col1, col2, col3)
LEFT JOIN
(
SELECT HasRows = 1, t1.col1, t2.col2, t2.col3
FROM table1 t1
INNER JOIN table2 t2
ON t1.col1=t2.col1
WHERE t1.col2='someValue' AND t2.col2='someValue'
) query
ON 1=1
;

Resources