How could I DRY this SQL which UNION ALL two similar statements? - sql

I need to UNION the results of the same SELECT code over two different schemas. (Example below.) But every time I need to change something in the code, I have to change it twice. I've already been bitten for forgetting to make the change in the second SELECT. Is there a way to abstract the SELECT statement to some sort of function?
This is a query saved on a .sql file, which I run sometimes. I do not have permissions to create views or functions to the DB.
SELECT a.column_a,
b.column_b,
c.column_c
FROM schema_A.table_a AS a,
schema_A.table_b AS b,
schema_A.table_c AS c
WHERE a.id_b = b.id
AND b.id_c = c.id
AND a.column_a LIKE 'something%'
UNION ALL
SELECT a.column_a,
b.column_b,
c.column_c
FROM schema_B.table_a AS a,
schema_B.table_b AS b,
schema_B.table_c AS c
WHERE a.id_b = b.id
AND b.id_c = c.id
AND a.column_a LIKE 'something%'
I'd like to be able to do something like this:
FUNCTION something(#schema) AS (
SELECT a.column_a,
b.column_b,
c.column_c
FROM #schema.table_a AS a,
#schema.table_b AS b,
#schema.table_c AS c
WHERE a.id_b = b.id
AND b.id_c = c.id
AND a.column_a LIKE 'something%'
)
SELECT something(schema_A)
UNION ALL
SELECT something(schema_B)

You could do:
SELECT a.column_a,
b.column_b,
c.column_c
FROM (select * from schema_A.table_a
union all
select * from schema_B.table_a
) AS a,
(Select * from schema_A.table_b
union all
Select * from schema_b.table_b
) AS b,
(select * from schema_A.table_c
union all
select * from schema_B.table_c
) AS c
WHERE a.id_b = b.id
AND b.id_c = c.id
AND a.column_a LIKE 'something%'
But that would not make life easier....... 😉
EDIT: My assumption that above statement has the same result as te original query is wrong, see DBFIDDLE.
I need more ☕☕ to know why ... 😉

Related

How to use NOT IN with the Union Result

I am trying to get the distinct values from one table having different conditions.
The Query that I am using is
Select A.*
from A
where A.x not in (select B.x from B
union
select C.x from C
)
Please note that I have been using CTE to get the different values.
When I standalone run this select B.x from B union select C.x from C
I see the output.
Same goes for table A also.
But when I run it together I see no data.
One way is to use not exists
Select A.* from A where
not exists (select 1 from b where b.x = A.x)
and
not exists (select 1 from c where c.x = A.x)
;with tabletemporary
AS
(
SELECT b.x AS name from b UNION SELECT c.x AS name FROM c
)
Select a.* FROM a WHERE a.x NOT IN
(SELECT name FROM tabletemporary)
you can use this code

Where one or another column exists in a sub select

