Conditionally fallback to different join condition if stricter condition not matched - sql

I have 2 tables j and c.
Both tables have columns ports and sec, and JOIN ON j.ports = c.ports and c.sec = j.sec.
For j.port = 'ABC', if there is no c.sec = j.sec for the same ports, then JOIN ON LEFT(c.sec, 6) = LEFT(j.sec, 6)
For other j.ports, I only want to join ON j.ports = c.ports and c.sec = j.sec
How can I do that?
Example Data
Table c
+------+------------+------------+
| Port | sec | Other |
+------+------------+------------+
| ABC | abcdefghij | ONE |
| ABC | klmnop | TWO |
| LMN | qwertyuiop | THREE |
| XYZ | asdfghjkl | FOUR |
+------+------------+------------+
Table j
+------+------------+
| Port | sec |
+------+------------+
| ABC | abcdefxxxx |
| ABC | klmnop |
| LMN | qwertyuiop |
| XYZ | zxcvbnm |
+------+------------+
EDITED: Desired Results
+------+------------+------------+
| Port | sec | other |
+------+------------+------------+
| ABC | abcdefghij | ONE | --> mactching on sec's 1st 6 characters
| ABC | klmnop | TWO | --> mactching on sec
| LMN | qwertyuiop | THREE | --> mactching on sec
+------+------------+------------+

This does conditional joining:
select t1.*, t2.*
from j t1 inner join c t2
on t2.ports = t1.ports and
case
when exists (select 1 from c where sec = t1.sec) then t1.sec
else left(t1.sec, 6)
end =
case
when exists (select 1 from c where sec = t1.sec) then t2.sec
else left(t2.sec, 6)
end
I question its efficiency but I think it does what you need.
See the demo.

You can do two outer joins and then do isnull type of operation. In oracle nvl is isnull of sqlserver
with c as
(
select 'ABC' port, 'abcdefghij' sec from dual
union all select 'ABC', 'klmnop' from dual
union all select 'LMN', 'qwertyuiop' from dual
union all select 'XYZ', 'asdfghjkl' from dual
),
j as
(
select 'ABC' port, 'abcdefxxxx' sec from dual
union all select 'ABC', 'klmnop' from dual
union all select 'LMN', 'qwertyuiop' from dual
union all select 'XYZ', 'zxcvbnm' from dual
)
select c.port, c.sec, nvl(j_full.sec, j_part.sec) j_sec
from c
left outer join j j_full on j_full.port = c.port and j_full.sec = c.sec
left outer join j j_part on j_part.port = c.port and substr(j_part.sec,1,6) = substr(c.sec,1,6)
order by 1,2

One way would be to just inner join on the less strict predicate then use a ranking function to discard unwanted rows in the event that c.port = 'ABC' and the stricter condition got a match for a particular c.port, c.sec combination.
with cte as
(
select c.port as cPort,
c.sec as cSec,
c.other as other,
j.sec as jSec,
RANK() OVER (PARTITION BY c.port, c.sec ORDER BY CASE WHEN c.port = 'ABC' AND j.sec = c.sec THEN 0 ELSE 1 END) AS rnk
from c inner join j on left(j.sec,6) = left(c.sec,6)
)
SELECT cPort, cSec, other, jSec
FROM cte
WHERE rnk = 1

Related

Join with union returns me wrong result

