Complex sql server query - sql

I need some help with a SQL query I'm working on. Here is a simplified version of the data I'm working with. I have 3 tables:
Contacts:
- ContactID
- ContactName
Submissions:
- SubmissionID
- ContactID
- SubmissionTypeID
SubmissionTypes:
- SubmissionTypeID
- SubmissionType
I need to return all of the Contacts (joined to Submissions on ContactID) where there are SubmissionTypeIDs that match up with a list of SubmissionTypeIDs. The tricky part is that I only want results where a Contact has a Submission record with a SubmissionTypeID that matches each of the values in the list. So, for instance, if I had this data:
Contacts
----------------
1 | Jim Johnson
2 | Sally Anderson
SubmissionTypes
----------------------
1 | Contact Form
2 | Request Form
3 | Generic Form
Submissions
----------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
If my SubmissionTypeID values are 1 and 2, I'd want to get the following results:
Jim Johnson | Contact Form
Jim Johnson | Request Form
I wouldn't want to see Sally Anderson because she doesn't have a record in Submissions for both values.
I'm guessing there are a few ways to do this. I'm excited to see your ideas.
Thank you!

Here's a convoluted way using double negation.
declare #list table (SubmissionTypeID int not null primary key);
insert into #list values (1), (2); -- values to search for.
with c as (
select
c.ContactID,
c.ContactName
from
Contacts c
where
not exists (
select
'x'
from
#list l
where
not exists (
select
'x'
from
Submissions s
where
s.ContactID = c.ContactID and
s.SubmissionTypeID = l.SubmissionTypeID
)
)
)
select
c.ContactName,
t.SubmissionType
from
c
inner join
Submissions s
on c.ContactId = s.ContactId
inner join
SubmissionTypes t
on s.SubmissionTypeID = t.SubmissionTypeID
inner join
#list l
on t.SubmissionTypeID = l.SubmissionTypeID;
Example SQLFiddle

