oracle intersection with or without distinct - sql

I have following query:
select id from t1
intersect
select id from t2
intersect
select id from t3
id could be non unique in some tables, so I need to use distinct.
In general what is better:
select distinct id from (
select id from t1
intersect
select id from t2
intersect
select id from t3)
or
select distinct id from t1
intersect
select id from t2 -- here id is unique
intersect
select distinct id from t3

There is no need for the DISTINCT. The INTERSECT operator automatically produces a distinct set of values. As you can see in this example, x has two rows with an ID of 1, y only has one row with an ID of 1 and the INTERSECTION of the two produces just a single row
SQL> ed
Wrote file afiedt.buf
1 with x as (select 1 id from dual union all select 1 from dual),
2 y as (select 1 id from dual)
3 select id
4 from x
5 intersect
6 select id
7* from y
SQL> /
ID
----------
1
Even if you take the INTERSECT of the two row table with itself, you still get a single row in the output
SQL> ed
Wrote file afiedt.buf
1 with x as (select 1 id from dual union all select 1 from dual)
2 select id
3 from x
4 intersect
5 select id
6* from x
SQL> /
ID
----------
1

Related

Create a duplicate row on top of Select statement

table TEST
id
Name
1
abc
2
xyz
In general i used to get records from below query
Select id,name from TEST.
id
Name
1
abc
2
xyz
but now i want to create a duplicate for each row on top my select query
expected output: please suggest how can i achieve result like below
id
Name
1
abc
1
abc
2
xyz
2
xyz
You may cross join your table with a sequence table containing how ever many copies you want. Here is an example using an inline sequence table:
SELECT t1.id, t1.Name
FROM yourTable t1
CROSS JOIN (
SELECT 1 AS seq FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual
) t2
WHERE t2.seq <= 2
ORDER BY t1.id;
To me, UNION (ALL) set operator seems to be quite simple.
Sample data:
SQL> select * from test;
ID NAME
---------- ----
1 abc
2 xyz
UNION ALL:
SQL> select * from test
2 union all
3 select * from test;
ID NAME
---------- ----
1 abc
2 xyz
1 abc
2 xyz
SQL>
CREATE table test(
id integer,
name VARCHAR2(4)
);
INSERT into test (id, name) VALUES (1,'ABC');
INSERT into test (id, name) VALUES (2,'XYZ');
with data as (select level l from dual connect by level <= 2)
select *
from test, data
order by id, l
/
One more option is LATERAL
SELECT t.*
FROM test
, LATERAL (
SELECT id, name FROM DUAL
union all
SELECT id, name FROM DUAL
) t
One option is using a self-join along with ROW_NUMBER analytic function such as
WITH t AS
(
SELECT t1.id, t1.name, ROW_NUMBER() OVER (PARTITION BY t1.id ORDER BY 0) AS rn
FROM test t1,
test t2
)
SELECT id, name
FROM t
WHERE rn <= 2
Demo

How to use subquery to drop rows from Tab1 which are in Tab2 in Oracle SQL?

I have tables in Oracle SQL like below:
Tab1
ID
-----
1
2
3
Tab2
ID
-----
3
4
5
And I need to take values from Tab1 which are not in Tab2. I made query like below:
select ID
from Tab1
where ID not in (select ID from Tab2)
Above query does not work, how can I change it to achieve result as I need:
ID
---
1
2
I can add that I prefere to use subquery in this problem, how can I do that in Oracle SQL ?
With the MINUS set operator:
SQL> with
2 tab1 (id) as
3 (select 1 from dual union all
4 select 2 from dual union all
5 select 3 from dual
6 ),
7 tab2 (id) as
8 (select 3 from dual union all
9 select 4 from dual union all
10 select 5 from dual
11 )
12 select id from tab1
13 minus
14 select id from tab2;
ID
----------
1
2
SQL>
BTW, query you used (with a subquery) returns correct result; did you mean to say that you prefer NOT to use a subquery?
<snip>
12 select id from tab1
13 where id not in (select id from tab2);
ID
----------
1
2
I tried this code and it worked fine :
select ID
from Table1
where ID not in (select ID from Table2)
You cant DROP rows from a table, but you can DELETE them.
So correcting you title to
How to use subquery to DELETE rows from Tab1 which are in Tab2 in Oracle SQL?
do so:
delete from tab1
where id in (select id from tab2);
1 row deleted.
select * from tab1;
ID
----------
1
2
Do not forget to commit to make the change permanent.

