SQL read multiple tables and self join - sql

Been trying to learn SQL and am stuck on a problem I want to understand:
Given the following tables:
TABLE - Customer.movie_id
ID MOVIE_ID
-------------
x Spiderman
y Batman
z Avengers
TABLE - Customer.game_id
ID GAME_ID
-------------
A COD
C HALO
D BATTLEFEILD
B MINECRAFT
TABLE - Customer.type_id
ID TYPE_ID
-------------
ii AGE
jj GENDER
kk INCOME
TABLE - Customer.Info
ID MOVIE_ID GAME_ID TYPE_ID DATA
--------------------------------------------
1 x A ii 20
2 x A jj F
3 x C kk 1000
4 y C ii 40
5 y D jj M
6 y C kk 5000
7 z B ii 60
8 z B jj F
9 z C kk 10000
Produce an output that will show rows of AGE only if MOVIE_ID and GAME_ID match the same values on the GENDER type rows.
TABLE - Customer.Info
ID MOVIE_ID GAME_ID TYPE_ID DATA
--------------------------------------------
1 x A ii 20
7 z B ii 60
I have been able to do queries individually in python and process it there, but I don't have any idea of how to combine all of this into one query.
Can anyone help?

This is probably the shortest. If any other rows exists that has the same ids and gender then return it:
select i1.*
from Customer.Info i1 inner join Customer.Info i2
on i2.MOVIE_ID = i1.MOVIE_ID and i2.GAME_ID = i3.GAME_ID
and i1.TYPE_ID = 'ii' and i2.TYPE_ID = 'jj'
where and exists (
select 1 from Customer.Info i3
where i3.ID <> i2.ID
and i3.MOVIE_ID = i2.MOVIE_ID and i3.GAME_ID = i2.GAME_ID
and i3.TYPE_ID = 'jj' and i3.DATA = i2.DATA
);
I don't know if you intend for groups of more than two to all match together without variation.

This data model is not normalized properly and everything is named poorly, but aside from that, you probably want some form of EXISTS clause, such as:
select
i.*
from
customer.info i
join
customer.type_id ti on ti.id = i.type_id
where
ti.type_id = 'AGE'
and
exists(
select 1
from customer.info i2
join customer.type_id ti2 on ti2.id = i2.type_id
where i2.movie_id = i.movie_id
and i2.game_id = i.game_id
and ti2.type_id = 'GENDER'
)
/
Or, if EXISTS is not available, you can do it joining two subqueries:
select
age_info.*
from
(
select i.*
from customer.info i
join customer.type_id ti on ti.id = i.type_id
where ti.type_id = 'AGE'
) age_info
join
(
select distinct i.movie_id, i.game_id
from customer.info i
join customer.type_id ti on ti.id = i.type_id
where ti.type_id = 'GENDER'
) gender_info
on gender_info.movie_id = age_info.movie_id
and gender_info.game_id = age_info.game_id
/

Related

Find parent id when all children share the same value