I'm looking to do something like this:
SELECT a, b, c, d FROM someTable WHERE
WHERE a in (SELECT testA FROM otherTable);
Only I want to be able to test if 2 columns exist in a sub select of 2 columns.
SELECT a, b, c, d FROM someTable WHERE
WHERE a OR b in (SELECT testA, testB FROM otherTable);
We are using MS SQL Server 2012
Try this
SELECT a, b, c, d
FROM someTable WHERE
WHERE a IN (SELECT testA FROM otherTable)
OR b IN (SELECT testB FROM otherTable)
or
SELECT a, b, c, d
FROM someTable WHERE
WHERE EXISTS
(SELECT NULL
FROM otherTable
WHERE testA = a OR testB = a
OR testA = b OR testB = b)
UPDATE:
Maybe you need to add index on testB column, if you have bad performance.
Also another option to use CROSS APPLY for MS SQL
SELECT a, b, c, d
FROM someTable ST
CROSS APPLY (
SELECT 1
FROM otherTable OT
WHERE OT.testA = ST.a OR OT.testB = ST.b
)
If any of this won't work, try using UNION. Mostly UNION gives better performance than OR
SELECT a, b, c, d
FROM someTable WHERE
WHERE a IN (SELECT testA FROM otherTable)
UNION
SELECT a, b, c, d
FROM someTable WHERE
WHERE b IN (SELECT testB FROM otherTable)
UPDATE 2:
For further reading on OR and UNION differences
Why is UNION faster than an OR statement
Try this..
SELECT a, b, c, d
FROM someTable
WHERE Exists
(
SELECT 1
FROM otherTable
Where a = testA OR b = testB
)
If I'm understanding your question correctly, LEFT JOIN is probably the way to go here:
SELECT a, b, c, d
FROM TableA ta
LEFT JOIN TableB tb
ON ta.a = tb.a
AND ta.b = tb.b
WHERE tb.a IS NOT NULL
AND tb.c IS NOT NULL
You could also use UNION and INNER JOIN:
SELECT a, b, c, d
FROM someTable
INNER JOIN OtherTable OT on someTable.B = OT.testB
UNION
SELECT a, b, c, d
FROM someTable
INNER JOIN OtherTable OT ON someTable.A= OT.testA
Note that the JOIN approach should be orders of magnitude faster if you have an index on the column
Joins seems to be one option, have you thought about using them with a Union?
SELECT a, b, c, d
FROM someTable
INNER JOIN OtherTable OT on someTable.B = OT.testB
UNION
SELECT a, b, c, d
FROM someTable
INNER JOIN OtherTable OT ON someTable.A= OT.testA

How can I get Oracle to have a better execution plan for this query?

We have some tables which keep track of processed transactions. These tables have millions of rows. Often times I want to look at the most recent X transactions, so I have a query like this to pull the info I want from a few tables:
select a.id, b.field_one, c.field_two
from trans a, table_two b, table_three c
where a.id = b.id
and a.id = c.id
and a.id in
(select id from trans where id > (select max(id) from trans) - 100);
Right now the query is very slow. The explain plan shows a full table scan on B and C. Now, if I evaluate the nested query separately and replace it with a list of comma separated IDs, the query is very fast. This seems obvious to me - it will only have 100 rows to join together so of course it will be faster than if it answered the query by first joining A and B together.
Conceptually I understand the query optimizer is trying to find a good execution plan but in this case it seems like it is doing a terrible job. Is there any way to force the DBMS to execute the nested query first? Possibly by using a Hint?
Thanks
Your method is probably obsuring the fact that only a maximum of 100 rows are being selected from trans.
Try this:
with cte_last_trans as (
select id
from (select id
from trans
where id > (select max(id)-100 from trans)
order by id desc)
where rownum <= 100)
select a.id,
b.field_one,
c.field_two
from cte_last_trans a,
table_two b,
table_three c
where a.id = b.id
and a.id = c.id
By the way, have you thought of the possibility that not all values of id might be present? If you want 100 rows to be returned, use:
with cte_last_trans as (
select id
from (select id
from trans
order by id desc)
where rownum <= 100)
select a.id,
b.field_one,
c.field_two
from cte_last_trans a,
table_two b,
table_three c
where a.id = b.id
and a.id = c.id
You can use a NO_MERGE hint which will force Oracle to execute the inner query first and not try to merge the two queries. Here's an example:
SELECT /*+NO_MERGE(seattle_dept)*/ e1.last_name, seattle_dept.department_name
FROM employees e1,
(SELECT location_id, department_id, department_name
FROM departments
WHERE location_id = 1700) seattle_dept
WHERE e1.department_id = seattle_dept.department_id;
select /*+ no_merge(inner) */ a.id, b.field_one, c.field_two
from trans a,
table_two b,
table_three c,
(select id from trans where id > (select max(id) from trans) - 100) inner
where a.id = b.id
and a.id = c.id
and a.id = inner.id;
The in does not seem to do anything have you tried removing it?
select a.id, b.field_one, c.field_two
from trans a, table_two b, table_three c
where a.id = b.id
and a.id = c.id
and a.id > (select max(id) from trans) - 100;
You could simply filter the 100 record in the primary trans table itself instead of joing it again and again.
select a.id, b.field_one, c.field_two
from
(select id from (select id, row_number() over(order by id desc) rn
from trans) where rn <=100) a,
table_two b, table_three c
where a.id = b.id
and a.id = c.id;
select a.id, b.field_one, c.field_two
from trans a, table_two b, table_three c
where a.id = b.id
and a.id = c.id
and a.id between (select max(id)-100 from trans) and (select max(id) from trans)