return 0 in select count when no record found

i'm trying to get an id , and a count in same query result.
the problem is when the record doesn't exist , the count return null instead of 0
this is the query
SELECT DISTINCT Id
,(
SELECT count(*)
FROM table1
WHERE reference_id = 300000009798620
)
FROM table1
WHERE reference_id = 300000009798620;
Just use:
SELECT max(id) as id, count(*)
FROM table1
WHERE reference_id = 300000009798620
Please try the below modified query
SELECT 300000009798620 as reference_id, count(*)
FROM table1 WHERE reference_id = 300000009798620
Would this do?
table1 CTE represents your table; I simplified REFERENCE_ID. Both rows share the same reference_id value (1), with two different ID column values (1 and 2)
temp1 selects IDs for par_reference_id parameter value
temp2 counts rows for par_reference_id
First execution returns something as par_reference_id = 1 (count is 2):
SQL> with
2 table1 (id, reference_id) as
3 -- this represents your TABLE1 (but reference_id is way simpler)
4 (select 1, 1 from dual union all
5 select 2, 1 from dual),
6 temp_1 as
7 -- distinct IDs per desired reference_id
8 (select distinct id
9 from table1
10 where reference_id = &&par_reference_id
11 ),
12 temp_2 as
13 -- number of rows for that reference_id
14 (select count(*) cnt
15 from table1
16 where reference_id = &&par_reference_id
17 )
18 -- and finally:
19 select b.id, a.cnt
20 from temp_2 a left join temp_1 b on 1 = 1;
Enter value for par_reference_id: 1
ID CNT
---------- ----------
1 2
2 2
Let's try some other reference_id value (which doesn't exist in table1), e.g. 100: query doesn't return any ID, but count = 0 (as you wanted):
SQL> undefine par_reference_id
SQL> /
Enter value for par_reference_id: 100
ID CNT
---------- ----------
0
SQL>
You can use DUAL to always get a row back with your ID, then a subquery to get the count.
SELECT 300000009798620 AS id,
(SELECT COUNT (*)
FROM table1
WHERE reference_id = 300000009798620) AS amt
FROM DUAL;

How to pass list of columns from one table to another table in bigquery

Following is my Table-A
Column_Name Flag
----------- ----
col-A 1
col-B 1
col-C 1
col-D 2
Columns col-A, col-B, col-C, col-D are present in Table-B as follows
ID col-A col-B col-C col-D
1 a b c d
I want to write a query something like
select (select column_name from table-A where flag = 1) from table-B
Above query should translate to something like
select col-A, col-B, col-C from table-B.
I tried the following:
select Array_to_String(Array(select column_name from table-A where flag = 1)) from table-B
But the above function Array_to_string gives me the list of columns as a single string.
Below is example for BigQuery Standard SQL
I've added yet another table (tableC) to have association between group)flag) and respective action. Overall it is simplified (as your question is) but shows possible approach - that you still will need to apply to your specific use case
Note, based on your comments - I assume you apply aggregation on columns within the rows
#standardSQL
WITH `tableA` AS (
SELECT 'colA' Column_Name, 1 Flag UNION ALL
SELECT 'colB', 1 UNION ALL
SELECT 'colC', 1 UNION ALL
SELECT 'colD', 2 UNION ALL
SELECT 'colE', 2
), `tableB` AS (
SELECT 1 id, 1 colA, 2 colB, 3 colC, 4 colD, 5 colE UNION ALL
SELECT 2, 5, 6, 7, 8, 9
), `tableC` AS (
SELECT 1 flag, 'SUM' action UNION ALL
SELECT 2, 'BIT_AND'
)
SELECT id,
CASE action
WHEN 'SUM' THEN SUM(CAST(val AS INT64))
WHEN 'BIT_AND' THEN BIT_AND(CAST(val AS INT64))
END val
FROM (
SELECT id,
SPLIT(kv, ':')[OFFSET(0)] col,
SPLIT(kv, ':')[OFFSET(1)] val
FROM `tableB` t,
UNNEST(SPLIT(REGEXP_REPLACE(TO_JSON_STRING(t), r'^{|}$|"', ''))) kv
)
JOIN `tableA` ON col = Column_Name AND flag = 1 -- set flag here
JOIN `tableC` USING(flag)
GROUP BY id, action
You just need to set value for flag
If you set flag = 1 - result will be SUM of columns
Row id val
1 1 6
2 2 18
If you set flag = 2 - result will be BIT_AND applied to "survived" columns
Row id val
1 1 4
2 2 8