I have some data that looks a little like this
Table C
id | end_time
-------------
1 '2019-01-01'
2 '2020-01-01'
3 '2019-07-01'
Table F
id | parent_id
12 | 1
13 | 1
21 | 2
22 | 2
31 | 3
32 | 3
33 | 3
34 | 3
Table oui
rel_id | Product Version
1 '2'
12 '2'
13 '1'
2 '1'
21 '2'
22 '1'
3 '2'
31 '1'
32 '1'
33 '1'
34 '1'
Data relationship:
c.id = f.parent_id
c.id or f.id = oui.rel_id
What I'm trying to find is where the rel_id for C in table oui is the parent's product version is 2, but ALL children are version 1.
I found a similar question over here: Find ID of parent where all children exactly match but couldn't quite adapt it to this use case.
Expected result:
c.id
----
3
Reasoning: Both c.id 1/2 have children which have at least 1 item in product version 2.
Try this below logic-
DEMO HERE
SELECT ID FROM C
WHERE ID NOT IN
(
SELECT C.ID
FROM C
INNER JOIN F ON C.id = F.parent_id
INNER JOIN oui ON F.ID = Oui.rel_id
WHERE C.ID = CAST(oui.Product_Version AS INT)
-- by default your column "Product Version" should be INT in table oui
)
The issue you mentioned in the below comment, you can try this opposite conversion as below-
SELECT ID FROM C
WHERE ID NOT IN
(
SELECT C.ID
FROM C
INNER JOIN F ON C.id = F.parent_id
INNER JOIN oui ON F.ID = Oui.rel_id
WHERE CAST(C.ID AS VARCHAR) = oui.Product_Version
)
You want to get C entries with product version = 2 for which exists F entries with product version = 1 and not exist F entries with product version <> 1.
I don't know why there is a separate table OUI at all. One would expect the product version to be a mere column in the tables C and F instead.
So, let's use two with clauses to get to better tables :-)
with better_c as (select c.*, oui.product_version from c join oui on oui.rel_id = c.id)
, better_f as (select f.*, oui.product_version from f join oui on oui.rel_id = f.id)
The real query can then be written with INTERSECT and EXECPT:
with ...
select id from better_c where product_version = 2
intersect
select parent_id from better_f where product_version = 1
except
select parent_id from better_f where product_version <> 1;
The same with [NOT] EXISTS:
with ...
select id
from better_c
where product_version = 2
and exists
(select null from better_f where product_version = 1 and parent_id = better_c.id)
and not exists
(select parent_id from better_f where product_version <> 1 and parent_id = better_c.id);
The same with [NOT] IN:
with ...
select id
from better_c
where product_version = 2
and id in (select parent_id from better_f where product_version = 1)
and id not in (select parent_id from better_f where product_version <> 1);
Try This:
select
t1.id
from c "t1"
inner join oui "t2" on t2.rel_id=t1.id
where t2.product_version='2' -- product_version for Parent
and
(select
count(*)
from f "t3"
inner join oui t4 on t4.rel_id=t3.id
where t4.product_version !='1' -- product_version for Child
and t3.parent_id=t1.id
)=0
Note: Above query will work perfectly if the ID in table C and ID in table F is not same and rel_id column in table oui is having unique values.

How to Join with select top

I have a problem with the combination of multiple tables.
My SQL query:
SELECT *
FROM CRM.Bank as a
JOIN CRM.Documents as b ON a.Bank_ID = b.Documents_ID
JOIN CRM.Counterparties as c ON c.Counterparties_ID = b.Documents_ID
JOIN CRM.Items as d ON d.Document_tran_ID = b.Documents_ID
I have table CRM.Items, which has the following columns:
Item_ID Document_tran_ID Name
======= ================ ====
1 1 Advertising banner
2 1 Shipping costs
3 2 Garden tent
4 2 Additional fasteners
5 2 Shipping costs
And now I have a problem how to connect only the first items to the document (d.Document_tran_ID = b.Documents_ID ) ??
I know I should use SELECT TOP. However, I have a problem with creating the correct query
Expect the result in the form of:
Bank_ID Documents_ID Counterparties_ID Document_tran_ID Name
======= ============ ================= ================ ====
22 1 4 1 Advertising banner
23 2 20 2 Garden tent
24 3 21 3 Other
Only the first Item from the document is matched.
I think you can try to use CROSS APPLY join. In the inner query, you can apply order condition for selecting TOP row
SELECT *
FROM CRM.Bank as a
JOIN CRM.Documents as b ON a.Bank_ID = b.Documents_ID
JOIN CRM.Counterparties as c ON c.Counterparties_ID = b.Documents_ID
CROSS APPLY
(select top 1 * from CRM.Items i where i.Document_tran_ID = b.Documents_ID) as d
use sub-query for documents table
SELECT *
FROM CRM.Bank as a
JOIN ( select min(Document_tran_ID) as Documents_ID from CRM.Documents) as b ON a.Bank_ID = b.Documents_ID
JOIN CRM.Counterparties as c ON c.Counterparties_ID = b.Documents_ID
JOIN CRM.Items as d ON d.Document_tran_ID = b.Documents_ID

Query to join two tables using two different columns from the first table