Simulate a left join without using "left join"

I need to simulate the left join effect without using the "left join" key.
I have two tables, A and B, both with id and name columns. I would like to select all the dbids on both tables, where the name in A equals the name in B.
I use this to make a synchronization, so at the beginning B is empty (so I will have couples with id from A with a value and id from B is null). Later I will have a mix of couples with value - value and value - null.
Normally it would be:
SELECT A.id, B.id
FROM A left join B
ON A.name = B.name
The problem is that I can't use the left join and wanted to know if/how it is possible to do the same thing.
you can use this approach, but you must be sure that the inner select only returns one row.
SELECT A.id,
(select B.id from B where A.name = B.name) as B_ID
FROM A
Just reverse the tables and use a right join instead.
SELECT A.id,
B.id
FROM B
RIGHT JOIN A
ON A.name = B.name
I'm not familiar with java/jpa. Using pure SQL, here's one approach:
SELECT A.id AS A_id, B.id AS B_id
FROM A INNER JOIN B
ON A.name = B.name
UNION
SELECT id AS A_id, NULL AS B_id
FROM A
WHERE name NOT IN ( SELECT name FROM B );
In SQL Server, for example, You can use the *= operator to make a left join:
select A.id, B.id
from A, B
where A.name *= B.name
Other databases might have a slightly different syntax, if such an operator exists at all.
This is the old syntax, used before the join keyword was introduced. You should of course use the join keyword instead if possible. The old syntax might not even work in newer versions of the database.
I can only think of two ways that haven't been given so far. My last three ideas have already been given (boohoo) but I put them here for posterity. I DID think of them without cheating. :-p
Calculate whether B has a match, then provide an extra UNIONed row for the B set to supply the NULL when there is no match.
SELECT A.Id, A.Something, B.Id, B.Whatever, B.SomethingElse
FROM
(
SELECT
A.*,
CASE
WHEN EXISTS (SELECT * FROM B WHERE A.Id = B.Id) THEN 1
ELSE 0
END Which
FROM A
) A
INNER JOIN (
SELECT 1 Which, B.* FROM B
UNION ALL SELECT 0, B* FROM B WHERE 1 = 0
) B ON A.Which = B.Which
AND (
A.Which = 0
OR (
A.Which = 1
AND A.Id = b.Id
)
)
A slightly different take on that same query:
SELECT A.Id, B.Id
FROM
(
SELECT
A.*,
CASE
WHEN EXISTS (SELECT * FROM B WHERE A.Id = B.Id) THEN A.Id
ELSE -1 // a value that does not exist in B
END PseudoId
FROM A
) A
INNER JOIN (
SELECT B.Id PseudoId, B.Id FROM B
UNION ALL SELECT -1, NULL
) B ON A.Which = B.Which
AND A.PseudoId = B.PseudoId
Only for SQL Server specifically. I know, it's really a left join, but it doesn't SAY LEFT in there!
SELECT A.Id, B.Id
FROM
A
OUTER APPLY (
SELECT *
FROM B
WHERE A.Id = B.Id
) B
Get the inner join then UNION the outer join:
SELECT A.Id, B.Id
FROM
A
INNER JOIN B ON A.name = B.name
UNION ALL
SELECT A.Id, NULL
FROM A
WHERE NOT EXISTS (
SELECT *
FROM B
WHERE A.Id = B.Id
)
Use RIGHT JOIN. That's not a LEFT JOIN!
SELECT A.Id, B.Id
FROM
B
RIGHT JOIN A ON B.name = A.name
Just select the B value in a subquery expression (let's hope there's only one B per A). Multiple columns from B can be their own expressions (YUCKO!):
SELECT A.Id, (SELECT TOP 1 B.Id FROM B WHERE A.Id = B.Id) Bid
FROM A
Anyone using Oracle may need some FROM DUAL clauses in any SELECTs that have no FROM.
You could use subqueries, something like:
select a.id
, nvl((select b.id from b where b.name = a.name), "") as bId
from a
you can use oracle + operator for left join :-
SELECT A.id, B.id
FROM A , B
ON A.name = B.name (+)
Find link :-
Oracle "(+)" Operator
SELECT A.id, B.id
FROM A full outer join B
ON A.name = B.name
where A.name is not null
I'm not sure if you just can't use a LEFT JOIN or if you're restricted from using any JOINS at all. But as far as I understand your requirements, an INNER JOIN should work:
SELECT A.id, B.id
FROM A
INNER JOIN B ON A.name = B.name
Simulating left join using pure simple sql:
SELECT A.name
FROM A
where (select count(B.name) from B where A.id = B.id)<1;
In left join there are no lines in B referring A so 0 names in B will refer to the lines in A that dont have a match
+ or A.id = B.id in where clause to simulate the inner join

