Get the NOT IN values into an array in postgres SQL - postgresql

I am looking to obtain a set of results where I receive a user_id and a collection of books that the user HAS NOT reviewed. I would like the following as the required output:
user_id
Array of books not reviewed
1
{Array of books not reviewed}
2
{Array of books not reviewed}.
Currently there are two tables that this query would require to be pulled from. One contains "reviews" that possesses the user_id of the review, and the book_id that the user HAS reviewed. The book_id is a foreign key to the the "books" table where the total list of books is contained. Therefore for each user_id in the output, I would require the total list of books, excluding the ones that have been reviewed per user.
See the insert statements below for the reviews and books tables:
Reviews tables -
CREATE TABLE reviews (
review_id SERIAL PRIMARY KEY,
rating INT CHECK (rating BETWEEN 0 AND 10),
review_text TEXT,
book_id INT REFERENCES books(id),
user_id INT
);
Example of a snippet of the table data:
review_id
rating
book_id.
user_id
1
8
2
1
2
6
3
3
Books table -
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR(100),
price_in_pence INTEGER,
quantity_in_stock INTEGER
);
Example of a snippet of books data:
id
title
price_in_pence
quantity_in_stock
1
bookname
549
12
2
LOTR
799
9
I have tried the following query, however this is not dynamic and only works per user_id entered (identified below using '**'):
SELECT r.user_id,
array( SELECT b.title
FROM books b
WHERE b.id NOT IN (SELECT r.book_id
FROM reviews r
WHERE user_id = '4')
) AS Books_not_reviewed
FROM reviews r
GROUP BY (r.user_id);
Apologies I am somewhat new to SQL. Any help would be greatly appreciated. I am using Postgres version 14.1

You can make your query dynamic, if you make the userid as a parameter and use prepared statements.
Also, the below query can give you the list of all books NOT reviewed by each user, you can modify the query to suit your needs.
select u.userid, array( select bookid from books
where bookid not in (select
bookid from reviews r
where r.userid=u.userid) )
from users u

Related

