sql query to collect users having common items - sql

I'm facing a problem with Postgres. Here is the example:
i got 3 tables: users, items and boxes
boxes table:
user_id | item_id
1 | 3
1 | 4
1 | 6
1 | 7
2 | 5
2 | 10
2 | 11
3 | 5
3 | 6
3 | 7
Given this boxes table, i would like to retrieve items among users who share minimum 2. So the SQL query result expected should be
item_id: 6, 7
because user 1 and user 3 share items 6 and 7.
But user 2 and 3 share only one item: the item 5 so item 5 is not in result.
I'm trying so many ways without success. I wonder if someone can help me.

Try this. It returns 6 and 7 (and 5,6,7 if you add a record "1,5"), but I haven't tested it extensively.
-- The Outer query gets all the item_ids matching the user_ids returned from the subquery
SELECT DISTINCT c.item_id FROM boxes c -- need DISTINCT because we get 1,3 and 3,1...
INNER JOIN boxes d ON c.item_id = d.item_id
INNER JOIN
--- the subquery gets all the combinations of user ids which have more than one shared item_id
(SELECT a.user_id as first_user,b.user_id as second_user FROM
boxes a
INNER JOIN boxes b ON a.item_id = b.item_id AND a.user_id <> b.user_id -- don't count items where the user_id is the same! Could just make the having clause be > 2 but this way is clearer
GROUP BY a.user_id,b.user_id
HAVING count(*) > 1) s
ON s.first_user = c.user_id AND s.second_user = d.user_id

Related

Join three tables and retrieve the expected result

I have 3 tables. User Accounts, IncomingSentences and AnnotatedSentences. Annotators annotate the incoming sentences and tag an intent to it. Then, admin reviews those taggings and makes the corrections on the tagged intent.
DB-Fiddle Playground link: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=00a770173fa0568cce2c482643de1d79
Assuming myself as the admin, I want to pull the error report per annotator.
My tables are as follows:
User Accounts table:
userId
userEmail
userRole
1
user1#gmail.com
editor
2
user2#gmail.com
editor
3
user3#gmail.com
editor
4
user4#gmail.com
admin
5
user5#gmail.com
admin
Incoming Sentences Table
sentenceId
sentence
createdAt
1
sentence1
2021-01-01
2
sentence2
2021-01-01
3
sentence3
2021-01-02
4
sentence4
2021-01-02
5
sentence5
2021-01-03
6
sentence6
2021-01-03
7
sentence7
2021-02-01
8
sentence8
2021-02-01
9
sentence9
2021-02-02
10
sentence10
2021-02-02
11
sentence11
2021-02-03
12
sentence12
2021-02-03
Annotated Sentences Table
id
annotatorId
sentenceId
annotatedIntent
1
1
1
intent1
2
4
1
intent2
3
2
2
intent4
4
3
4
intent4
5
1
5
intent2
6
3
3
intent3
7
5
3
intent2
8
1
6
intent4
9
4
6
intent1
10
1
7
intent1
11
4
7
intent3
12
3
9
intent3
13
2
10
intent3
14
5
10
intent1
Expected Output:
I want an output as a table which provides the info about total-sentences-annotated-per-each editor and the total-sentences-corrected-by-admin on top of editor annotated sentences. I don't want to view the admin-tagged-count in the same table. If it comes also, total-admin-corrected should return 0.
|userEmail |totalTagged|totalAdminCorrected|
|---------------|------------|---------------------|
|user1#gmail.com| 4 | 3 |
|user2#gmail.com| 2 | 1 |
|user3#gmail.com| 3 | 1 |
Query I wrote: I've tried my best. You can see that in the DB-Fiddle
My query is not resulting in the expected output. Requesting your help to achieve this.
My proposal...
SELECT UserEmail, SUM(EDICount), SUM(ADMCount)
FROM (SELECT UserAccounts.UserEmail, AnnotatedSentences.SentenceID, COUNT(*) AS EDICount
FROM AnnotatedSentences
LEFT JOIN UserAccounts ON UserAccounts.UserID=AnnotatedSentences.AnnotatorID
WHERE UserRole='editor'
GROUP BY UserAccounts.UserEmail, AnnotatedSentences.SentenceID) AS EDI
LEFT JOIN (SELECT AnnotatedSentences.SentenceID, COUNT(*) AS ADMCount
FROM AnnotatedSentences
LEFT JOIN UserAccounts ON UserAccounts.UserID=AnnotatedSentences.AnnotatorID
WHERE UserRole='admin'
GROUP BY AnnotatedSentences.SentenceID) AS ADM ON EDI.SentenceID=ADM.SentenceID
GROUP BY UserEmail
Because sentence_id might be reviewed by different users (role), you can try to use subquery (INNER JOIN between user_accounts & annotated_sentences) with window function + condition aggregate function, getting count by your logic.
if you don't want to see admin count information you can use where filter rows.
SELECT user_email,
count(Total_Tagged) Total_Tagged,
SUM(totalAdmin) totalAdmin
FROM (
SELECT ist.sentence_id,
user_email,
user_role,
count(CASE WHEN a.user_role = 'editor' THEN 1 END) over(partition by ist.sentence_id) + count(CASE WHEN a.user_role = 'admin' THEN 1 END) over(partition by ist.sentence_id) Total_Tagged,
count(CASE WHEN a.user_role = 'admin' THEN 1 END) over(partition by ist.sentence_id) totalAdmin
FROM user_accounts a
INNER JOIN annotated_sentences ats ON
a.user_id = ats.annotator_id
INNER JOIN incoming_sentences ist
ON ist.sentence_id = ats.sentence_id
) t1
WHERE user_role = 'editor'
GROUP BY user_email
ORDER BY user_email
sqlfiddle
Okay, i really rushed this so there might still be an error in the Code, but try something like this:
SELECT
a.user_email,
count(ist) Total_Tagged,
sum(innerTable.edits)
FROM
incoming_sentences ist
JOIN annotated_sentences ats ON
ist.sentence_id = ats.sentence_id
JOIN user_accounts a ON
a.user_id = ats.annotator_id
LEFT JOIN ( SELECT ics.sentence_id, count(anno.id) AS edits FROM annotated_sentences anno
LEFT JOIN user_accounts ua ON
ua.user_id = anno.annotator_id
LEFT JOIN incoming_sentences AS ics ON
ics.sentence_id = anno.sentence_id
WHERE user_role LIKE 'admin'
GROUP BY ics.sentence_id ) AS innerTable
ON innerTable.sentence_id = ist.sentence_id
GROUP BY a.user_email
The inner select should count how many admin-edits there are per post, the outer one then sums up that number for every post a user edited.
If it is guaranteed that one sentence can only be annotated once and only be reviewed once, then you can simply group by sentence and get the editor and admin. Then you group by editor and count.
select
editor,
count(*) as total_tagged,
count(admin) as total_admin_corrected
from
(
select
max(ua.user_email) filter (where ua.user_role = 'editor') as editor,
max(ua.user_email) filter (where ua.user_role = 'admin') as admin
from annotated_sentences ans
join user_accounts ua on ua.user_id = ans.annotator_id
group by ans.sentence_id
) with_editor_and_admin
group by editor
order by editor;
Demo: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=e409ec49af25ac8329a99b02161832fb