I have two tables .
Table A:
Table A ID Table Name owner1ID owner2ID
1 Work1 85 91
2 Work2 86 92
3 Work3 87 93
4 Work4 88 94
5 Work5 89 95
6 Work6 90 96
Table B:
OwnerID 0WNERFIRSTNAME 0WNERlASTNAME
85 A M
86 B N
87 C O
88 D P
90 E Q
91 F R
89 G S
92 H T
86 I U
94 J V
93 K W
95 L X
Can you please help me out in getting a query where i need the table which contains TABLEID OWNERFIRSTNAME and OWNERSECONDNAME.
Expected output:
TableAID 0WNER1FIRSTNAME 0WNER1LASTNAME 0WNER2FIRSTNAME 0WNER2LASTNAME
1 A M F R
You need to join on to TableB twice.
That means you need to give each instance of the table an alias, so you can differentiate which instance you're referring to...
SELECT
TableA.TableAID,
TableB1.0WNERFIRSTNAME AS 0WNER1FIRSTNAME,
TableB1.0WNERlASTNAME AS 0WNER1LASTNAME,
TableB2.0WNERFIRSTNAME AS 0WNER2FIRSTNAME,
TableB2.0WNERlASTNAME AS 0WNER2LASTNAME
FROM
TableA
INNER JOIN
TableB TableB1
ON TableB1.OwnerID = TableA.owner1ID
INNER JOIN
TableB TableB2
ON TableB2.OwnerID = TableA.owner2ID
P.S. Don't Spell 0WNERFIRSTNAME with a ZERO, Spell it OWNERFIRSTNAME!
While MatBaile's answer is the most common practice, your own example shows some problems. First is that we lose info about table 6 for which second owner is not found in second table. This can be easily corrected with left join:
select a.id, a.table_name,
b1.OwnerFirstName O1FN, b1.OwnerLastName O1LN,
b2.OwnerFirstName O2FN, b2.OwnerLastName O2LN
from a
left join b b1 on b1.OwnerId = a.Owner1Id
left join b b2 on b2.OwnerId = a.Owner2Id
What gives us:
ID TABLE_NAME O1FN O1LN O2FN O2LN
---------- ---------- ---- ---- ---- ----
1 Work1 A M F R
2 Work2 I U H T <-- two first owners
2 Work2 B N H T <-- two first owners
4 Work4 D P J V
3 Work3 C O K W
5 Work5 G S L X
6 Work6 E Q <-- null second owner
And second problem - for table 2 we got two entries, because in your example there are two owners with id = 86. I suspect that this is typo, but this can happen in similiar cases. You can leave it as is, or take only last row (if owner changed and you have info about this in some date column), or you can list all owners using listagg(), or take max value. Things are worse when there are more rows connected to 1. and 2. owner, your output is multiplied.
As a curiosity here is unpivot-pivot solution. In this case this query looks more complicated, but if there were 10 columns you had to do 10 joins and in this query only lists of columns requires change.
select *
from (
select id, table_name, type, ownerfirstname, ownerlastname
from (select * from a unpivot (ownerId for type in (owner1ID as 1, owner2ID as 2))) a
join b using (ownerId))
pivot (listagg(ownerfirstname||' '||ownerlastname, ', ') within group (order by null) owner
for type in (1, 2))
SQL Fiddle demo
ID TABLE_NAME 1_OWNER 2_OWNER
---------- ---------- ---------- ----------
1 Work1 A M F R
2 Work2 B N, I U H T <-- listagg() used to aggregate data
3 Work3 C O K W
4 Work4 D P J V
5 Work5 G S L X
6 Work6 E Q

How to select all products satisfying n numbers of attributeid/attributevalue conditions

I have a Products table and ProductAttributeValues table
Product
-------
ID 1
Name A
ID 2
Name B
ProductAttributeValues
-------
ID 1
ProductID 1
AttributeID 1
Values Z
ID 2
ProductID 1
AttributeID 2 1
Values Y
ID 3
ProductID 1
AttributeID 3 1
Values P
I need to select all products where,
((ProductAttributeValues.attrbuteid = X and ProductAttributeValues.Value = X)
AND (ProductAttributeValues.attrbuteid = X and ProductAttributeValues.Value = X)
AND ............................................................................
AND ............................................................................)
These conditions are dynamic. So it might be 1 or 2 or 3 or so on.
So how to select all products satisfying n numbers of aattributeid/attribute value conditions?
Method 1
Use AttributeValues table multiple times in the join.
SELECT P.*
FROM
Product P
JOIN ProductAttributeValues PA1 ON P.ID = PA1.ProductID AND PA1.AttributeID = X AND PA1.VALUES = X
JOIN ProductAttributeValues PA2 ON P.ID = PA2.ProductID AND PA2.AttributeID = Y AND PA2.VALUES = Y
JOIN ProductAttributeValues PA3 ON P.ID = PA3.ProductID AND PA3.AttributeID = Z AND PA3.VALUES = Z
..........And So on
Method 2
Pivot ProductAttributeValues table with AttributeID and AttributeValues as multiple columns. Now you can easily join your product and ProductAttributeValues to get the data you need.
SELECT *
FROM Product P
JOIN ProductAttributeValuesPivot PAP
ON P.ID = PAP.ProductID
WHERE PAP.AttributeX = X AND PAP.ValueX = X
AND PAP.AttributeY = Y AND PAP.ValueY = Y
PAP.AttributeZ = Z AND PAP.ValueZ = Z
............... And So on
Links for Pivot
Dynamic Pivot Table in SQL Server
Technet Article for PIVOT and UNPIVOT
http://sqlmag.com/t-sql/create-pivoted-tables-3-steps
This is a "set-within-sets" query. I think the most general approach is aggregation with a having caluse:
select productID
from ProductAttributeValues pav
group by productID
having sum(case when ProductAttributeValues.attrbuteid = X and ProductAttributeValues.Value = X
then 1 else 0 end) > 0 and
sum(case when ProductAttributeValues.attrbuteid = Y and ProductAttributeValues.Value = Y
then 1 else 0 end) > 0 and
. . .
Each condition in the having clause counts the number of attributes for each product that match the condition. The overall clause is true when all the conditions have at least one row.

