Declare statement in PL/SQL for a part of the query - oracle

I have a query like the following:
select t1.varid, t1.var1, t1.var2, t1.vardata, t2.varid, t2.var1, t2,.var2
from table1 t1, table2 t2
where t1.varid = t2.varid
and t1.vardata between to_date('20170807','YYYYMMDD') and to_date('20170808','YYYYMMDD')
Now the question is:
I need to extract the data not only in the data range specified in the query above, but I want to extract many other ranges in the same query.
To do that, I used UNION ALL and it works. The problem is that I have to re-write this part many times:
select t1.varid, t1.var1, t1.var2, t1.vardata, t2.varid, t2.var1, t2, var2
from table1 t1, table2 t2
where t1.varid = t2.varid
Is there a tip for how to define select t1.varid... as a named variable in order to avoid a long boring repeated query?
Any other solutions are welcome.

As discussed in the chat you can do it as below:
SELECT t1.varid as col1,
t1.var1 as col2,
t1.var2,
t1.vardata,
t2.varid,
t2.var1,
t2,
.var2
FROM table1 t1, table2 t2
WHERE t1.varid = t2.varid
AND t1.vardata BETWEEN TO_DATE ('20170807', 'YYYYMMDD')
AND TO_DATE ('20170808', 'YYYYMMDD')
--- Give date ranges.
OR t1.vardata BETWEEN TO_DATE ('20170907', 'YYYYMMDD')
AND TO_DATE ('20171008', 'YYYYMMDD')
OR ....
OR ....

You can try fetching the dates directly from a table like this instead of rewriting repeated statements :
Select 'select t1.varid, t1.var1, t1.var2, t1.vardata, t2.varid, t2.var1, t2,.var2
From table1 t1, table2 t2 where t1.varid = t2.varid and t1.vardata between to_date('''+MY_DATE+''', ''yyyymmdd'')' from tableofdate
Below is the schema i used.This is just a sample.Please edit it as per your requirements
create table tableofdate(my_date varchar(100))
insert into tableofdate values ('20170808')

Related

finding the best match

I am trying to find best match value from table T2 with respect to the value in table T3. For example, here I am expecting to get 441 as result.
Please advise.
Thanks.
create table t3 (rc varchar2(20));
create table t2 (rcs varchar2(20));
insert into T3 values ('441449729804');
insert into T2 values ('44');
insert into T2 values ('441');
commit;
If you only ever have a single row selected from t3 then:
SELECT t3.rc, t2.rcs
FROM t2
INNER JOIN t3
ON t3.rc LIKE t2.rcs || '%'
ORDER BY LENGTH(t2.rcs) DESC
FETCH FIRST ROW ONLY
If you can have multiple rows from t3 and want the best match for each then:
SELECT t3.rc,
t2.rcs
FROM t3
CROSS JOIN LATERAL (
SELECT rcs
FROM t2
WHERE t3.rc LIKE t2.rcs || '%'
ORDER BY LENGTH(t2.rcs) DESC
FETCH FIRST ROW ONLY
) t2
or:
SELECT rc,
rcs
FROM (
SELECT t3.rc,
t2.rcs,
ROW_NUMBER() OVER (PARTITION BY t3.rc ORDER BY LENGTH(t2.rcs) DESC) AS rn
FROM t2
INNER JOIN t3
ON t3.rc LIKE t2.rcs || '%'
)
WHERE rn = 1;
Which, for your sample data, all output:
RC
RCS
441449729804
441
fiddle

How do I insert values in a table using inner join?

I am trying to insert data into table1.col1 using following query.
INSERT INTO table1 t1( t1.col1)
SELECT t2.col1
FROM table2 t2
WHERE t1.col2= t2.col2;
Apparently, it wouldn't work(flawed logic maybe). How can I achieve similar results.
Let me know if I don't make sense.
INSERT INTO table1 (col1)
SELECT t2.col1
FROM table2 t2
INNER JOIN table1 t1 on t1.col2= t2.col2;
INSERT INTO table1 (col1)
SELECT t2.col1
FROM table1 t1,table2 t2
WHERE t1.col2= t2.col2;
It seems you need a MERGE statement with MATCHED(for already existing rows in table1) and
NOT MATCHED(for rows not inserted into table1 yet) options :
MERGE INTO table1 t1
USING table2 t2
ON (t1.col2 = t2.col2)
WHEN MATCHED THEN
UPDATE SET t1.col1 = t2.col1
WHEN NOT MATCHED THEN
INSERT (col1,col2)
VALUES (t2.col1, t2.col2);
Demo
So, I was not looking to insert but to update...stupid question I know :)
This is what I was looking for.
update table1 t1 set t1.col1 = (select t2.col1 from table2 t2 where t1.col2 = t2.col2);

Accessing aliased tables

