Alternative to UNION clause - sql

I'm trying to teach myself and better understand alternative methods to a UNION and discover when I can use joins.
As I'm playing with this I can't seem to get what I want without a UNION. Is it possible to write this in a single query?
SELECT DISTINCT a.id
FROM table1 a, table2 b
WHERE a.id = b.id
AND a.ind IS NULL
AND b.year >= '2017'
AND b.code IN ('01','02','03')
AND b.flag NOT IN ('F','L')
UNION
SELECT DISTINCT a.id
FROM table1 a, table3 c
WHERE a.id = c.id
AND a.ind IS NULL
AND c.area = 'MAIN'
AND SYSDATE >= c.start
Thanks in advance for any guidance or help.

SELECT DISTINCT a.id
FROM table1 a
LEFT JOIN table2 b on b.id = a.id AND b.year >= '2017'
AND b.code IN ('01', '02', '03') AND b.flag NOT IN ('F', 'L')
LEFT JOIN table3 c ON a.id = c.id and c.area = 'MAIN' and SYSDATE >= c.start
WHERE a.ind IS NULL
AND ( b.id IS NOT NULL or c.id IS NOT NULL)
This is one of those things where the old obsolete A,B join syntax really shows it's age, when you have some conditions that must go in a specific ON clause and others that must go in the WHERE clause. It would be very difficult to write this query that way, and even harder to read and understand it later. Better to always write out the full INNER JOIN, LEFT JOIN, etc.

Whenever you see a distinct, resources have been wasted.
SELECT a.id
FROM table1 a
where a.ind IS NULL
and ( exists (select null from table2 b
WHERE a.id = b.id
AND b.year >= '2017'
AND b.code IN ('01','02','03')
AND b.flag NOT IN ('F','L') )
or exists (SELECT null FROM table3 c
WHERE a.id = c.id
AND c.area = 'MAIN'
AND SYSDATE >= c.startdt)
)
Indexes on table2 (id,year,code,flag) and table3 (id,area,startdt) won't hurt performance. Oracle 11gR2 did not allow me to have a column named "start".

I would use this
select a.id
from table1 a
where
a.ind is null and (
a.id in (
select b.id
from table2 b
where
b.year >= '2017'
and b.code IN ('01','02','03')
and b.flag NOT IN ('F','L')
) or
a.id in (
select c.id
from table3 c
where
c.area = 'MAIN'
and sysdate >= c.start
)
)
DISTINCT + table joins can be rewritten as IN/EXISTS most of the times. In several databases it might even select a better execution plan (e.g: Oracle)

Please let me know if using Full outer join makes sense
with cte as (
select 1 as nu union
select 2 as nu union
select 3 as nu union
select 4 as nu union
select 5 as nu union
select 6 as nu )
,cte2 as (
select 1 as nu union
select 2 as nu union
select 3 as nu )
,cte3 as (
select 7 as nu union
select 8 as nu union
select 9 as nu )
select coalesce(cte.nu,cte2.nu,cte3.nu)
from cte
full outer join cte2 on cte.nu = cte2.nu
full outer join cte3 on cte.nu = cte3.nu

Please check if this works
SELECT DISTINCT a.id
FROM table1 a, table2 b, table3 c
WHERE (a.id = b.id
OR a.id = c.id)
AND a.ind IS NULL
AND (( b.year >= '2017'
AND b.code IN ('01','02','03')
AND b.flag NOT IN ('F','L'))
OR (c.area = 'MAIN'
AND SYSDATE >= c.start))

Related

How to subtract values from different tables?