cross joining two tables

I have a table that looks like this, lets call this table B.
id boardid schoolid subject cnt1 cnt2 cnt3 ....
=================================================================
1 20 21 f
2 20 21 r
3 20 21 w
4 20 21 m
5 20 30 r
6 20 30 w
7 20 30 m
Suppose the counts are just integers. Notice that there is no subject = f for schoolid = 30. Similarly, for most schools, some subject dosnt exist. You might have a schoolid that has just r, w or some that are just r, m, f..
So what I want to do is have 4 consistent rows for each school, and the row that dosnt exist I want dummy values. I thought about creating a secondary table
drop table #A
Select * into #A FROM
(
select [subject_s] = 'r', orderNo = 1
union all
select [subject_s] = 'w', orderNo = 2
union all
select [subject_s] = 'm', orderNo = 3
union all
select [subject_s] = 'f', orderNo = 4
) z
and doing some joins on them, but I've gotten NO where. I've tried inner join, left outer, cross join, everything. I've even tried to make cartesian product. I think my cartesian product messes up because I have orderno in there so it makes 16 rows per row in the main table. Actually typing this out, I realize if I remove the orderno, apply the cartesian product and then add orderno in later, it might work but I am interested to see what you guys can come up with. I am stumped.
End result
id boardid schoolid subject cnt1 cnt2 cnt3 ....
=================================================================
1 20 21 r
2 20 21 w
3 20 21 m
4 20 21 f
5 20 30 r
6 20 30 w
7 20 30 m
7 20 30 f
Try the following:
SELECT S.boardid, S.schoolid, A.[subject], B.cnt1, B.cnt2, B.cnt3
FROM (SELECT DISTINCT boardid, schoolid FROM YourTable) S
CROSS JOIN #A A
LEFT JOIN YourTable B
ON B.boardid = S.boardid AND B.schoolid = S.schoolid
AND A.[subject] = B.[subject]
Since I do not know which RDBMS you are using I tried the following with sqlite and a simpler table:
sqlite> create table schools (name varchar, subject varchar, teacher varchar);
sqlite> select * from schools;
School1|Maths|Mr Smith
School2|English|Jack
School3|English|Jimmy
School3|Maths|Jane
School4|Computer Science|Bob
sqlite> select
schoolnames.name,
subjects.subject,
ifnull(teachers.teacher, "Unknown")
from (select distinct name from schools) schoolnames
join (select distinct subject from schools) subjects
left join schools teachers
on schoolnames.name = teachers.name
and subjects.subject = teachers.subject;
School1|Maths|Mr Smith
School1|English|Unknown
School1|Computer Science|Unknown
School2|Maths|Unknown
School2|English|Jack
School2|Computer Science|Unknown
School3|Maths|Jane
School3|English|Jimmy
School3|Computer Science|Unknown
School4|Maths|Unknown
School4|English|Unknown
School4|Computer Science|Bob
I'd use:
SELECT
boardid, schoolid, dist_subject, id, cnt1, ...
FROM
(SELECT
boardid, schoolid, dist_subject
FROM
(SELECT
DISTINCT subject AS dist_subject
FROM b ) s full outer join
(SELECT
boardid, schoolid
FROM b
GROUP BY
boardid, schoolid ) g ) sg LEFT OUTER JOIN
b ON
sg.boardID = b.boardID AND
sg.schoolid = b.schoolID
sg.dist_subject = b.subject