Query to get all chats for loggedin users - postgresql

Database schema :
https://imgur.com/a/jNzcN5u
SELECT DISTINCT ON (c.sender_id,a.ads_id)
a.ads_id,
a.title,
b.name AS seller,
(select u2.name from users u2 where u2.user_id =a.user_id) as buyer,
c.sender_id,
r.recipient_id,
d.created_on
FROM ads a
INNER JOIN users u ON a.user_id = u.user_id
INNER JOIN recipient r ON a.ads_id = r.ads_id
INNER JOIN chat c ON c.chat_id = r.chat_id
WHERE r.recipient_id = 32 OR c.sender_id = 32
-- order by c.created_on desc
Above query giving some duplicates and not selecting the most recent chat sent by.
And also wants to order chat list by most recent chat with user.
If possible, need count for unread message.
Note : Any user can be a buyer or seller.
If any modification on table or suggestion, please let me know.
Thanks a lot for solving!

Related

Postgresql, inner join or subquery or view?

I have the following tables:
user
car
dealer
user_metrics
- user_id (FK) (required)
- dealer_id (FK) (can be null)
- car_id (FK) (can be null)
- saved
- .... other columns
A user can save a car or a dealership, when that happens the user_metrics.saved is set to true and the related car_id or dealership_id is set (car_id and dealership_id are exclusive, only one is set for a row).
I want user A to be able to see all users that have saved the same cars / dealerships.
So, if user A has saved car 1, 2,3 and dealership 5,7, I want to get all users that have saved any of those cars / dealerships.
I thought about inner join on user_metrics, but, I am not sure how to write the entire query that would deliver on this.
What query would allow me to get all users that have saved any of the cars/dealerships a certain user has saved?
If I understand as well maybe the below query solve your problem.
First should find a list of user A has been reserved after that should search which of car or dealer used by another user
with user_saved_data as (
select um.*,
u.name,
...
from user_metrics um
inner join user u
on um.user_id = u.id
where um.saved = true
and u.id = $1 -- User id of user 'A' or any username (Or use other column for create custom condition)
)
select usd.name as current_reserved_user,
u.name as reserved_by_user,
d.*,
c.*
from user_metrics um
inner join user u on um.user_id = u.id
left join user_saved_data usd on usd.dealer_id notnull and usd.dealer_id = um.dealer_id
left join user_saved_data usd on usd.car_id notnull and usd.car_id = um.car_id
left join dealer d on um.dealer_id = d.id
left join car c on um.car_id = c.id

Postgresql, define limit within join?

I have the following tables:
library
|id|...
books
|id|library_id (fk)|...
permissions
|user_id (fk)|library_id(fk)|read bool| ...
I want to find the most 10 recent books a certain user (id) can see in any library the user has read permissions on.
A library can have many books
A user can have one permissions record per library with read bool true or false
What I am not sure how to do is to limit the result of books per library they're in to a certain limit I want to set dynamically.
normally I would do this:
select b.id,
l.id
from book b
inner join library l on l.id = b.library_id
inner join permissions p on l.id = p.library_id
where p.user_id=${user.id} and p.read=true
order by b.created_at desc
I am not sure how to only return the most 10 (limit) recent books per library the user has access to.
How can I set the limit per library?
You would do a lateral join:
select b.id,
l.id
from permissions p
inner join library l on l.id = p.library_id
cross join lateral
(select * from book where library_id=l.id order by created_at desc LIMIT 10) b
where p.user_id=${user.id} and p.read=true

Postgresql WHERE clause using conditional sub-queries