I need to get the result of subtract values from these 3 different tables in SQL Server.
This is my SQL:
SELECT COUNT(A.Id)
FROM Table_A AS A WITH (NOLOCK)
WHERE A.City = 'NewYork'
SELECT COUNT(B.Id)
FROM Table_B AS B WITH (NOLOCK)
WHERE B.City = 'England'
SELECT COUNT(C.Id)
FROM Table_C AS C WITH (NOLOCK)
WHERE C.City = 'Berlin'
Let's say the result of the first query is 9, and the second one is 1, and the third one is 3.
I need to get (9-1-3 = 5). How can I do this?
You can do this with a CTE or with subqueries.
Using a CTE:
WITH tbla AS (
SELECT COUNT(A.Id) A
FROM Table_A AS A
WHERE A.City = 'NewYork'
),
tblb AS (
SELECT COUNT(B.Id) B
FROM Table_B AS B
WHERE B.City = 'England'
),
tblc AS (
SELECT COUNT(C.Id) C
FROM Table_C AS C
WHERE C.City = 'Berlin'
)
SELECT a.A - b.B - c.C
FROM tbla a
CROSS JOIN tblb b
CROSS JOIN tblc c;
Same thing only with subqueries
SELECT a.A - b.B - c.C
FROM (
SELECT COUNT(A.Id) A
FROM Table_A AS A
WHERE A.City = 'NewYork'
) a
CROSS JOIN (
SELECT COUNT(B.Id) B
FROM Table_B AS B
WHERE B.City = 'England'
) b
CROSS JOIN (
SELECT COUNT(C.Id) C
FROM Table_C AS C
WHERE C.City = 'Berlin'
) c;
Note: I removed the WITH (NOLOCK) hints because in all likelihood they are not needed in this case.
As all queries return a scalar value 8one row one column you can simply add or subtract them
SELECT
(SELECT
COUNT(A.Id)
FROM
Table_A AS A
WHERE
A.City = 'NewYork') - (SELECT
COUNT(B.Id)
FROM
Table_B AS B
WHERE
B.City = 'England') - (SELECT
COUNT(C.Id)
FROM
Table_C AS C
WHERE
C.City = 'Berlin') as Whatever

return pair of values instead of one value from two queries

I've got two queries that return single result.
They look something like this
// query 1
SELECT A.id FROM tableA A
INNER JOIN tableB B
ON B.id = A.id
WHERE b.status = 'ACTIVE'
// query 2
SELECT C.id FROM tableC C
WHERE c.status = 'ACTIVE'
How to combine them and make return the pair of values instead of one value from different queries? I mean to get something like [A.id, C.id]
Currently I have to use two queries in the applications and I want to combine them into one.
I think like this will do
SELECT (SELECT A.id FROM tableA A
INNER JOIN tableB B
ON B.id = A.id
WHERE b.status = 'ACTIVE'
) as 'query1',
(
SELECT C.id FROM tableC C
WHERE c.status = 'ACTIVE'
) as 'query2'
As your question is not clear, so i assume that you either needids from mentioned queries in one row or in different rows, you can use union all/union (provided that datatypes are compatible or implicitly convertible and duplicates or allowed or not) as below.
Combining Result in different rows.
SELECT A.id
FROM tableA A
INNER JOIN tableB B
ON B.id = A.id
WHERE b.status = 'ACTIVE'
union all
SELECT C.id
FROM tableC C
WHERE c.status = 'ACTIVE'
Combining Result in Single Row.
select max(id1), max(id2)
from(
SELECT A.id as id1, NULL as id2
FROM tableA A
INNER JOIN tableB B
ON B.id = A.id
WHERE b.status = 'ACTIVE'
union all
SELECT NULL, C.id
FROM tableC C
WHERE c.status = 'ACTIVE'
) t;
SAMPLE DEMO
You can run following query which work fine for me:
select t1.id as aid ,t2.id as cid
from (
SELECT A.id
FROM tableA A
INNER JOIN tableB B ON B.id = A.id
WHERE b.status = 'ACTIVE'
) t1
full outer join (
SELECT C.id
FROM tableC C
WHERE c.status = 'ACTIVE'
) t2 on t1.id=t2.id
You can join your second query with your first query as follows, so that you will get two (A.id, C.id) values in one query...
SELECT A.ID,C.ID FROM
(SELECT A.ID FROM table_A A INNER JOIN
table_B B ON A.ID=B.ID WHERE B.STATUS='A')A
INNER JOIN table_c C
ON C.ID=A.ID WHERE C.STATUS='A';

Query Logic best approach