One way is with INTERSECT:
select c.contactname, t.submissiontype
from contacts c
join submissions s
on c.contactid = s.contactid
join submissiontypes t
on s.submissiontypeid = t.submissiontypeid
join (select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid = 1
intersect
select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid = 2) v
on c.contactid = v.contactid
where s.submissiontypeid in (1, 2)
Fiddle: http://sqlfiddle.com/#!6/9ee4e/2/0
You can also COUNT where equal to 2 (you have 2 values you're checking for):
select c.contactname, t.submissiontype
from contacts c
join submissions s
on c.contactid = s.contactid
join submissiontypes t
on s.submissiontypeid = t.submissiontypeid
join (select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid in (1, 2)
group by c.contactid
having count(distinct s.submissiontypeid) = 2) v
on c.contactid = v.contactid
where s.submissiontypeid in (1, 2)
Fiddle: http://sqlfiddle.com/#!6/9ee4e/1/0

Try this.. It works fine to me
DECLARE #list TABLE (SubmissionTypeID int not null primary key);
INSERT INTO #list VALUES(1); -- values to search for.
SELECT C.ContactName, ST.SubmissionTypeName
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY S.ContactID ORDER BY S.ContactID) AS RCount
FROM
Submission S
WHERE EXISTS
(SELECT 1 FROM #list l WHERE l.SubmissionTypeID = S.SubmissionTypeID)) AS Result
INNER JOIN Submission S1 ON S1.ContactID = Result.ContactID
INNER JOIN Contacts C ON C.ContactID = Result.ContactID
INNER JOIN SubmissionTypes ST ON ST.SubmissionTypeID = S1.SubmissionTypeID
WHERE RCOunt = (SELECT COUNT(DISTINCT SubmissionTypeID) FROM #list)
AND EXISTS
(SELECT 1 FROM #list l WHERE l.SubmissionTypeID = ST.SubmissionTypeID)
;

Related

How to write sql query to find wealth of person?

I am having trouble writing a query that returns the names of all people that have less than half of the wealth of the richest person. We define the wealth of a person as the total money on all of his/her accounts.
The 3 tables are:
Persons
id | name | address | age | eyeColor | gender
BankAccounts
id | balance
AccountOf
id | person_id → Persons | account_id → BankAccounts
Can anyone help me please
SELECT Persons.name
FROM Persons P1
LEFT JOIN AccountOf A1 ON A1.person_id = P1.id
LEFT JOIN BankAccounts B ON B.id = A1.account_id
GROUP BY name
HAVING SUM(B.balance) < MAX((SELECT SUM(B.balance) as b
FROM AccountOf A1
LEFT JOIN BankAccounts B ON B.id = A1.account_id
GROUP BY A1.person_id ORDER BY b DESC LIMIT 1)
) * 0.5;
You haven't provided any sample data or DBFiddle so this is naturally untested, however you could use a CTE* to construct a query thus:
with p as (
select p.name, Sum(b.balance) balance
from persons p
join accountOf a on a.person_id = p.Id
join BankAccounts b on b.Id = a.account_id
group by p.name
)
select *
from p
where p.balance < (select Max(balance) from p) * 0.5;
(* Requires MySql 8+)

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.

Sql query to find users with same skills

I have three tables
UserInfo (U-id,U-name)
Skill(S-id,S-Name)
and a bridge table between them (cause they have many to many relationship)
UserSkill(U-id,S-id)
I want to write a query to find users with same skills
for example this is a sample data in UserSkill table
U-id S-id
1 1
1 2
1 7
2 1
2 6
so the result would be like this
UserName1 UserName2 SkillName
A B Java
ad this is my query
{select ui.UserName,ui2.UserName,SkillName
from
UserSkill us1 inner join UserSkill us2
on us1.SkillID = us2.SkillID and us1.UserID <> us2.UserID
inner join UsersINFO UI
on ui.UserID = us1.UserID
inner join UsersINFO ui2
on ui2.UserID = us2.UserID
inner join Skill s
on s.SkillID = us2.SkillID}
I want to know whether anyone knows a better way to write the query
You just need to add Where ui.UserName<ui2.UserName at the end
select ui.UserName,ui2.UserName,SkillName
from
UserSkill us1 inner join UserSkill us2
on us1.SkillID = us2.SkillID and us1.UserID <> us2.UserID
inner join UsersINFO UI
on ui.UserID = us1.UserID
inner join UsersINFO ui2
on ui2.UserID = us2.UserID
inner join Skill s
on s.SkillID = us2.SkillID
Where ui.UserName<ui2.UserName
Output
UserName UserName SkillName
A B Java
Live Demo
http://sqlfiddle.com/#!18/64540/1
The result from your query is just messy. It reports A, B, java and B, A, java.
declare #J table (uid int, sid int, primary key (uid, sid));
insert into #J values
(1, 1)
, (1, 2)
, (1, 7)
, (2, 1)
, (2, 6)
, (3, 1)
, (3, 2);
declare #N table (id int primary key, name varchar(10));
insert into #N values
(1, 'bob')
, (2, 'ted')
, (3, 'mac');
select j1.sid, n1.name, n2.name
from #J j1
join #J j2
on j2.sid = j1.sid
and j2.uid <> j1.uid
join #N n1
on n1.id = j1.uid
join #N n2
on n2.id = j2.uid
order by j1.sid, j1.uid, j2.uid;
sid name name
----------- ---------- ----------
1 bob ted
1 bob mac
1 ted bob
1 ted mac
1 mac bob
1 mac ted
2 bob mac
2 mac bob
Even if you replace <> with > it is still messy when you have more than 2 with the same skill.
select j1.sid, n1.name, n2.name
from #J j1
join #J j2
on j2.sid = j1.sid
and j2.uid > j1.uid
join #N n1
on n1.id = j1.uid
join #N n2
on n2.id = j2.uid
order by j1.sid, j1.uid, j2.uid;
sid name name
----------- ---------- ----------
1 bob ted
1 bob mac
1 ted mac
2 bob mac
I suggest you NOT report as pairs
select *
from ( select j1.sid, n1.name
, count(*) over (partition by j1.sid) as cnt
from #J j1
join #N n1
on n1.id = j1.uid
) t
where t.cnt > 1
order by t.sid, t.name;
sid name cnt
----------- ---------- -----------
1 bob 3
1 mac 3
1 ted 3
2 bob 2
2 mac 2
I suggest not to have in pairs like above below is a sol :
Here in below soln.. I am joining data and just grouping users by skill id and skillname.
SELECT
o.skillname,
LISTAGG(o.username, ',') WITHIN GROUP(
ORDER BY
o.username ASC
) user_common_skill
FROM
(
SELECT
t.username,
t.userid,
t.skillid,
s.skillname
FROM
(
SELECT
ui.username,
ui.userid,
us.skillid
FROM
userskill us
INNER JOIN usersinfo ui ON us.userid = ui.userid
) t
LEFT JOIN skill s ON t.skillid = s.skillid
) o
GROUP BY
o.skillid,
o.skillname
Results of above query
The following returns all pairs of users that have a skill in common, along with that skill:
select ui.name, us2.name, s.s_name
from userskill us join
userskill us2
on us.sid = us2.sid join
skills s
on s.sid = us.sid join
userinfo ui
on ui.uid = us.uid join
userinfo ui2
on ui2.uid = us2.uid
where us.uid <> us2.uid;

Using 'AND' in a many-to-many relationship

I have a Users table and a Groups table. Users can be in multiple groups via a 'UserInGroup' table and Groups can have a 'GroupTypeId'.
[User]
--------------
Id | Name
1 | Bob
2 | James
[UserInGroup]
-----------------
UserId | GroupId
1 1
1 2
[Group]
Id | Name | TypeId
------------------------
1 | Directors | 1
2 | IT | 1
3 | London | 2
I want to create a query to return for example users that are in both 'Directors' AND 'London' (rather than 'Directors' OR 'London'). However, I only want to AND groups of a different 'Type', I want to OR groups of the same type. I could do with having a separate table per group type but I can't as they are created dynamically.
Ideally I want to be able to query users who are in 'Directors' OR 'IT' AND 'London'.
What is the most efficient way of doing this?
This problem is commonly known as Relational Division.
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(*) = 2
SQLFiddle Demo
SQL of Relational Division
But if a UNIQUE constraint was not enforce on GROUP for every USER, DISTINCT keywords is needed to filter out unique groups:
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(DISTINCT c.Name) = 2
OUTPUT from both queries
╔══════╗
║ NAME ║
╠══════╣
║ Bob ║
╚══════╝
I arrived at the following solution (with help from J W and this article):
SELECT
u.Name UserName
FROM [User] u
INNER JOIN [UserInGroup] uig
ON uig.UserId = u.Id
INNER JOIN [Group] g
ON g.Id = uig.GroupId
WHERE
g.Id IN (1,2,3) -- these are the passed in groupids
GROUP BY
u.Name
having count(distinct g.TypeId)
= (select count(distinct g1.TypeId)
from [group] g1 where g1.Id IN (1,2,3))
This allows me to group the relational division by a discriminator field. An alternative would be this:
SELECT a.Name
FROM [User] a
INNER JOIN
(
SELECT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name IN ('Directors','IT')
GROUP BY b.UserID
HAVING COUNT(DISTINCT c.Name) >= 1
) b ON a.ID = b.UserID
INNER JOIN
(
SELECT DISTINCT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name = 'London'
) c ON a.ID = c.UserID
With an extra join for each GroupTypeId. Execution plans look similar, so I went with the first option.

SQL - Group By Distinct Values

My question, is there a faster way to the following query?
I'm using ORACLE 10g
Say i have a table Manufacturer and Car, and i want to count all occurrences of the column 'Car.Name'. here is How i'd do it:
SELECT manuf.Name, COUNT(car1.Name), COUNT(car2.Name), COUNT(car3.Name)
FROM Manufacturer manuf
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari1') car1 ON manuf.PK = car1.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari2') car2 ON manuf.PK = car2.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari3') car3 ON manuf.PK = car3.ManufPK
GROUP BY manuf.Name
Wanted Results:
Manufacturer | Ferrari1 | Ferrari2 | Ferrari3
----------------------------------------------
Fiat | 1 | 0 | 5
Ford | 2 | 3 | 0
I tried this with few LEFT JOINs, and it worked fine. But when i added a lot (like 90+), it was ultra slow (more than 1 minute).
My question, is there a faster way to do this query?
If you are happy to see the cars counted down the page, try:
select m.Name manufacturer_name,
c.Name car_name,
count(*)
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name, c.Name
If you need to see individual cars across the page, try:
select m.Name manufacturer_name,
sum(case c.Name when 'Ferrari1' then 1 else 0 end) Ferrari1_Count,
sum(case c.Name when 'Ferrari2' then 1 else 0 end) Ferrari2_Count,
sum(case c.Name when 'Ferrari3' then 1 else 0 end) Ferrari3_Count
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name
SELECT manuf.Name, COUNT(DISTINCT c.Name)
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name
OR depending on your needs
SELECT manuf.Name, c.Name, COUNT(*) Cnt
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name, c.Name
PS: Your question is not very clear. Provide some wanted resultset to refine the answer
You can also try this:
SELECT manuf.Name
, car1.cnt AS Ferrari1
, car2.cnt AS Ferrari2
, car3.cnt AS Ferrari3
FROM
Manufacturer AS manuf
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari1'
GROUP BY ManufPK
) AS car1
ON car1.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari2'
GROUP BY ManufPK
) AS car2
ON car2.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari3'
GROUP BY ManufPK
) AS car3
ON car3.ManufPK = manuf.PK
ORDER BY manuf.Name