postgreSQL - How to have condition on Join model result - sql

I have been pulling my hair on how to translate my business requirement into a SQL query.
The case in itself is not that complex. I have two main tables user and list and a many to many relationship between the 2 of them though the userList table, where a user can be connected to a list by having a specific role, like being the owner or a collaborator.
Relevant properties for that example:
user -> id
list -> id | isPublic (BOOL)
userList -> id | userId | listId | role (OWNER|COLLABORATOR)
I have an API endpoint that allows for a user (requester) to retrieve all the lists of another user (target). I have the following 2 cases:
Case 1: requester = target
I have that under control with:
`SELECT
"list".*,
"userList"."id" AS "userList.id",
"userList"."listId" AS "userList.listId"
FROM
"list" AS "list"
INNER JOIN
"userList" AS "userList"
ON "list"."id" = "userList"."listId" AND "userList"."userId" = '${targetUserId}'`;
Case 2: requester =/= target
This is where I'm stuck as I have the following additional constraints:
a user can only see other users' lists that are public
OR lists for which they have the role of COLLABORATOR or OWNER
So I'm looking to do something like:
Get all the lists for which target is connected to (no matter the role) AND for which, EITHER the list is public OR the requester is also connected to that list.
Sample Data
user table
id
----------
john
anna
list table
id | isPublic
------------------
1 false
2 true
3 false
userList table
id | userId | listId | role
- john 1 OWNER
- john 2 OWNER
- john 3 OWNER
- anna 1 COLLABORATOR
Anna requests to see all of John's lists
desired result:
[{
id: 1,
isPublic: false
}, {
id: 2,
isPublic: true
}]
Any help would be greatly appreciated :)
Many thanks

Does this do what you want?
select l.*
from list
inner join userlist ul on ul.listid = l.id
group by l.id
having
bool_or(ul.role = 'OWNER' and ul.userid = 'john') -- owned by John
and (l.ispublic or bool_or(ul.userid = 'anna') -- public or allowed to Anna

Related

SQL / typeORM query to get a DM conversation in a chat web application

I am pretty new to SQL and typeORM, and I especially have troubles when it comes to many to many relationships.
I am working on a chat application. I have two tables connected by a many to many to many relationship.
========= =========
| Room | | User |
========= =========
| id | -< | id |
--------- / ---------
| type | / | ... |
--------- / ---------
| User[]| >--
---------
| ... |
---------
Room.type can be of value dm, private or public
I also have a join table in between the two tables, but it is handled by typeORM so I don't really care about it.
What I want to do is to query the Room table and get all the rooms of type dm where the user are exactly user1_id and user2_id (no more, no less) but I really don't know how to formulate this query. Something like:
SELECT * FROM 'Room'
WHERE type IS 'dm'
AND WHERE 'user1_id' IN 'User.id'
AND WHERE 'user2_id' IN 'User.id'
AND WHERE LENGTH(Room.User) = 2
I am posting this with SQL but eventually I will translate it with typeORM syntax which should not be a problem.
Thanks.
Since there's a many to many relation between Room and User and as the model looks like Room has an array of User, there'd be a join table in the database named room_users_user. Now you want all rooms where only user1_id and user2_id are participants. The corresponding SQL query should be:
SELECT rooms.id from rooms
LEFT JOIN room_users_user ON rooms.id = room_users_user.room_id
LEFT JOIN users ON room_users_user.user_id = users.id
WHERE rooms.type = 'dm' AND users.id IN (2, 4)
GROUP BY rooms.id
HAVING COUNT(users.id) = 2;
which can be written in typeorm like this:
const roomIds = await roomRepository.createQueryBuilder('room')
.leftJoinAndSelect('room.users', 'users')
.select('room.id', 'roomId')
.where('room.type = :type', { type: 'dm' })
.andWhere('users.id in (:...userId)', { userId: [user1_id, user2_id] })
.groupBy('room.id')
.having('count(users.id) = 2')
.getRawMany();
A DB Fiddle for better understanding.

SQL query for fetching users and common friends

I know similiar questions have been asked and answered before, I have reviewed them but still can't quite wrap my head around how to do this in my case.
I would like to create a query (I use postgreSQL) that would return users from my database filtered by name, sorted by the number of friends in common with a given user (the user sending the request).
The data structure is as follows:
I have a users table, that has a column called search_full_name which stores name + surname in the format of "ADAM SMITH". This is what I filter with.
I have a user_friends table that stores information about who is friends with whom. So I have two columns in there: user_id and friend_id . The data is symmetric, i.e. for every (1,3) there is a (3,1) entry.
So far in the friend search I was just using a query like
select * from users where users.search_full_name like '%query%'
But now, I would like to additionally order the result by the amount of friends in common with the user asking, so my query would have two inputs: query and userId.
Turns out I am not as good with sql as I thought, and I would really appreciate your help, it would be great to see some explanations too.
I imagine the desired output as:
+---------+------------------+----------------------+--+
| user_id | search_full_name | common_friends_count | |
+---------+------------------+----------------------+--+
| 45 | Adam Smith | 14 | |
| 123 | Adam Cole | 11 | |
| 12 | Adamic Kapi | 0 | |
+---------+------------------+----------------------+--+
for a query like 'Adam'
I have been trying this for a whole day now and I feel my brain has exploded.
Please help, thanks
The basic idea is a self-join. The following gets a match on users who share friends with the specified user:
select uf2.user_id, count(*) as num_friends
from user_friends uf join
user_friends uf2
on uf2.friend_id = uf.friend_id and
uf2.user_id <> uf2.user_id
where uf2.user_id = ?
group by uf2.user_id
order by count(*) desc; -- the user you care about
Ok, so after a few hours I came up with a query that works :) Here it is for future reference:
select u.id, u.search_full_name, count(uf.friend_id) as common_friend_count
from users u left join user_friends uf on (u.id = uf.user_id and uf.friend_id in (select friend_id from user_friends where user_id = ?))
where u.search_full_name like ?
group by u.search_full_name, u.id
order by common_friend_count desc;

