BULK COLLECT INTO a UNION query into a table of objects - oracle

How can I collect into a table of objects, the values produced by a query that has a union in it as shown below
Select customer_name
from customer
where customer_id = 'xxx'
BULK COLLECT INTO customer_obj
UNION
Select customer_name
from customer
where customer_name like '%adam%'
the constraints above are completely made up.

The bulk collect clause comes right after the (first) select clause, before the (first) from clause. You have it in the wrong place.
It is not clear why you are using a union (although that by itself will not result in an error). Perhaps as an unintended consequence, you will get a list of distinct names, because that is what union does (as opposed to union all).
Other than that, as has been pointed out in a Comment already, you don't need union - you need an or in the where clause. But even if you modify your query that way, you still must move bulk collect to its proper place.

Another option would be to put your UNION into an inline view. For example,
SELECT cust.customer_name
BULK COLLECT
INTO customer_obj
FROM (
SELECT customer_name
FROM customer
WHERE customer_id = 'xxx'
UNION
SELECT customer_name
FROM customer
WHERE customer_name LIKE '%adam%'
) cust

Related

Using a CTE table as part of `START WITH` clause in recursive query

I don't seem to be able to use a CTE table as part of the below recursive query?
I have two CTE tables. One that gets a bunch of grouper codes and another that explodes those grouper codes out into the "child codes" (exploded_codes) that I want to use in the below query. When I add the EXPLODED_CODES CTE table into the IN() clause of my main query (shown below), no data is returned (no error though). However, if I hard code the values that are returned from a simple select statement on the EXPLODED_CODES CTE table, into the IN() clause, the query returns as expected! Any clue why this may be?
Here is my recursive query. Is there any reason anyone can think of that using the CTE table wouldnt work, but hard coding the values that the CTE table contains would? I know it has something to do with the recursion logic because when I change starts with to where and comment out the connect by clause, it returns some data (although its not the full parent-child hierarchy that the recursive query returns using the hard coded values.
select *
from events
start with
events.event_cd in (
select event_cd from exploded_codes --This code returns no data
--22600750,135148330,107919568 --These are the values in the CTE and returns correct data
)
connect by nocycle ((prior parent_event_id = event_id and prior event_id <> event_id)
or (prior event_id = parent_event_id) )
This is essentially all I am trying to do. However, it works fine in my mock ups like this:
with mytable as (
select 1 as pk1, 123 as event_id, 123 as parent_event_id, 777 as event_cd from dual
union select 2, 456, 123, 777 as event_cd from dual
union select 3, 756, 423, 999 as event_cd from dual
)
--select * from mytable
, codes as (
select 777 as event_cd from dual union select 88 as event_cd from dual union
select 111 as event_cd from dual
)
select myTable.*--, level, SYS_CONNECT_BY_PATH(event_id, '/') as path
from
myTable
start with event_cd in (select event_cd from codes)
connect by nocycle (prior parent_event_id = event_id and prior event_id <> event_id)
or (prior event_id = parent_event_id)
However, when I query my actual database table, it returns no rows depending on how I populate the CTE table (event though my table ends up with the same values each way.
What I cant seem to understand is that when I uncomment the below line from my CODES table, is when my recursive query doesnt return anything. However, when I hard code the values in like I am below, my recursive query returns fine. I have tried casting the datatype to NUMBER (which is how event_cd is stored in the EVENT table) and that has no affect.
with codes as (
--This method of populating the table causes my recursive query to not return anything
--select xcode from xr_template_codes where xtype = 'event-set' and SECTION_DISPLAY = 'Coding Summary'
--So does this method (which is the equivalent of the above).
--select xcode from xr_template_codes where xcode = 107919402
--union
--select xcode from xr_template_codes where xcode = 101320306
--This method seems to work fine. These are just the two values returned from the XR_TEMPLATES_CODES table.
select 101320306 as event_cd from dual union select 107919402 as event_cd from dual
)
select *
from
event
start with
event.event_cd in (select to_number(event_cd) from codes)
and event.id = 15169295
connect by nocycle (prior parent_event_id = event_id and prior event_id <> event_id)
or (prior event_id = parent_event_id)

Reduce overload on pl/sql

I have a requirement to do matching of few attributes one by one. I'm looking to avoid multiple select statements. Below is the example.
Table1
Col1|Price|Brand|size
-----------------------
A|10$|BRAND1|SIZE1
B|10$|BRAND1|SIZE1
C|30$|BRAND2|SIZE2
D|40$|BRAND2|SIZE4
Table2
Col1|Col2|Col3
--------------
B|XYZ|PQR
C|ZZZ|YYY
Table3
Col1|COL2|COL3|LIKECOL1|Price|brand|size
-----------------------------------------
B|XYZ|PQR|A|10$|BRAND1|SIZE1
C|ZZZ|YYY|D|NULL|BRAND2|NULL
In table3, I need to insert data from table2 by checking below conditions.
Find a match for record in table2, if Brand and size, Price match
If no match found, then try just Brand, Size
still no match found, try brand only
In the above example, for the first record in table2, found match with all the 3 attributes and so inserted into table3 and second record, record 'D' is matching but only 'Brand'.
All I can think of is writing 3 different insert statements like below into an oracle pl/sql block.
insert into table3
select from tab2
where all 3 attributes are matching;
insert into table3
select from tab2
where brand and price are matching
and not exists in table3 (not exists is to avoid
inserting the same record which was already
inserted with all 3 attributes matched);
insert into table3
select from tab2
where Brand is matching and not exists in table3;
Can anyone please suggest a better way to achieve it in any better way avoiding multiple times selecting from table2.
This is a case for OUTER APPLY.
OUTER APPLY is a type of lateral join that allows you join on dynamic views that refer to tables appearing earlier in your FROM clause. With that ability, you can define a dynamic view that finds all the matches, sorts them by the pecking order you've specified, and then use FETCH FIRST 1 ROW ONLY to only include the 1st one in the results.
Using OUTER APPLY means that if there is no match, you will still get the table B record -- just with all the match columns null. If you don't want that, you can change OUTER APPLY to CROSS APPLY.
Here is a working example (with step by step comments), shamelessly stealing the table creation scripts from Michael Piankov's answer:
create table Table1 (Col1,Price,Brand,size1)
as select 'A','10','BRAND1','SIZE1' from dual union all
select 'B','10','BRAND1','SIZE1' from dual union all
select 'C','30','BRAND2','SIZE2' from dual union all
select 'D','40','BRAND2','SIZE4'from dual
create table Table2(Col1,Col2,Col3)
as select 'B','XYZ','PQR' from dual union all
select'C','ZZZ','YYY' from dual;
-- INSERT INTO table3
SELECT t2.col1, t2.col2, t2.col3,
t1.col1 likecol1,
decode(t1.price,t1_template.price,t1_template.price, null) price,
decode(t1.brand,t1_template.brand,t1_template.brand, null) brand,
decode(t1.size1,t1_template.size1,t1_template.size1, null) size1
FROM
-- Start with table2
table2 t2
-- Get the row from table1 matching on col1... this is our search template
inner join table1 t1_template on
t1_template.col1 = t2.col1
-- Get the best match from table1 for our search
-- template, excluding the search template itself
outer apply (
SELECT * FROM table1 t1
WHERE 1=1
-- Exclude search template itself
and t1.col1 != t2.col1
-- All matches include BRAND
and t1.brand = t1_template.brand
-- order by match strength based on price and size
order by case when t1.price = t1_template.price and t1.size1 = t1_template.size1 THEN 1
when t1.size1 = t1_template.size1 THEN 2
else 3 END
-- Only get the best match for each row in T2
FETCH FIRST 1 ROW ONLY) t1;
Unfortunately is not clear what do you mean when say match. What is you expectation if there is more then one match?
Should it be only first matching or it will generate all available pairs?
Regarding you question how to avoid multiple inserts there is more then one way:
You could use multitable insert with INSERT first and condition.
You could join table1 to self and get all pairs and filter results in where condition
You could use analytical function
I suppose there is another ways. But why you would like to avoid 3 simple inserts. Its easy to read and maintain. And may be
There is example with analytical function next:
create table Table1 (Col1,Price,Brand,size1)
as select 'A','10','BRAND1','SIZE1' from dual union all
select 'B','10','BRAND1','SIZE1' from dual union all
select 'C','30','BRAND2','SIZE2' from dual union all
select 'D','40','BRAND2','SIZE4'from dual
create table Table2(Col1,Col2,Col3)
as select 'B','XYZ','PQR' from dual union all
select'C','ZZZ','YYY' from dual
with s as (
select Col1,Price,Brand,size1,
count(*) over(partition by Price,Brand,size1 ) as match3,
count(*) over(partition by Price,Brand ) as match2,
count(*) over(partition by Brand ) as match1,
lead(Col1) over(partition by Price,Brand,size1 order by Col1) as like3,
lead(Col1) over(partition by Price,Brand order by Col1) as like2,
lead(Col1) over(partition by Brand order by Col1) as like1,
lag(Col1) over(partition by Price,Brand,size1 order by Col1) as like_desc3,
lag(Col1) over(partition by Price,Brand order by Col1) as like_desc2,
lag(Col1) over(partition by Brand order by Col1) as like_desc1
from Table1 t )
select t.Col1,t.Col2,t.Col3, coalesce(s.like3, like_desc3, s.like1, like_desc1, s.like1, like_desc1),
case when match3 > 1 then size1 end as size1,
case when match1 > 1 then Brand end as Brand,
case when match2 > 1 then Price end as Price
from table2 t
left join s on s.Col1 = t.Col1
COL1 COL2 COL3 LIKE_COL SIZE1 BRAND PRICE
B XYZ PQR A SIZE1 BRAND1 10
C ZZZ YYY D - BRAND2 -

how to display identical values in a single column using EXCEL or ORACLE

Hello I need a formula in column ā€˜Cā€™ which calculates/adds the amount of B Column based on the column A ID. If there are several amounts in same ID it should add the total amount and would show the result in column ā€˜Cā€™ as a single row.
the output can be obtained from Oracle SQL query or an Excel formula.your help would be appreciated.
You can get the same output from Oracle itself, using analytical functions like below.
SUM() OVER(PARTITION BY ... ) -> This actually do the cumulative sum
WITH MYTABLE(ID,AMT) AS
(SELECT '2UF2', '500' FROM DUAL
UNION ALL
SELECT '2TC6', '300' FROM DUAL
UNION ALL
SELECT '2TC6', '200' FROM DUAL
UNION ALL
SELECT '2TC6', '800' FROM DUAL
)
SELECT ID,
AMT,
CASE ROW_NUMBER() OVER(PARTITION BY ID ORDER BY NULL)
WHEN 1
THEN SUM(AMT) OVER(PARTITION BY ID ORDER BY NULL)
END AS FORMULA
FROM MYTABLE
ORDER BY ID, FORMULA NULLS LAST;
SQL Fiddle Demo
You can use rollup in oracle
Select id,amt,sum (amt) nullFrom table nullGroup by rollup (id,amt)
For more details see below link
https://oracle-base.com/articles/misc/rollup-cube-grouping-functions-and-grouping-sets
In SQL you need an aggregation function, in this case sum, and a group by clause. The generic query should look like the following:
Select sum(b) from table group by a
I hope this helps.

select * from (select first_name, last_name from employees)

I do understand the meaning of this statement but I don't understand why do we need this?
This is equivalent to
select first_Name, last_name from employees
I can see this type of statements in many examples. Can you please explain when we need this? In practical do we use this type of statements?
Can you please explain when we need this?
These are called Derived Tables.
A "derived table" is essentially a statement-local temporary table
created by means of a subquery in the FROM clause of a SQL SELECT
statement. It exists only in memory and behaves like a standard view
or table.
In SQL, subqueries can only see values from parent queries one level deep.
In practical do we use this type of statements?
The most common use of it is the classic row-limiting query using ROWNUM.
Row-Limiting query:
SELECT *
FROM (SELECT *
FROM emp
ORDER BY sal DESC)
WHERE ROWNUM <= 5;
Pagination query:
SELECT eno
FROM (SELECT e.empno eno,
e.ROWNUM rn
FROM (SELECT empno
FROM emp
ORDER BY sal DESC) e)
WHERE rn <= 5;
This kind of statement is useless, you're right, but there are many occasions when you need a subselect because you can't do everything in one statement. Of the top of my head I'd be thinking about for instance, combining aggregate functions, get the min, max and avg of a sum
select min(t.summed), max(t.summed), avg(t.summed)
from (select type, sum(value) as summed from table1 group by type) t
this is just from the top of my head, but I did encounter many occasions where subselects in the from clause were necessary. Once the statements are complex enough you'll see it.

oracle not in query takes longer than in query

I have 2 tables each has about 230000 records. When I make a query:
select count(*)
from table1
where field1 in (select field2 from table2).
It takes about 0.2 second.
If I use the same query just changing in to not in
select count(*)
from table1
where field1 NOT in (select field2 from table2).
It never ends.
Why ?
It's the difference between a scan and a seek.
When you ask for "IN" you ask for specifically these values.
This means the database engine can use indexes to seek to the correct data pages.
When you ask for "NOT IN" you ask for all values except these values.
This means the database engine has to scan the entirety of the table/indexes to find all values.
The other factor is the amount of data. The IN query likely involves much less data and therefore much less I/O than the NOT IN.
Compare it to a phonebook, If you want people only named Smith you can just pick the section for Smith and return that. You don't have to read any pages in the book before or any pages after the Smith section.
If you ask for all non-Smith - you have to read all pages before Smith and all after Smith.
This illustrates both the seek/scan aspect and the data amount aspect.
Its better to user not exists, as not in uses row search which takes too long
In worst case both queries can be resolved using two full table scans plus a hash join (semi or anti). We're talking a few seconds for 230 000 rows unless there is something exceptionally going on in your case.
My guess is that either field1 or field2 is nullable. When you use a NOT IN construct, Oracle has to perform an expensive filter operation which is basically executing the inner query once for each row in the outer table. This is 230 000 full table scans...
You can verify this by looking at the the execution plan. It would look something like:
SELECT
FILTER (NOT EXISTS SELECT 0...)
TABLE ACCESS FULL ...
TABLE ACCESS FULL ...
If there are no NULL values in either column (field1, field2) you can help Oracle with this piece of information so another more efficient execution strategy can be used:
select count(*)
from table1
where field1 is not null
and field1 not in (select field2 from table2 where field2 is not null)
This will generate a plan that looks something like:
SELECT
HASH JOIN ANTI
FULL TABLE SCAN ...
FULL TABLE SCAN ...
...or you can change the construct to NOT EXISTS (will generate the same plan as above):
select count(*)
from table1
where not exists(
select 'x'
from table2
where table2.field2 = table1.field1
);
Please note that changing from NOT IN to NOT EXISTS may change the result of the query. Have a look at the following example and try the two different where-clauses to see the difference:
with table1 as(
select 1 as field1 from dual union all
select null as field1 from dual union all
select 2 as field1 from dual
)
,table2 as(
select 1 as field2 from dual union all
select null as field2 from dual union all
select 3 as field2 from dual
)
select *
from table1
--where field1 not in(select field2 from table2)
where not exists(select 'x' from table2 where field1 = field2)
Try:
SELECT count(*)
FROM table1 t1
LEFT JOIN table2 t2 ON t1.field1 = t2.field2
WHERE t2.primary_key IS NULL

Resources