I have a situation where each of the clients has users and each user can access to information about one or more branches.
We also have sys admins who can see everything and in database don't have any sites assigned to them. It just says the user is sys admin, so our system does not restrict the access.
I need to make a database query where I extract the list of branches the user has access to, but if the user is sys admin, I want to extract the list of all branches in the system.
I was trying something like this, but it does not work:
Select sites.name, sites.id
FROM sites
WHERE
sites.id IN (
CASE
WHEN (select u.level FROM users "u" WHERE u.username = 'JohnBrown') ='ROLE_SYSTEM_ADMIN'
THEN
(select id FROM sites)
ELSE
(select s2.id FROM users_have_sites uhs2
left join users u2 ON u2.id = uhs2.user_id
left join sites s2 ON s2.id = uhs2.site_id
where u2.username = 'JonhBrown')
END
)
I am getting this error:
ERROR: more than one row returned by a subquery used as an expression
I think something like this would work for you:
SELECT s.name, s.id
FROM sites s
LEFT JOIN users_have_sites uhs ON uhs.site_id = s.id
LEFT JOIN users u ON u.id = uhs.user_id AND u.username = 'JohnBrown'
WHERE (CASE WHEN (SELECT u.level FROM users WHERE u.username = 'JohnBrown') = 'ROLE_SYSTEM_ADMIN'
THEN TRUE ELSE FALSE END
OR u.id IS NOT NULL);
The LEFT JOINs do not filter out records from the sites table like an INNER JOIN would, so any site that meets either of the conditions in the WHERE clause will be in the result. This means that if your subquery shows that the user is a sys admind or if there is a record for that user and site is found in the users_have_sites table, those sites will be in the result set.
EDIT: Another fairly easy to read solution would be something like this:
SELECT s.name, s.id
FROM sites s,
users_have_sites uhs,
users u
WHERE u.username = 'JohnBrown'
AND (u.level = 'ROLE_SYSTEM_ADMIN'
OR (s.id = uhs.site_id AND u.id = uhs.user_id))
GROUP BY s.name, s.id;
The downside of this query is that it uses implicit joins which are not used very much any more. They are generally seen as an older way of doing things and can be less efficient. This will join all rows of on table to all rows of another table and then all of your filtering (and what you would generally think of as join conditions) are all in the WHERE clause. These typed of joins can be less efficient but this one should not be as the WHERE clause makes sure that only 1 result per site.
I think that this does what you want:
select s.name, s.id
from sites s
inner join users u on u.username = 'JohnBrown'
where
u.level = 'ROLE_SYSTEM_ADMIN'
or exists (
select 1
from users_have_sites uhs
where uhs.site_id = s.id and uhs.user_id = u.id
)
Here is another version of the query that you may find easier to follow (I do):
select s.name, s.id
from users u
inner join sites s
on u.level = 'ROLE_SYSTEM_ADMIN'
or exists (
select 1
from users_have_sites uhs
where uhs.site_id = s.id and uhs.user_id = u.id
)
where u.username = 'JohnBrown'

Distinct value for multiplie column as one

I have a table transactions with columns : id_transaction, credited_person, debited_person and a table users with id_user.
For each user I want a list of all the people with whom he made a transaction (it does not matter if the user is debited or credited)
Can someone help me please ?
You can join users to transactions, group by user and use string_agg():
select u.user_id,
string_agg(distinct
case
when u.user_id = t.credited_person then t.debited_person
else t.credited_person
end,
','
)
from users u left join transactions t
on u.user_id in (t.credited_person, t.debited_person)
group by u.user_id

Get distinct row by primary key, but use value from another column

I'm trying to get the sum of the total time that was spent sending all emails within a campaign.
Because of the joins in my query I end up with the 'processing_time' column duplicated over many rows. So running sum(s.processing_time) as send_time will always over represent how long it took to run.
select
c.id,
c.sender,
c.subject,
count(*) as total_items,
count(distinct s.id) as sends,
sum(s.processing_time) as send_time,
from campaigns c
left join sends s on c.id = s.campaigns_id
left join opens o on s.id = o.sends_id
group by c.id;
I'd ideally like to do something like sum(s.processing_time when distinct s.id) but I can't quite work out how to achieve that.
I have made other attempts using case but I always run into the same issue, I need to get the distinct rows based on the ID column, but work with another column.
Since you want statistics related to distinct s.id as well as c.id, group by both columns. Collect the (intermediate) data that you need,
and use this table as the inner table in a nested sub-select query.
In the outer select, group by c.id alone.
Since the inner select groups by s.id, values which are unique per s.id will not get double-counted when you sum/group by c.id.
SELECT id
, sender
, subject
, sum(total_items) as total_items
, sum(sends) as sends
, sum(processing_time) as send_time
FROM (
SELECT
c.id
, s.id as sid
, count(*) as total_items
, 1 as sends
, s.processing_time
, c.sender
, c.subject
FROM campaigns c
LEFT JOIN sends s on c.id = s.campaigns_id
LEFT JOIN opens o on s.id = o.sends_id
GROUP BY c.id, c.sender, c.subject, s.processing_time, s.id) t
GROUP BY id, sender, subject
ORDER BY id
Since the final table includes sender and subject, you'll need to group by these columns as well to avoid an error such as:
ERROR: column "c.sender" must appear in the GROUP BY clause or be used in an aggregate function
LINE 14: , c.sender