DISTINCT ON with a One to Many Relation through another table - postgresql

I have four Postgres tables:
Professions
id
name
123
profession 1
345
profession 2
Versions
id
professionId
status
createdAt
567
123
Live
2020-01-01
999
123
Draft
2021-02-01
666
345
Live
2021-01-01
Organisations
id
name
333
organisation 1
444
organisation 2
655
organisation 3
ProfessionToOrganisation
id
professionId
organisationId
Role
665
123
333
Regulator
533
123
444
Something else
534
345
444
Regulator
I am trying to get the latest version of each profession, regardless of their status, so am using a combination of DISTINCT ON and ORDER BY like so:
SELECT DISTINCT ON (
professionVersions.professionId,
professions.name
) professionVersions.id AS "professionVersions_id", professions.name, organisations.name
FROM professionVersions
LEFT JOIN professions ON professions.id = professionVersions.professionId
LEFT JOIN professionToOrganisation ON
professionToOrganisation.professionId = professions.id
LEFT JOIN organisations ON professionToOrganisation.organisationId = organisations.id
ORDER BY
professions.name,
professionVersions.professionId,
professionVersions.created_at DESC
However, I only get one organisation returned for each profession, i.e:
professionVersions_id
name
name
999
Profession 1
Organisation 1
666
Profession 2
Organisation 2
Whereas I wouild like:
professionVersions_id
name
name
999
Profession 1
Organisation 1
999
Profession 1
Organisation 2
666
Profession 2
Organisation 2
How do I go about achieving this?
I've got a DB Fiddle here - https://www.db-fiddle.com/f/44iWbw7oyDtVMsQfE45jPA/0

Related

postgres group by category but require 1 of each value

Edit
i've created a db fiddle to show the current db schema
https://www.db-fiddle.com/f/vwnrjuBHKYYDsXr3kAAvWE/1
I'm struggling with grouping sets unique values in a gift table.
There are 4 gifts per category which I want to count as 1 set. Kind of like a loyalty card where the set is complete when you have all 4 of the gifts.
I want to query how many sets each user has of each category.
So the source table looks like (simplified):
row
user_id
gift_id
gift_category
1
123
1
perfume
2
123
2
perfume
3
123
3
perfume
4
123
4
perfume
5
123
1
perfume
6
123
2
perfume
7
123
4
perfume
8
123
6
drink
desired outcome should be:
user_id
gift_category
set_count
123
perfume
1
because category drink doesn't have all 4 gifts (based on 4 unique gift_id's) this one doesn't count. Also because the user misses 1 perfume (gift_id 3) of the second set this also doesn't count.
Is there a way to to query the desired outcome?
*EDIT
should have at least one of each gift_id 1,2,3,4 for perfume and 5,6,7,8 for drinks to have 1 set.
the gift table looks like this (denormalized version):
gift_id
gift_category
name
1
perfume
chanel
2
perfume
olly
3
perfume
christine
4
perfume
lacoste
5
drink
beer
6
drink
wine
7
drink
vodka
8
drink
rum
so,to have 1 set of each, you have to have 1 gift id in each category
We need to compute a list of all the possible matches between Users and Gifts then outer join on the Sale table (User_Gifts).
Then GROUP BY the gift_category and but only return the groups that do not have any missing Sales
SELECT Users.user_id, gifts.gift_category_id, gift_categories.name as gift_category
FROM Users
CROSS JOIN Gifts
INNER JOIN gift_categories ON Gifts.gift_category_id = gift_categories.gift_category_id
LEFT OUTER JOIN user_gifts ON user_gifts.gift_id = gifts.gift_id and user_gifts.user_id = Users.user_id
GROUP BY Users.user_id, gifts.gift_category_id, gift_categories.name
HAVING COUNT(CASE WHEN user_gifts.gift_Id IS NULL THEN 1 END) = 0
Have a look at this fiddle for proof: https://www.db-https://www.db-fiddle.com/f/vwnrjuBHKYYDsXr3kAAvWE/2
user_id
gift_category_id
gift_category
123
1
perfume
NOTE:
In the Fiddle, there was no Users table, so I used a CTE to construct one out of the unique user_id in the user_gifts table data.
Step 1, Construct the matrix of all options and those that have been taken, we accept duplicates here:
SELECT Users.user_id
, Gifts.*
, gift_categories.name as gift_category
, user_gifts.*
FROM Users
CROSS JOIN Gifts
INNER JOIN gift_categories ON Gifts.gift_category_id = gift_categories.gift_category_id
LEFT OUTER JOIN user_gifts ON user_gifts.gift_id = gifts.gift_id and user_gifts.user_id = Users.user_id
user_id
gift_id
name
gift_category_id
name
user_gift_id
gift_id
user_id
123
1
chanel
1
perfume
1
1
123
123
1
chanel
1
perfume
5
1
123
123
2
olly
1
perfume
6
2
123
123
2
olly
1
perfume
2
2
123
123
3
christine
1
perfume
3
3
123
123
4
lacoste
1
perfume
4
4
123
123
4
lacoste
1
perfume
7
4
123
123
5
beer
2
drinks
null
null
null
123
6
wine
2
drinks
8
6
123
123
7
vodka
2
drinks
null
null
null
123
8
rum
2
drinks
null
null
null
Step 2: Group the results and Count the zeros and non-zeros (we only need the zeros but its helpful to see that it works)
SELECT Users.user_id, gifts.gift_category_id, gift_categories.name as gift_category
, COUNT(CASE WHEN user_gifts.gift_Id IS NULL THEN 1 END) as MissingGifts
, COUNT(CASE WHEN user_gifts.gift_Id IS NOT NULL THEN 1 END) as SoldGifts
FROM Users
CROSS JOIN Gifts
INNER JOIN gift_categories ON Gifts.gift_category_id = gift_categories.gift_category_id
LEFT OUTER JOIN user_gifts ON user_gifts.gift_id = gifts.gift_id and user_gifts.user_id = Users.user_id
GROUP BY Users.user_id, gifts.gift_category_id, gift_categories.name
user_id
gift_category_id
gift_category
missinggifts
soldgifts
123
1
perfume
0
7
123
2
drinks
3
0
Step 3: Use HAVING to ensure we only select the rows that have Zero, 0s, or in this set, zero missinggifts
HAVING COUNT(CASE WHEN user_gifts.gift_Id IS NULL THEN 1 END) = 0