I need to use union all twice. And then to join them with the same table. First union will contain a where statement and the second it will not.
Problem is that when I use the second table my result is changing.
select sum(x.quantity*x.Price)
from CustomerTrans t1
inner join (
select *
from InventoryTrans
union all
select *
from InventoryTransTemp
) x on t1.TrnDocumentID = x.TrnDocumentID
group by t1.TrnDocumentID
Here is the output from this result
First Result
Then I am adding the second union with the where statement inside
select sum(x.quantity*x.Price), sum(x2.quantity*x2.Price)
from CustomerTrans t1
left join (
select *
from InventoryTrans
union all
select *
from InventoryTransTemp
) x on t1.TrnDocumentID = x.TrnDocumentID
left join (
select *
from InventoryTrans
where printed = 2 or InvoicePaymentID = 2
union all
select *
from InventoryTransTemp
where printed = 2 or InvoicePaymentID = 2
) x2 on t1.TrnDocumentID = x2.TrnDocumentID
group by t1.TrnDocumentID
Here is the second result
Second Result
Second result it should be 3.80 and not 7.60
It look like it multiples my price *2 instead *1.
What happens here is that you join rows you don't want to join. Let's say your first subquery returns
+----------+-------+
| quantity | price |
+----------+-------+
| 10 | 100 |
| 10 | 200 |
+----------+-------+
for a particular document ID. And your second subquery returns only
+----------+-------+
| quantity | price |
+----------+-------+
| 10 | 200 |
+----------+-------+
The joined result is:
+------------+---------+-------------+----------+
| x.quantity | x.price | x2.quantity | x2.price |
+------------+---------+-------------+----------+
| 10 | 100 | 10 | 200 |
| 10 | 200 | 10 | 200 |
+------------+---------+-------------+----------+
And the aggregations results thereafter are:
+----------+-----------+
| x_result | x2_result |
+----------+-----------+
| 3000 | 4000 |
+----------+-----------+
instead of
+----------+-----------+
| x_result | x2_result |
+----------+-----------+
| 3000 | 2000 |
+----------+-----------+
Instead of joining single rows, you want to join aggregation results (the totals per document):
select
ct.*,
coalesce(u1.total, 0) as u1_total,
coalesce(u2.total, 0) as u2_total
from customertrans ct
left join
(
select trndocumentid, sum(quantity * price) as total
from
(
select * from inventorytrans
union all
select * from inventorytranstemp
) union1
group by trndocumentid
) u1 on u1.trndocumentid = ct.trndocumentid
left join
(
select trndocumentid, sum(quantity * price) as total
from
(
select * from inventorytrans where printed = 2 or invoicepaymentid = 2
union all
select * from inventorytranstemp where printed = 2 or invoicepaymentid = 2
) union2
group by trndocumentid
) u2 on u2.trndocumentid = ct.trndocumentid
group by ct.trndocumentid
order by ct.trndocumentid;

How to create a query with all of dependencies in hierarchical organization?

I've been trying hard to create a query to see all dependencies in a hierarchical organization. But the only I have accuaried is to retrieve the parent dependency. I have attached an image to show what I need.
Thanks for any clue you can give me.
This is the code I have tried with the production table.
WITH CTE AS
(SELECT
H1.systemuserid,
H1.pes_aprobadorid,
H1.yomifullname,
H1.internalemailaddress
FROM [dbo].[ext_systemuser] H1
WHERE H1.pes_aprobadorid is null
UNION ALL
SELECT
H2.systemuserid,
H2.pes_aprobadorid,
H2.yomifullname,
H2.internalemailaddress
FROM [dbo].[ext_systemuser] H2
INNER JOIN CTE c ON h2.pes_aprobadorid=c.systemuserid)
SELECT *
FROM CTE
OPTION (MAXRECURSION 1000)
You are almost there with your query. You just have to include all rows as a starting point. Also the join should be cte.parent_id = ext.user_id and not the other way round. I've done an example query in postgres, but you shall easily adapt it to your DBMS.
with recursive st_units as (
select 0 as id, NULL as pid, 'Director' as nm
union all select 1, 0, 'Department 1'
union all select 2, 0, 'Department 2'
union all select 3, 1, 'Unit 1'
union all select 4, 3, 'Unit 1.1'
),
cte AS
(
SELECT id, pid, cast(nm as text) as path, 1 as lvl
FROM st_units
UNION ALL
SELECT c.id, u.pid, cast(path || '->' || u.nm as text), lvl + 1
FROM st_units as u
INNER JOIN cte as c on c.pid = u.id
)
SELECT id, pid, path, lvl
FROM cte
ORDER BY lvl, id
id | pid | path | lvl
-: | ---: | :--------------------------------------- | --:
0 | null | Director | 1
1 | 0 | Department 1 | 1
2 | 0 | Department 2 | 1
3 | 1 | Unit 1 | 1
4 | 3 | Unit 1.1 | 1
1 | null | Department 1->Director | 2
2 | null | Department 2->Director | 2
3 | 0 | Unit 1->Department 1 | 2
4 | 1 | Unit 1.1->Unit 1 | 2
3 | null | Unit 1->Department 1->Director | 3
4 | 0 | Unit 1.1->Unit 1->Department 1 | 3
4 | null | Unit 1.1->Unit 1->Department 1->Director | 4
db<>fiddle here
I've reached this code that it is working but when I include a hierarchy table of more than 1800 the query is endless.
With cte AS
(select systemuserid, systemuserid as pes_aprobadorid, internalemailaddress, yomifullname
from #TestTable
union all
SELECT c.systemuserid, u.pes_aprobadorid, u.internalemailaddress, u.yomifullname
FROM #TestTable as u
INNER JOIN cte as c on c.pes_aprobadorid = u.systemuserid
)
select distinct * from cte
where pes_aprobadorid is not null
OPTION (MAXRECURSION 0)