Left Join Display All Data From Table1 and Table2

I am trying to do a left join so that I get all of my rows from Table 1 even if there is no value corresponding to it in the second table.
My structures are:
Location Table:
ID LocName
1 Trk1
2 Trk2
3 Trk3
4 Unk
Quantity Table:
ID PartID Quantity LocationID
1 1 2 1
2 3 12 2
3 2 6 1
4 6 8 3
5 6 5 1
I am trying to join but also make a query on a specific PartID. My query is:
SELECT
INV_LOCATIONS.ID AS LocationID,
INV_LOCATIONS.NAME AS LocationName,
INV_QUANTITY.QUANTITY AS Quantity
FROM INV_LOCATIONS
LEFT JOIN INV_QUANTITY ON INV_LOCATIONS.ID = INV_QUANTITY.LOCATION_ID
WHERE INV_QUANTITY.PART_ID = 1;
My output right now would be:
ID LocName Quantity
1 Trk1 5
3 Trk3 8
The Desired output is:
ID LocName Quantity
1 Trk1 5
2 Trk2 NULL/0
3 Trk3 8
4 Unk NULL/0
I assume it is because I have the WHERE INV_QUANTITY.PART_ID = 1 and that is forcing it to be in the quantity table. I need to be able to verify it is on the right part but how do I also include it if it doesn't exist. I know I have done something very similar before but I cannot remember which project and so I cannot find the code anywhere.
You need to move the filtering logic to the ON clause:
SELECT il.ID AS LocationID, il.NAME AS LocationName,
iq.QUANTITY AS Quantity
FROM INV_LOCATIONS il LEFT JOIN
INV_QUANTITY iq
ON il.ID = iq.LOCATION_ID AND iq.PART_ID = 1;

Get max value from a joined list paired with another column in DB2