Postgres Update with if statement or case (don't know how to call it)

I have three tables:
Users contains Id
ChatRoomUserLinks contains Id, RoomId (Chats.Id), UserId
Chats contains Id, Name, RoomType
If Chat connected with 2 people (only 2 people in one chat) I need to set RoomType = 0, if more than 2 people in one chat set RoomType = 1.
Sorry, I'm new in postgres, so I do'not know.
For example:
Chat1 contains 2 people and Chat2 contains 3 people, so in db should be looking like:
1 Chat1 0
2 Chat2 1
Well, maybe updating the chat table like this:
https://www.db-fiddle.com/f/kzdxNPoi7fpgjshM1LSxsK/0
CREATE TABLE users (
id serial PRIMARY KEY,
name TEXT);
CREATE TABLE chats (
id serial PRIMARY KEY,
name text,
type int);
CREATE TABLE chatroomuserlinks (
id serial PRIMARY KEY,
chat_id int REFERENCES chats,
user_id int REFERENCES users ) ;
INSERT INTO users (name) VALUES ('alice'), ('bob'), ('charlie');
INSERT INTO chats (name) VALUES ('private'), ('social');
INSERT INTO chatroomuserlinks (chat_id, user_id) VALUES (1, 1), (1,2), (2,1), (2,2), (2,3);
UPDATE chats
SET type = sub.type
FROM (SELECT chat_id, CASE WHEN count(l.user_id) <= 2 THEN 0 ELSE 1 END as type
FROM chatroomuserlinks l group by l.chat_id) sub
WHERE sub.chat_id = id;

Having error on JOIN

This is a Firebird database.
First Table
Contacts
Company_ID - job_title
Second Table
Client_id - Co_name
In contacts, I want to the job_title field to contain the co_name.
client_id and company_id are the same.
Co_name correspond to company_id as well as client_id.
this:
UPDATE Contacts
SET Contacts.Job_title = Clients.co_name
WHERE
company_id IN (
SELECT
client_id
FROM
clients
JOIN Contacts c ON Client_id = company_id
WHERE
record_status = 'A'
)
gives me an error as cannot find (clients.co_name)
this other option:
UPDATE Contacts
JOIN Clients ON Clients.Client_id = Contacts.Client_id
SET Contacts.Job_title = Clients.Client_name
gives me an error on JOIN
Any other ideas please?
Thank you all
Possibly already answered here: Update records in one table using another table's records as WHERE parameters - looks like it should work, but I'm not a Firebird expert.
The code from that answer (but look for more context, and alternative answers):
UPDATE Table1
SET Column1 = NULL
WHERE NOT EXISTS
(SELECT 1
FROM Table2
WHERE Table2.Column2 = Table1.Column2)
(couldn't flag question as duplicate because that question has no upvoted or accepted answer)

Using ANY inside HAVING clause in Postgres?

Let's say I've the following schema :
CREATE TABLE author(
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE article(
id SERIAL PRIMARY KEY,
rating NUMERIC NOT NULL,
author_id INTEGER NOT NULL REFERENCES author
);
CREATE INDEX ON article(author_id);
I would like to fetch all authors and their top 5 articles if there exists atleast one article of the author with rating > 4.
It was tempting to write this:
SELECT au.id AS author,
json_agg(ar.*) AS articles
FROM
author au
JOIN LATERAL
(SELECT *
FROM article
WHERE author_id = au.id
ORDER BY rating DESC LIMIT 5) ar ON (TRUE)
GROUP BY au.id
HAVING any(ar.rating) > 4;
While any(ar.rating) > 4 looks like a filter expression on each group, any(ar.rating) is not an aggregated value. So, it seems reasonable for Postgres to reject this query. Is it possible to write the query with HAVING?
As an alternative, I write this query to fetch the results :
SELECT au.id AS author,
json_agg(ar.*) AS articles
FROM
(SELECT au.*
FROM author au
WHERE EXISTS
(SELECT 1
FROM article
WHERE rating > 4 AND author_id = au.id)) au
JOIN LATERAL
(SELECT *
FROM article
WHERE author_id = au.id
ORDER BY rating DESC LIMIT 5) ar ON (TRUE)
GROUP BY au.id;
This however doesn't combine both the grouping and checking for the existence of an article with rating > 4 in a single step. Is there a better way to write this query?
If you insist on using ANY you have to use array_agg to aggregate that column into an array.
HAVING
4< ANY(Array_Agg(ar.rating))
But if any is higher than 4 it also means that the maximum is higher that 4 so more readable will be
HAVING
4 < Max(ar.rating)

Retrieving Representative Records for Unique Values of Single Column

For Postgresql 8.x, I have an answers table containing (id, user_id, question_id, choice) where choice is a string value. I need a query that will return a set of records (all columns returned) for all unique choice values. What I'm looking for is a single representative record for each unique choice. I also want to have an aggregate votes column that is a count() of the number of records matching each unique choice accompanying each record. I want to force choice to lowercase for this comparison to be made (HeLLo and Hello should be considered equal). I can't GROUP BY lower(choice) because I want all columns in the result-set. Grouping by all columns causes all records to return, including all duplicates.
1. Closest I've gotten
select lower(choice), count(choice) as votes from answers where question_id = 21 group by lower(choice) order by votes desc;
The issue with this is it will not return all columns.
lower | votes
-----------------------------------------------+-------
dancing in the moonlight | 8
pumped up kicks | 7
party rock anthem | 6
sexy and i know it | 5
moves like jagger | 4
2. Trying with all columns
select *, count(choice) as votes from answers where question_id = 21 group by lower(choice) order by votes desc;
Because I am not specifying every column from the SELECT in my GROUP BY, this throws an error telling me to do so.
3. Specifying all columns in the GROUP BY
select *, count(choice) as votes from answers where question_id = 21 group by lower(choice), id, user_id, question_id, choice order by votes desc;
This simply dumps the table with votes column as 1 for all records.
How can I get the vote count and unique representative records from 1., but with all columns from the table returned?
Join grouped results back with primary table, then show only one row for each (question,answer) combination.
similar to this:
WITH top5 AS (
select question_id, lower(choice) as choice, count(*) as votes
from answers
where question_id = 21
group by question_id , lower(choice)
order by count(*) desc
limit 5
)
SELECT DISTINCT ON(question_id,choice) *
FROM top5
JOIN answers USING(question_id,lower(choice))
ORDER BY question_id, lower(choice), answers.id;
Here's what I ended up with:
SELECT answers.*, cc.votes as votes FROM answers join (
select max(id) as id, count(id) as votes
from answers
group by trim(lower(choice))
) cc
on answers.id = cc.id ORDER BY votes desc, lower(response) asc

PostgreSQL join to most recent record between tables

I have two tables pertaining to this question: conversations has many messages. The basic structure (with just the relevant columns) is as follows:
conversations (
int id (PK)
)
create table conversation_participants (
int id (PK),
int conversation_id (FK conversations),
int user_id (FK users),
unique key on [conversation_id, profile_id]
)
create table messages (
int id (PK),
int conversation_id (FK conversations),
int sender_id (FK users),
int recipient_id (FK users),
text body
)
For each conversations entry, given a user_id I want to receive:
all conversations that user participated in (i.e.: conversations.*)
joined to the most recent matching message (i.e.: order by messages.id desc limit 1)
conversations ordered by their most recent message id (i.e.: order by messages.id desc)
Unfortunately, all the query help I can seem to find on anything like this pertains to MySQL, and that doesn't work in PostgreSQL. The closest thing I found is this answer on StackOverflow that gives an example of the select distinct on (...) syntax. However, unless I'm just doing it wrong, I can't seem to get the results ordered in the correct way given the grouping constraints I need with that method.
All information is in the table "messages", you don't need the other tables:
SELECT
id,
body,
c.* -- content from conversations
FROM messages
JOIN
(SELECT MAX(id) AS id, conversation_id
FROM messages
WHERE 1 IN(sender_id, recipient_id) -- the number is the userid, should be dynamic
GROUP BY conversation_id) sub
USING(id, conversation_id)
JOIN conversations c ON c.id = messages.conversation_id
ORDER BY
id DESC;
Edit: Just JOIN on "conversations" to get the data needed from this table.
Try this:
select
*
from
conversation_participants cp
join conversations c on
c.id = cp.conversation_id
-- assuming you only want the conversations where a
-- message has been left. otherwise use left join
join messages m on
m.conversation_id = cp.conversation_id
and m.id = (
select
id
from
messages _m
where
_m.conversation_id = m.conversation_id
and sender_id = 1
order by
id desc
limit 1
)
where
cp.user_id = 1
order by
m.id desc;