SQL multiple shared WHERE criteria

I'm trying to find a good, efficient way to run a query like this:
SELECT *
FROM tableA a
WHERE a.manager IN ( SELECT id
FROM tableB b
CONNECT BY PRIOR b.id = b.manager_id
START WITH b.id = 'managerBob')
OR a.teamLead IN ( SELECT ID
FROM tableB b
CONNECT BY PRIOR b.ID = b.manager_id
START WITH b.ID = 'managerBob')
OR a.creator IN ( SELECT id
FROM tableB b
CONNECT BY PRIOR b.id = b.manager_id
START WITH b.id = 'managerBob')
As you can see, I'm trying to use multiple WHERE clauses, but each clause is using the same dataset on the right-hand side of the equation. It seems to run very slowly if I use more than one clause, and I'm pretty sure that it's because Oracle is running each subquery. Is there a way to make something like this work?
SELECT *
FROM tableA a
WHERE a.manager,
a.teamLead,
a.creator in ( SELECT id
FROM tableB b
CONNECT BY PRIOR b.id = b.manager_id
START WITH b.id = 'managerBob')
By the way, I'm sorry if this is something I could have Googled, I'm not sure what to call this.
Subquery factoring may help:
WITH people AS
( SELECT id
FROM tableB b
CONNECT BY PRIOR b.id = b.manager_id
START WITH b.id = 'managerBob'
)
SELECT *
FROM tableA a
WHERE a.manager IN (SELECT id FROM people)
OR a.teamLead IN (SELECT id FROM people)
OR a.creator IN (SELECT id FROM people)
You can do:
WITH bob_subordinates AS (
( SELECT id
FROM tableB b
CONNECT BY PRIOR b.id = b.manager_id
START WITH b.id = 'managerBob')
SELECT * FROM tableA a
WHERE a.manager in (select id from bob_subordinates)
OR a.teamlead in (select id from bob_subordinates)
or a.creator in (select id from bob_subordinates)
Alternative (check the use of DISTINCT: if ids are not unique in table B then this is not equivalent):
WITH bob_subordinates AS (
( SELECT DISTINCT id
FROM tableB b
CONNECT BY PRIOR b.id = b.manager_id
START WITH b.id = 'managerBob')
SELECT DISTINCT a.*
FROM tableA a JOIN bob_subordinates b ON b.id IN (a.manager, a.teamlead, a.creator);
UPDATE as per comments - try
SELECT A.* FROM
(SELECT bb.id FROM tableB bb CONNECT BY PRIOR bb.id = bb.manager_id START WITH bb.id = 'managerBob') B INNER JOIN TABLEA A ON B.ID IN (A.MANAGER, A.TEAMLEAD, A.CREATOR)