In Oracle, how do I get a page of distinct values from sorted results?

I have 2 columns in a one-to-many relationship. I want to sort on the "many" and return the first occurrence of the "one". I need to page through the data so, for example, I need to be able to get the 3rd group of 10 unique "one" values.
I have a query like this:
SELECT id, name
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
ORDER BY name, id;
There can be multiple rows in table2 for each row in table1.
The results of my query look like this:
id | name
----------------
2 | apple
23 | banana
77 | cranberry
23 | dark chocolate
8 | egg
2 | yak
19 | zebra
I need to page through the result set with each page containing n unique ids. For example, if start=1 and n=4 I want to get back
2
23
77
8
in the order they were sorted on (i.e., name), where id is returned in the position of its first occurrence. Likewise if start=3 and n=4 and order = desc I want
8
23
77
2
I tried this:
SELECT * FROM (
SELECT id, ROWNUM rnum FROM (
SELECT DISTINCT id FROM (
SELECT id, name
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
ORDER BY name, id)
WHERE ROWNUM <= 4)
WHERE rnum >=1)
which gave me the ids in numerical order, instead of being ordered as the names would be.
I also tried:
SELECT * FROM (
SELECT DISTINCT id, ROWNUM rnum FROM (
SELECT id FROM (
SELECT id, name
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
ORDER BY name, id)
WHERE ROWNUM <= 4)
WHERE rnum >=1)
but that gave me duplicate values.
How can I page through the results of this data? I just need the ids, nothing from the "many" table.
update
I suppose I'm getting closer with changing my inner query to
SELECT id, name, rank() over (order by name, id)
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
...but I'm still getting duplicate ids.
You may need to debug it a little, but but it will be something like this:
SELECT * FROM (
SELECT * FROM (
SELECT id FROM (
SELECT id, name, row_number() over (partition by id order by name) rn
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
)
) WHERE rn=1 ORDER BY name, id
) WHERE rownum>=1 and rownum<=4;
It's a bit convoluted (and I would tend to suspect that it could be simplified) but it should work. You'd can put whatever start and end position you want in the WHERE clause-- I'm showing here with start=2 and n=4 are pulled from a separate table but you could simplify things by using a couple of parameters instead.
SQL> ed
Wrote file afiedt.buf
1 with t as (
2 select 2 id, 'apple' name from dual union all
3 select 23, 'banana' from dual union all
4 select 77, 'cranberry' from dual union all
5 select 23, 'dark chocolate' from dual union all
6 select 8, 'egg' from dual union all
7 select 2, 'yak' from dual union all
8 select 19, 'zebra' from dual
9 ),
10 x as (
11 select 2 start_pos, 4 n from dual
12 )
13 select *
14 from (
15 select distinct
16 id,
17 dense_rank() over (order by min_id_rnk) outer_rnk
18 from (
19 select id,
20 min(rnk) over (partition by id) min_id_rnk
21 from (
22 select id,
23 name,
24 rank() over (order by name) rnk
25 from t
26 )
27 )
28 )
29 where outer_rnk between (select start_pos from x) and (select start_pos+n-1 from x)
30* order by outer_rnk
SQL> /
ID OUTER_RNK
---------- ----------
23 2
77 3
8 4
19 5