Select from multiple table, eliminating duplicates values - sql

I have these tables and values:
Person Account
------------------ -----------------------
ID | CREATED_BY ID | TYPE | DATA
------------------ -----------------------
1 | 1 | T1 | USEFUL DATA
2 | 2 | T2 |
3 | 3 | T3 |
4 | 4 | T2 |
Person_account_link
--------------------------
ID | PERSON_ID | ACCOUNT_ID
--------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 3
4 | 3 | 4
I want to select all persons with T1 account type and get the data column, for the others persons they should be in the result without any account information.
(I note that person 1 has two accounts : account_id_1 and account_id_2 but only one row must be displayed (priority for T1 type if exist otherwise null)
The result should be :
Table1
-----------------------------------------------------
PERSON_ID | ACCOUNT_ID | ACCOUNT_TYPE | ACCOUNT_DATA
-----------------------------------------------------
1 | 1 | T1 | USEFUL DATA
2 | NULL | NULL | NULL
3 | NULL | NULL | NULL
4 | NULL | NULL | NULL

You can do conditional aggregation :
SELECT p.id,
MAX(CASE WHEN a.type = 'T1' THEN a.id END) AS ACCOUNT_ID,
MAX(CASE WHEN a.type = 'T1' THEN 'T1' END) AS ACCOUNT_TYPE,
MAX(CASE WHEN a.type = 'T1' THEN a.data END) AS ACCOUNT_DATA
FROM person p LEFT JOIN
Person_account_link pl
ON p.id = pl.person_id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;

You would need an outer join, starting with Person and then to the other two tables. I would also aggregate with group by and min to tackle the situation where a person would have two or more T1 accounts. In that case one of the data is taken (the min of them):
select p.id person_id,
min(a.id) account_id,
min(a.type) account_type,
min(a.data) account_data
from Person p
left join Person_account_link pa on p.id = pa.person_id
left join Account a on pa.account_id = a.id and a.type = 'T1'
group by p.id

In Postgres, I like to use the FILTER keyword. In addition, the Person table is not needed if you only want persons with an account. If you want all persons:
SELECT p.id,
MAX(a.id) FILTER (a.type = 'T1') as account_id,
MAX(a.type) FILTER (a.type = 'T1') as account_type,
MAX(a.data) FILTER (a.type = 'T1') as account_data
FROM Person p LEFT JOIN
Person_account_link pl
ON pl.person_id = p.id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;

Related

Get related tables not contains result

I have a DesignGroup table as:
+--------------------------------------+----------+
| DesignGroupId | Name |
+--------------------------------------+----------+
| 3A81C1FF-442F-4291-B8E2-7079D80920CF | Design 1 |
| 3238F4C6-7BA7-4B3F-9383-17702B0D1CC3 | Design 2 |
+--------------------------------------+----------+
Each DesignGroup can have multiple customers, so I have a table DesignGroupCustomers as:
+--------------------------------------+--------------------------------------+-------------+
| DesignGroupCustomerId | DesignGroupId (FK) | CustomerKey |
+--------------------------------------+--------------------------------------+-------------+
| D0828677-F295-46F7-BB85-65888D5A48B7 | 3A81C1FF-442F-4291-B8E2-7079D80920CF | 10 |
| 10C01BB9-1DDB-4DB4-BEC4-9539E030BF68 | 3A81C1FF-442F-4291-B8E2-7079D80920CF | 20 |
| F88C9F66-C0D9-EB11-8481-5CF9DDF6DC87 | 3238F4C6-7BA7-4B3F-9383-17702B0D1CC3 | 10 |
+--------------------------------------+--------------------------------------+-------------+
Each customer have a CustomerType as, customerTable:
+-------------+-------------+
| CustomerKey | CustTypeKey |
+-------------+-------------+
| 10 | 2 |
| 20 | 1 |
+-------------+-------------+
That I want to achieve is to get only this statement:
return only the DesignGroup who not have a customer with custTypeKey = 1
In this case it should return Design 2 because it does not have customer with custTypeKey = 1
I was thinking about CTE usage but I just have not idea how to get the desire result:
;WITH CTE
AS (SELECT
[DG].[DesignGroupId]
, ROW_NUMBER() OVER(PARTITION BY [DesignGroupCustomer]) AS [RN]
FROM [DesignGroup] AS [DG]
INNER JOIN [DesignGroupCustomer] AS [DGC] ON [DG].[DesignGroupId] = [DGC].[DesignGroupId]
INNER JOIN [Customer] AS [C] ON [DGC].[CustomerKey] = [C].[CustomerKey]
INNER JOIN [CustomerType] AS [CT] ON [C].[CustTypeKey] = [CT].[CustTypeKey])
SELECT
[DesignGroupId]
FROM [CTE] -- WHERE CustomerType NOT CONTAINS (1)
WITH temp AS (
SELECT DISTINCT
dgc.DesignGroupId AS DesignGroupId
FROM DesignGroupCustomers dgc
INNER JOIN customerTable ct
ON dgc.CustomerKey = ct.CustomerKey
WHERE ct.CustTypeKey = 1
)
SELECT
DesignGroupId
FROM DesignGroup
WHERE DesignGroupId NOT IN (
SELECT
DesignGroupId
FROM temp
)
Firstly, you can get all designgroups having CustTypeKey =1 and then get all other designgroups using NOT IN. Please let me know if you face any issues
You can use a subquery to return the design groups which have this customer type key of 1 and then LEFT JOIN the subquery on the design table and filter down to results that have a DesignGroupId of null (any design group that isn't included in the dataset of the subquery)
SELECT d.[DesignGroupId]
FROM [DesignGroup] AS d
LEFT JOIN
(
SELECT dgc.[DesignGroupId]
FROM [DesignGroupCustomer] AS dgc
ON dgc.[DesignGroupId] = d.[DesignGroupId]
INNER JOIN [Customer] AS c
ON c.[CustomerKey] = dgc.[CustomerKey]
WHERE c.[CustTypeKey] = 1
GROUP BY dgc.[DesignGroupId]
) x
ON x.[DesignGroupId] = d.[DesignGroupId]
WHERE x.[DesignGroupId] IS NULL

How to populate a table based on a value from a different table

I have two tables of data which I can join using a left join linked on the ID in both tables. Where the course and the person are the same, I need to populate the RegNumber as the same as the RegNumber which is already there for 1 row:
How it is currently: if I join table 1 and table 2 with a left join.
Table 1
ID | Course| Person
67705 | A | 1
68521 | A | 1
85742 | A | 1
89625 | A | 1
67857 | B | 2
86694 | B | 2
88075 | B | 2
88710 | C | 3
47924 | C | 3
66981 | C | 3
12311 | B | 1
12312 | B | 1
12313 | B | 1
Table 2
ID | RegNumber
67705 | N712316
NULL | NULL
NULL | NULL
NULL | NULL
67857 | N712338
NULL | NULL
NULL | NULL
NULL | NULL
47924 | M481035
NULL | NULL
12311 | N645525
NULL | NULL
NULL | NULL
I need table 2 to look like this:
ID | RegNumber
67705 | N712316
68521 | N712316
85742 | N712316
89625 | N712316
67857 | N712338
86694 | N712338
88075 | N712338
88710 | N712338
47924 | M481035
66981 | M481035
12311 | N645525
12312 | N645525
12313 | N645525
That is, I need to insert new rows into Table 2
Can anyone help me please? This is Totally beyond my capability!
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from table2 r2 join table1 r1
on r1.Id = r2.Id
and r1.Course = t1.Course
and r1.Person = t1.person
order by id) reg
where not exists (select 1 from table2 t2 where t1.ID = t2.ID)
you can improve performance a little bit by loading data into temp table first :
select t1.ID , Course,Person,regNumber
into #LoadedData
from table1 t1
join table2 t2 on t1.Id = t2.ID
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from #LoadedData l
where l.Course = t1.Course
and l.Person = t1.person
order by id) reg
where not exists (select 1 from #LoadedData l where t1.ID = l.ID)
in either case having an index on (ID, Course, Person) will help with performance
Assuming:
You are missing items in table 2 that inherit data from other records in table 1.
What makes two different IDs share the same Regnumber is to have BOTH course and person number in common.
You really need to join table 1 to itself to create the mapping that associates ID 67705 with ID 68521, then you can join in table 2 to pick up the Regnumber.
Try this:
Insert into table2 (ID,RegNumber)
Select right1.ID, left2.RegNumber
From (
(table2 left2 INNER JOIN
table1 left1 On (left1.ID=left2.ID)
INNER JOIN table1 right1 On (left1.Course=right1.Course AND left1.Person=right1.Person)
) LEFT OUTER JOIN table2 right2 On (right1.ID=right2.ID)
WHERE right2.ID Is Null
The 4th table join (alias right2) is purely defensive, to handle two records in table2 having identical Person & Course in table1.
I have solved this myself.
I concatenated the person and course columns and then joined them using that new concatenated field
insert into table 2 (ID,RegNumber)
select X1.ID,X2.Regnumber
from (select concat(course,person) as X,ID from table1) X1
join (select concat(t1.course,t1.person) as X, t2.RegNumber
from table1 t1
join table2 t2 on t1.ID = t2.ID) X2
on X1.X = X2.X
where X1.ID not in (select ID from table2)

How to find record ids with missing property ids in another table

The PostgreSQL database has two tables: user_properties and properties. The properties table contains a list of all possible properties with ids (a dictionary). The user_properties table contains what properties a user has, referencing property id from the properties table.
The properties table:
----------------------
prop_id | prop_name
----------------------
1 | Email
----------------------
2 | Phone number
----------------------
3 | Something else 1
----------------------
4 | Something else 2
----------------------
The user_properties table:
--------------------------------
user_id | prop_id | prop_value
--------------------------------
100 | 1 | asd#zxc.com
--------------------------------
100 | 2 | 1234567
--------------------------------
100 | 2 | 2345678
--------------------------------
101 | 3 | *******
--------------------------------
101 | 3 | +++++++
--------------------------------
I need to know which properties are missing for every user_id.
The expected result should look like:
-----------------------
user_id | missing_prop_id
-----------------------
100 | 3
-----------------------
100 | 4
-----------------------
101 | 1
-----------------------
101 | 2
-----------------------
101 | 4
-----------------------
You can use except as :
with properties(prop_id,prop_name) as
(
values(1, 'Email'),(2, 'Phone number'),
(3, 'Something else 1'),(4, 'Something else 2')
), user_properties( user_id, prop_id, prop_value) as
(
values(100,1,'asd#zxc.com'),(100,2,'1234567'),(100,2,'2345678'),
(101,3,'*******'),(101,3,'+++++++')
), t2 as
(
select u.user_id, p.prop_id as missing_prop_id
from user_properties u
cross join properties p
group by u.user_id, p.prop_id
except
select u.user_id,
p.prop_id
from user_properties u
right join properties p
on u.prop_id = p.prop_id
group by u.user_id, p.prop_id
)
select * from t2 order by user_id, missing_prop_id;
user_id missing_prop_id
100 3
100 4
101 1
101 2
101 4
Demo
You can simply solve this by join...
SELECT DISTINCT t3.user_id, t3.prop_id FROM
(SELECT DISTINCT user_id, t2.prop_id FROM user_properties t1, properties t2) t3
LEFT JOIN user_properties t4 ON t3.user_id = t4.user_id and t3.prop_id = t4.prop_id WHERE t4.prop_id is null
http://sqlfiddle.com/#!17/0f4e3/2/0
You can use a cross join to generate all the rows and left join to filter out the ones that don't exist:
select u.user_id, p.prop_id
from (select distinct user_id from user_properties
) u cross join
properties p left join
user_properties up
on up.user_id = u.user_id and
up.prop_id = p.prop_id
where up.user_id is null;
Presumably, you have a users table so the subquery for u is not needed:
select u.user_id, p.prop_id
from users u cross join
properties p left join
user_properties up
on up.user_id = u.user_id and
up.prop_id = p.prop_id
where up.user_id is null;
Thanks everyone for the help. I've come up with the following query myself:
select mp.user_id, mp.prop_id missing_prop_id from
(select distinct up.user_id, p.prop_id from user_properties up cross join properties p) mp
except
select distinct user_id, prop_id from user_properties
http://sqlfiddle.com/#!17/0f4e3/3/0

In SQL Query a one-to-many relationship with condition

I have the following tables:
event_tbl
| event_id (PK) | event_date | event_location |
|---------------|------------|----------------|
| 1 | 01/01/2018 | Miami |
| 2 | 02/04/2018 | Tampa |
performer_tbl
| performer_id (PK) | event_id (FK) | genre |
|-------------------|---------------|-------|
| 1 | 1 | A |
| 2 | 1 | B |
| 3 | 2 | A |
| 4 | 2 | C |
I want to find events that have both genre A and genre B (should just return event 1), and I'm lost on writing the query. Maybe I just haven't had enough coffee, but all I can come up with is doing two derived columns with a case statement that count either genre and group by the event_id, then filtering both to >0. It just doesn't seem very elegant.
This should do the job (in MySQL, for other DBMS the syntax can be varied easily):
SELECT
e.event_id
FROM
event_tbl e
JOIN performer_tbl p USING(event_id)
GROUP BY e.event_id
HAVING SUM(IF(p.genre = 'A', 1, 0)) >= 1 AND SUM(IF(p.genre = 'B', 1, 0)) >= 1;
if you are using sql server, check below:
Select * From
event_tbl
where event_id
IN
(
select event_id
from performer_tbl as A
where exists (select 1
from perfoermer_tbl as B
where B.event_id = A.event_id and B.genre = 'A')
and
exists (select 1
from perfoermer_tbl as B
where B.event_id = A.event_id and B.genre = 'B')
)
This should work in any SQL database (at least in mysql, sql server, postgres or oracle)
select event_tbl.* FROM (
select event_id
from performer_tbl
where genre = 'A'
GROUP BY event_id) a_t
INNER JOIN (select event_id
from performer_tbl
where genre = 'B'
GROUP BY event_id) b_t
ON a_t.event_id = b_t.event_id
INNER JOIN event_tbl
ON event_tbl.event_id = a_t.event_id
This also works using left joins: (Since there are no function calls or sub-selects, it is fast. Also, it's usable in most SQL engines.)
SELECT DISTINCT
p1.event_id
,e.event_date
,e.event_location
FROM
performer_tbl as p1
inner join event_tbl as e on
p1.event_id = e.event_id
left outer join performer_tbl as p2 on
p1.event_id = p2.event_id
AND p2.genre = 'A'
left outer join performer_tbl as p3 on
p1.event_id = p3.event_id
AND p3.genre = 'B'
WHERE
p2.genre IS NOT NULL
AND p3.genre IS NOT NULL;
If I correctly understand what you need, you can try this:
Select *
from event_tbl e
where exists (select *
from performer_tbl p
where p.event_id = e.event_id
and p.genre in ('A', 'B'))

Get count of related records in two joined tables

Firstly, I apologize for my English. I want get auctions with count of bids and buys. It should look like this:
id | name | bids | buys
-----------------------
1 | Foo | 4 | 1
2 | Bar | 0 | 0
I have tables like following:
auction:
id | name
---------
1 | Foo
2 | Bar
auction_bid:
id | auction_id
---------------
1 | 1
2 | 1
3 | 1
4 | 1
auction_buy:
id | auction_id
---------------
1 | 1
I can get numbers in two queries:
SELECT *, COUNT(abid.id) AS `bids` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) GROUP BY t.id
SELECT *, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
But when i combined it into one:
SELECT *, COUNT(abid.id) AS `bids`, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
It was returning wrong amount (bids as much as buys).
How to fix this and get counts in one query?
You'll need to count DISTINCT abuy and abid IDs to eliminate the duplicates;
SELECT t.id, t.name,
COUNT(DISTINCT abid.id) `bids`,
COUNT(DISTINCT abuy.id) `buys`
FROM `auction` `t`
LEFT JOIN auction_bid abid ON t.id = abid.auction_id
LEFT JOIN auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id, t.name;
An SQLfiddle to test with.
Try this:
SELECT t.*,COUNT(abid.id) as bids,buys
FROM auction t LEFT JOIN
auction_bid abid ON t.id = abid.auction_id LEFT JOIN
(SELECT t.id, Count(abuy.id) as buys
FROM auction t LEFT JOIN
auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id) Temp ON t.id=Temp.id
GROUP BY t.id
Result:
ID NAME BIDS BUYS
1 Foo 2 0
2 Bar 1 1
Result in SQL Fiddle.