I have the following tables:
Table I:
etu | nr |
1 2
2 2
2 3
2 1
3 4
3 9
Table A:
etu | rsp | nr
2 8 2
2 7 3
2 3 1
3 2 4
3 6 9
Now what I want to have as a result table is
etu | nr | rsp
2.. 3 7
3.. 9 6
So etu and nr are linked together and if multiple equal etu entries are available only the one with the highest nr is taken and the rsp value is added in the result table. in addition if more etu entries are available in the table I there are .. added to the etu value.
Explain: For the 3 9 6 row: The last row on table I is 3 9 so 3 is the number that is looked for and 9 is the highest number for the 3 rows. So we take that and add the rsp value for that ( 6 ) and we add that to the result table. For the 2 row it is the same 2 3 being the highest 2 row in table I.
I got something like:
select x.etu, x.rsp, y.nr from(
select i.etu etu, max(i.nr) maxnr, a.rsp from i left join a on
i.etu=a.etu and i.nr=a.nr group by etu)t
inner join a x on x.etu=t.etu and x.nr=t.nr inner join y on y.etu=t.etu
and y.nr=t.nr
or
select i.etu, max(i.nr) a.rsp from i left join a on i.etu=a.etu and
i.nr=a.nr grounp by
None even get me close to get the results that I want less add the .. after the etu when having the right result.
The system is DB10.5 Windows.
Thank you for all your help in advance.
Viking
I would use a CTE here like this:
with tmp as (
select i.etu, max(i.nr) as nt, count(*) as cnt
from i
group by i.etu)
select case
when tmp.cnt = 1 then char(a.etu)
else concat(rtrim(char(a.etu)), '..')
end as etu,
a.nr,
a.rsp
from tmp
left outer join a
on a.etu = tmp.etu
and a.nr = tmp.nr
The CTE provides the information necessary to join with a to get the correct response, and append the .. as necessary.

Efficient SQL to calculate # of shared affiliations

So I have
a table that stores asymmetrical connections between two persons
(like a Twitter follow; not like a Facebook friend) and
a table that stores a person's affiliations to various groups
My task is to find, for each asymmetrical relationship, the number of affiliations shared between the "from person" and the "to person".
I made this brute force solution, but I'm wondering if brighter minds could come up with something more efficient.
select frm01.from_person_id, frm01.to_person_id, count(*) num_affl
from
(
select lnk.from_person_id, lnk.to_person_id, ga.grp_id from_grp_id
from links lnk
left outer join grp_affl ga on lnk.from_person_id = ga.person_id
group by lnk.from_person_id, lnk.to_person_id, grp_id
) frm01
inner join
(
select lnk.from_person_id, lnk.to_person_id, ga.grp_id to_grp_id
from links lnk
left outer join grp_affl ga on lnk.to_person_id = ga.person_id
group by lnk.from_person_id, lnk.to_person_id, grp_id
) to01
on (
frm01.from_person_id = to01.from_person_id
and frm01.to_person_id = to01.to_person_id
and frm01.from_grp_id = to01.to_grp_id
)
group by frm01.from_person_id, frm01.to_person_id;
Using ANSI SQL on Netezza (which doesn't allow correlated subqueries).
TIA!
Edited to add table schema:
table lnk:
from_person_id to_person_id
1 4
2 5
3 6
4 2
5 3
table grp_affl:
person_id grp_id
1 A
1 B
1 C
2 A
3 B
4 C
5 A
5 B
5 C
6 A
expected output:
from_person_id to_person_id num_affl
1 4 1
2 5 1
3 6 0
4 2 0
5 3 1
Persons 1 & 4 have 1 affiliation in common (C), 2 & 5 have A in common, 5 & 3 have B in common. 3 & 6 have nothing in common. Likewise 4 & 2.
You can do this with aggregation and the right joins:
select pairs.from_person, pairs.to_person, count(*)
from links pairs join
grp_affil fromga
on fromga.person_id = pairs.from_person join
grp_affil toga
on toga.person_id = pairs.to_person and
toga.grp_id = fromga.grp_id
group by pairs.from_person, pairs.to_person;
The joins bring in the groups. The last condition only brings in matching groups between the two persons. The final group by counts them.

using or in mysql and merging columns

I can select using or with the sql statement
select f.userid, f.friend_userid from friends f where userid = 1 or friend_userid = 1;
now either userid or friend_userid is returning 1.
i want the two columns i.e userid and friend_userid to get merged
into a single column without 1
such that only one row is displayed...
the output i m getting is...
userid | friend_userid
1 | 2
1 | 7
1 | 5
12|1
24 | 1
I want to get displayed like...
userid
2
7
5
12
24
I m using mysql....
Thanks
Pradyut
India
Looks like you want a join, probably a LEFT JOIN, between two instances of table friends. If the fields other than userid and friend_userid are, say, a and b (you don't tell us and it's impossible to guess:
SELECT f.a, f.b, f1.a, f1.b
FROM friends f
LEFT JOIN friends f` ON (f.userid = f1.friend_userid)
WHERE f.userid = 1
So, you need the column friend_userid where condition "UserId=1" is met and UserId column when "friend_userid=1" condition is met. Write the following query:
select f.friend_userid as userId from friends f where f.userid = 1
UNION
select f.userid as userId from friends f where f.friend_userid = 1;
And you get what you wanted.
with the sql
select f.userid, f.friend_userid from friends f where userid = 1 or friend_userid = 1;
the output i m getting is...
userid | friend_userid
1 | 2
1 | 7
1 | 5
12|1
24 | 1
I want to get displayed like...
userid
2
7
5
12
24
thanks
Pradyut