I am having hard time writing a HiveQL Join

I am pretty sure this question have been asked but can't get my search query to return the answer. I have two table
**Table Online**
Col1 Col2 Score |
a b 1 |
a c 2 |
a d 3 |
f e 4 |
**Table Offline**
Col1 Col2 Score |
a m 10 |
a c 20 |
a d 30 |
t k 40 |
**Table Output**
Col1 Col2 Online.Score Offline.Score |
a c 2 20 |
a d 3 30 |
a b 1 |
a m 10 |
You can do this with a full join:
select coalesce(onl.col1, ofl.col1) as col1,
coalesce(onl.col2, ofl.col2) as col2,
onl.score, ofl.score
from (select onl.*
from online onl
where onl.col1 = 'a'
) onl full join
(select ofl.*
from offline ofl
where ofl.col1 = 'a'
) ofl
on onl.col1 = ofl.col1 and onl.col2 = ofl.col2;
Filtering is tricky withe full join, which is why this uses a subquery.
Use below query!
SELECT online.col1
,online.col2
,coalesce(online.score, 0) AS onlinescore
,coalesce(offlilne.score, 0) AS offlinescore
FROM online
INNER JOIN offline
ON online.col1 = offline.col1
AND online.col2 = offline.col2
UNION ALL
SELECT online.col1
,online.col2
,coalesce(online.score, 0) AS onlinescore
,'' AS offlinescore
FROM online
LEFT JOIN offline
ON online.col1 = offline.col1
AND online.col2 = offline.col2
WHERE offline.col1 IS NULL
UNION ALL
SELECT offline.col1
,offline.col2
,'' AS onlinescore
,coalesce(offline.score, 0) AS offlinescore
FROM offline
LEFT JOIN online
ON online.col1 = offline.col1
AND online.col2 = offline.col2
WHERE online.col1 IS NULL

Error in SQL. Different values when using count