i'm after the data obtained by my two queries plus any other data from the driving table. I'm using the following code but have a feeling my results are wrong.
select * from(
select * from tbl_a a
inner join tbl_b b on (a.id = b.id and a.col_a = b.col_b and a.col_c = '1')
union all
select * from tbl_a a
inner join tbl_b b on (a.col_a = b.col_b and a.col_c = '1')
where (1=1)
and a.id <> b.id
and a.start_time <= b.u_start_time
and a.end_time >= b.u_end_time
union all
select * from tbl_a a
where a.another_id
NOT IN ( -- either query above)
) results;
I'd just like to know if this makes sense or how I could possibly simplify some of this...
Here is query for the first 2 unions,and it is not clear what is the third union condition
SELECT *
FROM
tbl_a a
left join tbl_b b on b.id = a.id and b.col_b = a.col_a
left join tbl_b b1 on a.col_a= b1.col_b and a.id<>b1.id and a.start_time<=b1.u_start_time and a.end_time>=b1.u_end_time
WHERE
a.col_c=1
and COALESCE(b.id,b1.id) is not null

How to perform FULL OUTER JOIN in ORACLE using '+' operator?

Instead of using keywords like FULL OUTER JOIN or FULL JOIN, how can I perform full outer join using 'where' clause with the help of '+' operator?!
You can't (at least directly). Oracle only supports a full outer join using SQL:1999 syntax.
You can fake it by unioning two outer joins:
select a.field1, b.field2
from table_a a, table_b b
where a.id = b.id(+)
union all
select a.field1, b.field2
from table_a a, table b b
where a.id(+) = b.id
and a.id is null
It's a lot more readable using the SQL:1999 syntax:
select a.field1, b.field2
from table_a a full outer join table_b b
on a.id = b.id
Here's an example you can run in oracle to see the results for yourself as well.
with
a as
(select 'A' tbl, level id from dual connect by level < 1000),
b as
(select 'B' tbl, level + 500 id from dual connect by level < 1000)
select a.tbl, a.id, b.tbl, b.id from a, b where a.id = b.id(+)
union all
select a.tbl, a.id, b.tbl, b.id from a, b where a.id(+) = b.id and a.id is null
Is the same as:
with
a as
(select 'A' tbl, level id from dual connect by level < 1000),
b as
(select 'B' tbl, level + 500 id from dual connect by level < 1000)
select a.tbl, a.id, b.tbl, b.id from a full outer join b on a.id = b.id

Aliasing derived table which is a union of two selects

I can't get the syntax right for aliasing the derived table correctly:
SELECT * FROM
(SELECT a.*, b.*
FROM a INNER JOIN b ON a.B_id = b.B_id
WHERE a.flag IS NULL AND b.date < NOW()
UNION
SELECT a.*, b.*
FROM a INNER JOIN b ON a.B_id = b.B_id
INNER JOIN c ON a.C_id = c.C_id
WHERE a.flag IS NOT NULL AND c.date < NOW())
AS t1
ORDER BY RAND() LIMIT 1
I'm getting a Duplicate column name of B_id. Any suggestions?
The problem isn't the union, it's the select a.*, b.* in each of the inner select statements - since a and b both have B_id columns, that means you have two B_id cols in the result.
You can fix that by changing the selects to something like:
select a.*, b.col_1, b.col_2 -- repeat for columns of b you need
In general, I'd avoid using select table1.* in queries you're using from code (rather than just interactive queries). If someone adds a column to the table, various queries can suddenly stop working.
In your derived table, you are retrieving the column id that exists in table a and table b, so you need to choose one of them or give an alias to them:
SELECT * FROM
(SELECT a.*, b.[all columns except id]
FROM a INNER JOIN b ON a.B_id = b.B_id
WHERE a.flag IS NULL AND b.date < NOW()
UNION
SELECT a.*, b.[all columns except id]
FROM a INNER JOIN b ON a.B_id = b.B_id
INNER JOIN c ON a.C_id = c.C_id
WHERE a.flag IS NOT NULL AND c.date < NOW())
AS t1
ORDER BY RAND() LIMIT 1
First, you could use UNION ALL instead of UNION. The two subqueries will have no common rows because of the excluding condtion on a.flag.
Another way you could write it, is:
SELECT a.*, b.*
FROM a
INNER JOIN b
ON a.B_id = b.B_id
WHERE ( a.flag IS NULL
AND b.date < NOW()
)
OR
( a.flag IS NOT NULL
AND EXISTS
( SELECT *
FROM c
WHERE a.C_id = c.C_id
AND c.date < NOW()
)
)
ORDER BY RAND()
LIMIT 1