SQL - How to get a max value from a table and add it into another (sqlite3)

Just like the title says, how would i get the maximun value from one table and add it into a field into another table from the same database:
I currently have my main table "users":
username | password | Email | Highscore 1 | Highscore 2 | Highscore 3 |
I also have my other tables :
"user_scores1":
username | Score 1 |
"user_scores2":
username | Score 2 |
"user_scores3":
username | Score 3 |
The "user_scores" tables contains all the scores of all the users (for the 3 different game modes) whenever they play. Whenever the user finishes the game for a particular game mode, a new score gets added into a new row as well as their username associaed to it, to the table of scores for that gamemode
I want to filter out all the scores from a user (e.g user1) and then get their highest score from the game modes, (e.g filtering out all the scores of user1 from the user_scores1 table)
With this, i want to get the highest score of that specific user from that specific table , and add it into my main table "users" in the appropite field (e.g like the previous example ,filtering out all the scores of user1 from the user_scores1 table, then getting the highest score and adding that score into my main table "users" into highscores1 where the username is user1 )
Is this what you want?
update users
set highscore1 = (select max(score) from user_scores1 us where us.username = users.name),
highscore2 = (select max(score) from user_scores2 us where us.username = users.name),
highscore3 = (select max(score) from user_scores3 us where us.username = users.name);

How to properly loop through a table and output to an array in PostgreSQL?