This question is wrong. I had some very big misunderstanding about how union works. I am reading about it now.
edit 04.12.2016
If you are still intersted, you can go here
Selecting the right column
I have something like this
with table3 as
(
select t1.c1, t1.c2...
from table1 t1
union all
select t2.c1, t2.c2...
from table2 t2
)select * from table3
I need to insert all rows from above in another table
insert into table4 t4
(
t4.c1, t4.c2...
)
select t3.c1, t3.c2...
from table3 t3
My question is, will this insert work. I have clumns in table 1 and 2 named the same, will I need to reference them somehow differently?
Do I need to write it like this?
insert into table4 t4
(
t4.c1, t4.c2...
)
select t3.t1.c1, t3.t1.c2, t3.t2.c1...
from table3 t3
with is part of select statement. You can insert result of select and you can use with in this select. Maybe syntax is not the most intuitive but this should work:
insert into table4
with table3 as
(
select t1.c1, t1.c2...
from table1 t1
union all
select t2.c1, t2.c2...
from table2 t2
) select * from table3;
And no you don't need (even can't) use double aliases.
No alias needed
if the column match you could simply use insert select
insert into table4
( select t1.c1, t1.c2...
from table1 t1
union all
select t2.c1, t2.c2...
from table2 t2)
otherwise you should declare the column name
insert insert into table4(c1, c2... )
( select t1.c1, t1.c2...
from table1 t1
union all
select t2.c1, t2.c2...
from table2 t2)
Assuming that you needto use that UNION ALL, instead of single insert-as-select statements to insert into another table, you can try to use different aliases for columns from different tables:
with table1 as
(
select t2.name as t2_name,
t2.address as t2_address,
t2.age as t2_age,
null as t3_name,
null as t3_address,
null as t3_age,
from table2 t2
union all
select null,
null,
null,
t3.name,
t3.address,
t3.age
from table3 t3
)

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
;

How to optimize this SELECT with sub query Oracle

Here is my query,
SELECT ID As Col1,
(
SELECT VID FROM TABLE2 t
WHERE (a.ID=t.ID or a.ID=t.ID2)
AND t.STARTDTE =
(
SELECT MAX(tt.STARTDTE)
FROM TABLE2 tt
WHERE (a.ID=tt.ID or a.ID=tt.ID2) AND tt.STARTDTE < SYSDATE
)
) As Col2
FROM TABLE1 a
Table1 has 48850 records and Table2 has 15944098 records.
I have separate indexes in TABLE2 on ID,ID & STARTDTE, STARTDTE, ID, ID2 & STARTDTE.
The query is still too slow. How can this be improved? Please help.
I'm guessing that the OR in inner queries is messing up with the optimizer's ability to use indexes. Also I wouldn't recommend a solution that would scan all of TABLE2 given its size.
This is why in this case I would suggest using a function that will efficiently retrieve the information you are looking for (2 index scan per call):
CREATE OR REPLACE FUNCTION getvid(p_id table1.id%TYPE)
RETURN table2.vid%TYPE IS
l_result table2.vid%TYPE;
BEGIN
SELECT vid
INTO l_result
FROM (SELECT vid, startdte
FROM (SELECT vid, startdte
FROM table2 t
WHERE t.id = p_id
AND t.startdte < SYSDATE
ORDER BY t.startdte DESC)
WHERE rownum = 1
UNION ALL
SELECT vid, startdte
FROM (SELECT vid, startdte
FROM table2 t
WHERE t.id2 = p_id
AND t.startdte < SYSDATE
ORDER BY t.startdte DESC)
WHERE rownum = 1
ORDER BY startdte DESC)
WHERE rownum = 1;
RETURN l_result;
END;
Your SQL would become:
SELECT ID As Col1,
getvid(a.id) vid
FROM TABLE1 a
Make sure you have indexes on both table2(id, startdte DESC) and table2(id2, startdte DESC). The order of the index is very important.
Possibly try the following, though untested.
WITH max_times AS
(SELECT a.ID, MAX(t.STARTDTE) AS Startdte
FROM TABLE1 a, TABLE2 t
WHERE (a.ID=t.ID OR a.ID=t.ID2)
AND t.STARTDTE < SYSDATE
GROUP BY a.ID)
SELECT b.ID As Col1, tt.VID
FROM TABLE1 b
LEFT OUTER JOIN max_times mt
ON (b.ID = mt.ID)
LEFT OUTER JOIN TABLE2 tt
ON ((mt.ID=tt.ID OR mt.ID=tt.ID2)
AND mt.startdte = tt.startdte)
You can look at analytic functions to avoid having to hit the second table twice. Something like this might work:
SELECT id AS col1, vid
FROM (
SELECT t1.id, t2.vid, RANK() OVER (PARTITION BY t1.id ORDER BY
CASE WHEN t2.startdte < TRUNC(SYSDATE) THEN t2.startdte ELSE null END
NULLS LAST) AS rn
FROM table1 t1
JOIN table2 t2 ON t2.id IN (t1.ID, t1.ID2)
)
WHERE rn = 1;
The inner select gets the id and vid values from the two tables with a simple join on id or id2. The rank function calculates a ranking for each matching row in the second table based on the startdte. It's complicated a bit by you wanting to filter on that date, so I've used a case to effectively ignore any dates today or later by changing the evaluated value to null, and in this instance that means the order by in the over clause needs nulls last so they're ignored.
I'd suggest you run the inner select on its own first - maybe with just a couple of id values for brevity - to see what its doing, and what ranks are being allocated.
The outer query is then just picking the top-ranked result for each id.
You may still get duplicates though; if table2 has more than one row for an id with the same startdte they'll get the same rank, but then you may have had that situation before. You may need to add more fields to the order by to break ties in a way that makes sens to you.
But this is largely speculation without being able to see where your existing query is actually slow.

Resources