This is my table
+-----+-----+-----+-------+-------+---------+
| pa | pl | rp | corp | year | rating |
|-----+-----+-----+-------+-------+---------|
| pa1 | pl1 | rp1 | a1 | 2016 | 6 |
| pa1 | pl1 | rp1 | a1 | 2017 | 7 |
| pa1 | pl1 | rp1 | a2 | 2016 | 6.5 |
| pa1 | pl1 | rp1 | a2 | 2017 | 7.5 |
| pa1 | pl1 | rp2 | a1 | 2016 | 2 |
| pa1 | pl1 | rp2 | a1 | 2017 | 1.5 |
| pa1 | pl1 | rp2 | a2 | 2016 | 4 |
| pa1 | pl1 | rp2 | a2 | 2017 | 4.5 |
+-------------------------------------------+
Its name is list.
The query I wrote is to find where the average values have increased with time.
This is my query.
select count(sq.rp)
from
(select l1.pa, l1.pl, l1.rp, l1.corp, l1.rating as r1, l2.rating as r2
from list l1, list l2
where l1.year = '2016' and l2.year = '2017' and l1.pa = l2.pa
and l1.pl = l2.pl and l1.rp = l2.rp and l1.corp = l2.corp) sq
group by pa, pl, rp
having (avg(sq.r2)-avg(sq.r1)) > 0
When I don't use the count in the first line ( or replacing the first line with this line
select sq.rp
It shows the output result as with one row 'rp1'.
+-----+
| rp |
|-----|
| rp1 |
+-----+
But when I use the 'count' keyword also, it shows the count as 2.
I can't understand the reason.
Consider below example's
with x as
(select 'a' idx, 10 from dual union all
select 'a' idx, 20 from dual)
select idx from x group by idx
In the first you selected idx & group by idx but we havn't provided any aggregate functions but we are still grouping by. As per the document
In each group, no two rows have the same value for the grouping column or columns
so it actually just does distinct of column idx for you.
Try below with group by & count together and you might understand the query:
with x as
(select 'a' idx, 10 from dual union all
select 'a' idx, 20 from dual)
select idx,count(idx) from x group by idx
GROUP BY without an aggregate function is the same as using DISTINCT, therefore just one row. GROUP BY in combination with an aggregate function applies that aggregate function on all rows of the group, therefore 2. Execute the inner select without the group and you will see that it returns two rows because of the join.
Not clear what data as result you need to obtain but to me is more readable join table with AVG already calculated and then just select the column you're interested in.. try the below example:
the table "list" should fit your example replacing columns pa pl and rp with k (key)
select * --sub2.k, sub2.av
from (
select k,y, AVG(v) av
from (
select 'val1' k, 'a1' corp,'2016' y ,6 v
union select 'val1', 'a2','2016' ,4
union select 'val1', 'a1','2017',7
union select 'val1', 'a2','2017',9
union select 'val2', 'a1','2016',1
union select 'val2', 'a1','2017',3
union select 'val2', 'a2','2016',3
union select 'val2', 'a2','2017',5
) list
where list.y = 2016
group by k,y
) sub1
join (
select k,y, AVG(v) av
from (
select 'val1' k, 'a1' corp,'2016' y ,6 v
union select 'val1', 'a2','2016' ,4
union select 'val1', 'a1','2017',7
union select 'val1', 'a2','2017',9
union select 'val2', 'a1','2016',1
union select 'val2', 'a1','2017',3
union select 'val2', 'a2','2016',3
union select 'val2', 'a2','2017',5
) list
where list.y = 2017
group by k,y
) sub2 on sub1.k=sub2.k
and sub2.av-sub1.av >0

I want to make this happen in SQL Server

driverphone| drivername|guarantor1_phone|guarantor2_phone
---------------------------------------------------------
0801 |Mr A |0803 |0802
0802 |Mr B |0804 |0801
0803 |Mr C |0805 |0801
0804 |Mr D |0802 |0805
0805 |Mr E |0801 |0803
I want to get this result set in SQL Server
driverphone| drivername|Total Guaranteed
----------------------------------------
0801 |Mr A | 3
0802 |Mr B | 2
0803 |Mr C | 2
0804 |Mr D | 1
0805 |Mr E | 2
That is to select the total number guaranteed by each driver.
driver->guarantor relationship is based on phone numbers.
Do make LEFT JOIN with both guarantor columns and make a Distinct count of it.
Schema:
CREATE TABLE #TAB (
driverphone VARCHAR(10)
,drivername VARCHAR(10)
,guarantor1_phone VARCHAR(10)
,guarantor2_phone VARCHAR(10)
)
INSERT INTO #TAB
SELECT '0801',' Mr A', '0803', '0802'
UNION ALL
SELECT '0802',' Mr B', '0804', '0801'
UNION ALL
SELECT '0803',' Mr C', '0805', '0801'
UNION ALL
SELECT '0804',' Mr D', '0802', '0805'
UNION ALL
SELECT '0805',' Mr E', '0801', '0803'
Now do select like below
SELECT T.driverphone
,T.drivername
,COUNT(DISTINCT T2.driverphone) + COUNT(DISTINCT T3.driverphone)
FROM #TAB T
LEFT JOIN #TAB T2 ON T.driverphone = T2.guarantor1_phone
LEFT JOIN #TAB T3 ON T.driverphone = T3.guarantor2_phone
GROUP BY T.driverphone
,T.drivername
Result will be
+-------------+------------+------------------+
| driverphone | drivername | (No column name) |
+-------------+------------+------------------+
| 0801 | Mr A | 3 |
| 0802 | Mr B | 2 |
| 0803 | Mr C | 2 |
| 0804 | Mr D | 1 |
| 0805 | Mr E | 2 |
+-------------+------------+------------------+
I think the simplest method is outer apply:
select t.driverphone, t.drivername, g.totalguaranteed
from t outer apply
(select count(*) as totalguaranteed
from (values (guarantor1_phone), (guarantor2_phone)) v(guarantor)
where v.guarantor = t.driverphone
) g;