Here is a DB example
table "Users"
fname | lname | id | email
Joe | smith | 1 | yadda#goo.com
Bob | smith | 2 | bob#goo.com
Jane | smith | 3 | jane#goo.com
table "Awards"
userId | award
1 | bigaward
1 | smallaward
1 | thisaward
2 | thataward
table "Invites"
userId | invited
1 | true
3 | true
Basically, how do you write a query in PostgreSQL that allows you to create something like this:
[{
fname:"Joe",
lname:"Smith",
id: 1,
email: "yadda#goo.com",
invited: true,
awards: ["bigaward", "smallaward", "thisaward"]
},
{
fname:"Jane",
lname:"Smith",
id: 3,
email: "jane#goo.com",
invited: true,
awards: []
}]
Here is what I am trying to do...
SELECT users.fname, users.lname, users.id, users.email, invites.invited, awards.award(needs to be an array)
FROM users
JOIN awards on ....(unknown)
JOIN invites on invites.userid = users.id
WHERE invited = true
the above array would be the desired output, just can't figure out a good one shot query. I tried the PostgreSQL docs, but to no avail. I think I might need a WITH statement?
Thanks in advance, Postgres guru!
PostgreSQL v. 9.2
Answered by RhodiumToad on the postgresql IRC:
SELECT users.fname, users.lname, .... array(select awards.award from awards where a.id = user.id) as awards
FROM users
JOIN invites on invites.userid = users.id
WHERE invited = true
array() then through a query inside of it... brilliant!
I think it can be as simple as:
SELECT u.fname, u.lname, u.id, u.email, i.invited, array_agg(a.award)
FROM users u
JOIN invites i ON i.userid = u.id AND i.invited
LEFT JOIN awards a ON a.userid = u.id
GROUP BY u.fname, u.lname, u.id, u.email, i.invited
Your display in JSON format just makes it seem more complicated.
Use a basic GROUP BY including all columns not to be aggregated - leaving award, which you aggre3gate into an array with the help of the aggregate function array_agg().
The LEFT JOIN is important, so not to exclude users without any awards by mistake.
Note that I ignored CaMeL-case spelling in my example to avoid the ugly double-quoting.
This is considerably faster for bigger tables than notoriously slow correlated subqueries - like demonstrated in the added example in the question.
->SQLfiddle demo
You will need to use a hasNext or forEach (or similar) method to iterate trough the database. Whilst you're iterating you can add the results to an array and then encode that array to JSON.
I think I may have misunderstood your question. Apologies if inhave

Tough SQL Update

2 databases QF AND TK
QF has the following:
Imagine you have a table called FunctionalGroup with this data:
FunctionalGroupID | FunctionalGroup
1 Engineering
2 Purchasing
And a table that was a set of login's with a functionalgroupID to reference the group the person is in...
LoginID | FunctionalGroupID | Login
1 1 Jon
2 1 Joe
3 2 Jane
So Jon and Joe are engineering while Jane is purchasing..simple enough
Now there is another database TK.
TK has the following table Login with something to this effect:
Login | FunctionalGroupID
Jon Purchasing
Joe Purchasing
Jane Purchasing
Notice how Jon and Joe in this database are now part of the purchasing group...But notice how this field is the text field and no ID. So what I want to do is use this table as the master data source and update the QF table such that the logins table from the QF now looks like this:
LoginID | FunctionalGroupID | Login
1 2 Jon
2 2 Joe
3 2 Jane
That is update this table to make Jon and Joe part of the purchasing group by setting their functionalgroupid = 2. Because 2 means purchasing.
I tried this:
UPDATE
Login
SET Login.FunctionalGroupID = FunctionalGroup.FunctionalGroupID
FROM Login INNER JOIN
TKKCommonData.dbo.Login lz
ON lz.Login = Login.Login
AND lz.FunctionalGroupID = FunctionalGroup.FunctionalGroup
But I get an error:
Msg 4104, Level 16, State 1, Line 1
The multi-part identifier "FunctionalGroup.FunctionalGroup" could not be bound.
This seems so easy but Im just not sure how to write the update statement. Im just looking to join the tables by the Login (which is the users name) and then by the Functionalgroup names.
I even tried this EDIT per Jay's answer with same error message
UPDATE
QuikFix.dbo.Login
SET QuikFix.dbo.Login.FunctionalGroupID = QuikFix.dbo.FunctionalGroup.FunctionalGroupID
FROM QuikFix.dbo.Login INNER JOIN
TKKCommonData.dbo.Login
ON TKKCommonData.dbo.Login.Login = QuikFix.dbo.Login.Login
AND TKKCommonData.dbo.Login.FunctionalGroupID = QuikFix.dbo.FunctionalGroup.FunctionalGroup
WHERE TKKCommonData.dbo.Login.LoginID= 101
You need an additional INNER JOIN:
UPDATE Login
SET
Login.FunctionalGroupID = FunctionalGroup.FunctionalGroupID
FROM Login
INNER JOIN TKKCommonData.dbo.Login lz
ON lz.Login = Login.Login
INNER JOIN FunctionalGroup
ON lz.FunctionalGroupID = FunctionalGroup.FunctionalGroup
Specify the database name for all of the tables in your query instead of just TKKCommonData.dbo.Login, seems like it can't find the FunctionalGroup table in the database the query is running against.