How to hidden value when it doesn't match

I'm in a trouble like this:
For a following table A with data
|ID| |Department| |Name|
1 IT JOHN
3 IT JOHN
5 IT JOHN
1 SALE LINH
3 SALE LINH
2 SALE LINH
3 CLERK MINA
6 CLERK MINA
4 CLERK MINA
There are some ROLEID, each ROLEID has role corresponding, such as 1 is Manager,2 is leader...
I want to select based on ROLEID, if exist ROLEID = 3, all values has department is IT won't display. How can I do that?

PostgreSQL : comparing two sets of results does not work

I have a table that contains 3 columns of ids, clothes, shoes, customers and relates them.
I have a query that works fine :
select clothes, shoes from table where customers = 101 (all clothes and shoes of customer 101). This returns
clothes - shoes (SET A)
1 6
1 2
33 12
24 null
Another query that works fine :
select clothes ,shoes from table
where customers in
(select customers from table where clothes = 1 and customers <> 101 ) (all clothes and shoes of any other customer than 101, with specified clothes). This returns
shoes - clothes(SET B)
6 null
null 24
1 1
2 1
12 null
null 26
14 null
Now I want to get all clothes and shoes from SET A that are not in SET B.
So (example) select from SET A where NOT IN SET B. This should return just clothes 33, right?
I try to convert this to a working query :
select clothes, shoes from table where customers = 101
and
(clothes,shoes) not in
(
select clothes,shoes from
table where customers in
(select customers from table where clothes = 1 and customers <> 101 )
) ;
I tried different syntaxes, but the above looks more logic.
Problem is I never get clothes 33, just an empty set.
How do I fix this? What goes wrong?
Thanks
Edit , here is the contents of the table
id shoes customers clothes
1 1 1 1
2 1 4 1
3 1 5 1
4 2 2 2
5 2 3 1
6 1 3 1
44 2 101 1
46 6 101 1
49 12 101 33
51 13 102
52 101 24
59 107 51
60 107 24
62 23 108 51
63 23 108 2
93 124 25
95 6 125
98 127 25
100 3 128
103 24 131
104 25 132
105 102 28
106 10 102
107 23 133
108 4 26
109 6 4
110 4 24
111 12 4
112 14 4
116 102 48
117 102 24
118 102 25
119 102 26
120 102 29
122 134 31
The except clause in PostgreSQL works the way the minus operator does in Oracle. I think that will give you what you want.
I think notionally your query looks right, but I suspect those pesky nulls are impacting your results. Just like a null is not-NOT equal to 5 (it's nothing, therefore it's neither equal to nor not equal to anything), a null is also not-NOT "in" anything...
select clothes, shoes
from table1
where customers = 101
except
select clothes, shoes
from table1
where customers in (
select customers
from table1
where clothes = 1 and customers != 101
)
For PostgreSQL null is undefined value, so You must get rid of potential nulls in your result:
select id,clothes,shoes from t1 where customers = 101 -- or select id...
and (
clothes not in
(
select COALESCE(clothes,-1) from
t1 where customers in
(select customers from t1 where clothes = 1 and customers <> 101 )
)
OR
shoes not in
(
select COALESCE(shoes,-1) from
t1 where customers in
(select customers from t1 where clothes = 1 and customers <> 101 )
)
)
if You wanted unique pairs you would use:
select clothes, shoes from t1 where customers = 101
and
(clothes,shoes) not in
(
select coalesce(clothes,-1),coalesce(shoes,-1) from
t1 where customers in
(select customers from t1 where clothes = 1 and customers <> 101 )
) ;
You can't get "clothes 33" if You are selecting both clothes and shoes columns...
Also if u need to know exactly which column, clothes or shoes was unique to this customer, You might use this little "hack":
select id,clothes,-1 AS shoes from t1 where customers = 101
and
clothes not in
(
select COALESCE(clothes,-1) from
t1 where customers in
(select customers from t1 where clothes = 1 and customers <> 101)
)
UNION
select id,-1,shoes from t1 where customers = 101
and
shoes not in
(
select COALESCE(shoes,-1) from
t1 where customers in
(select customers from t1 where clothes = 1 and customers <> 101)
)
And Your result would be:
id=49, clothes=33, shoes=-1
(I assume that there aren't any clothes or shoes with id -1, You may put any exotic value here)
Cheers

selecting out duplicate records within the same table column and list them out

I've searched but so far don't find answer fits my situation.
How do you write select statement to selecting out duplicate records within the same table column and list them (so not group by it)??
example: to find duplicates for contract_id column and list them out
ID contract_id Sales1 Sales2
1 12345 100 200
2 54321 300 674
3 12345 343 435
4 09876 125 654
5 54321 374 233
6 22334 543 335
Result should look like this with order by contract_id as well:
ID contract_id Sales1 Sales2
1 12345 100 200
3 12345 343 435
2 54321 300 674
5 54321 374 233
You could use a subquery on the count >1
select * from my_table
where contract_id in (
select contract_id
from my_table
group by contract_id
having count(*) > 1
)

Return all records regardless if there is a match

In my Table 1, It may have AND have a null entry in the address column to corresponding record OR not have a matching entry in Table 2.
I want to present all the records in Table 1 but also present corresponding entries from Table 2. My RESULT is what I am trying to achieve.
Table 1
ID First Last
1 John Smith
2 Bob Long
3 Bill Davis
4 Sam Bird
5 Tom Fenton
6 Mary Willis
Table 2
RefID ID Address
1 1 123 Main
2 2 555 Center
3 3 626 Smith
4 4 412 Walnut
5 1
6 2 555 Center
7 3
8 4 412 Walnut
Result
Id First Last Address
1 John Smith 123 Main
2 Bob Long 555 Center
3 Bill Davis 626 Smith
4 Sam Bird 412 Walnut
5 Tom Fenton
6 Mary Willis
You need an outer join for this:
SELECT * FROM Table1 t1 LEFT OUTER JOIN Table2 t2 ON t1.ID = t2.RefID
How do you join those two tables? If table 2 have more than 1 matched address, how do you want display them? Please clarify in your question.
Here is a query based on my assumptions.
SELECT
ID, First, Last,
Address = (SELECT MAX(Address) FROM Table2 t2 WHERE t1.ID = t2.ID)
FROM Table1 t1