Postgres query IN query - sql

Is it possible in Postgres to determine if at least one result of query 1 is inside query 2 results?
For example:
SELECT * FROM items
WHERE
(SELECT id FROM users) IN (SELECT user_id FROM user_items WHERE item_id = 1)
I know that this query can be a nonsense, I'm just asking how to do that check in the where clause. In my real query (more complex), I'm getting:
(Postgrex.Error) ERROR 21000 (cardinality_violation): more than one row returned by a subquery used as an expression
if there is more than one result from query1 (query1 IN query2)
EDIT
select user_id
from notification_token n
join notification_folder f on n.user_id = f.user_id
where ((SELECT tag_id FROM notification_folder_tag WHERE notification_folder_id = f.id) IN (SELECT tag_id FROM event_tag WHERE event_id = 1))
tables:
notification_token
| user_id | notification_token |
--------------------------------------------------
| 1 | token1 |
| 2 | token2 |
| 3 | token3 |
notification_folder
| user_id | data |
--------------------------------------------------
| 1 | "useless string" |
notification_folder_tag
| notification_folder_id | tag_id |
--------------------------------------------------
| 1 | 1 |
| 1 | 2 |
| 2 | 5 |
event_tag
| event_id | tag_id |
--------------------------------------------------
| 1 | 1 |
| 2 | 2 |
| 3 | 8 |
The result that I want is user_id 1 from notification_token.
"Where" should be true because at least one tag_id from the left side of the IN (result 1,2) is contained in the right side of the IN (result 1).
Anyways i get error when the left side of the IN is composed by more than one entry. It works properly with just one entry

Try this
SELECT * FROM items
WHERE
EXISTS (SELECT id FROM users)
IN
(SELECT user_id FROM user_items WHERE item_id = 1);
If this doesn't work, go for relational database queries.

You seem to want items anyone who ordered item_1 has also ordered. If this interpretation is correct, then here is one way to write the query:
select distinct i.*
from items i join
user_items ui
on ui.item_id = i.item_id
where ui.user_id in (select ui2.user_id
from user_items ui2
where ui2.item_id = 1
);

Related

PostgreSQL Waiting List Query

I have a table that contains WaitingListPosition records, which is essentially a ticket to signify a user is waiting to join a membership for a Club, which has its own record in the Clubs table. In the WaitingListPositions table there are multiple positions for various Clubs. This is what the table currently looks like with a SELECT query:
id | club_id | location_id | user_id | child_id | created_at
----+---------+-------------+---------+----------+-------------------------------
2 | 8 | 3 | 10 | | 2021-07-05 12:49:24.036091+00
4 | 8 | 3 | 33 | | 2021-07-05 12:55:57.54674+00
5 | 9 | 5 | | 5 | 2021-07-05 12:58:16.319837+00
I have an API that returns a list of WaitingListPosition objects in array, which returns all the WaitingListPositions that user has. I need to get the following data out but have been unsuccessful:
Each object must contain all the WaitingListPosition data
The current position in the waiting list (the list is ordered by created_at (timestamp) field)
The total amount of WaitingListPositions that correspond to the same Club of the current WaitingListPosition object
I need to figure out a query that would produce the following results for the API (using the previous data as example):
id | club_id | location_id | user_id | child_id | created_at | position | total_count
----+---------+-------------+---------+----------+-------------------------------+-----------+---------------
2 | 8 | 3 | 10 | | 2021-07-05 12:49:24.036091+00 | 1 | 2
4 | 8 | 3 | 33 | | 2021-07-05 12:55:57.54674+00 | 2 | 2
5 | 9 | 5 | | 5 | 2021-07-05 12:58:16.319837+00 | 1 | 1
I was able to successfully get the total_count into the query without any problems, perhaps not the most efficient way, but by running a subquery of all the memberships where the user/child is involved, and then getting the count for each location. Worked fine. I just cant for the life of me get the position...
This is what I've got so far, obviously the position is incorrect and is taking the row number of the wrong data.
WITH total_counts AS
(SELECT club_id,
COUNT(id)
FROM ClubWaitingListPositions
WHERE club_id = ANY((SELECT DISTINCT(club_id)
FROM ClubWaitingListPositions
WHERE user_id = $1
OR (child_id = ANY(SELECT id
FROM Children
WHERE $1 = ANY(parents)))))
GROUP BY club_id),
positions AS (SELECT results.*,
ClubLocations.location_name AS location_name,
Clubs.name AS club_name
FROM (SELECT ClubWaitingListPositions.*,
ROW_NUMBER() OVER(ORDER BY created_at) AS position,
(SELECT count
FROM total_counts
WHERE club_id = ClubWaitingListPositions.club_id)
AS total_count
FROM ClubWaitingListPositions) results
INNER JOIN ClubLocations ON ClubLocations.id = results.location_id
INNER JOIN Clubs ON ClubLocations.club_id = Clubs.id
WHERE (user_id = $1)
OR (child_id = ANY(SELECT id FROM Children WHERE $1 = ANY(parents))))
SELECT positions.*,
CONCAT(Users.first_name, ' ', Users.last_name) AS member_name
FROM positions
INNER JOIN Users ON Users.id = positions.user_id
WHERE user_id IS NOT NULL
UNION
SELECT positions.*,
CONCAT(Children.first_name, ' ', Children.last_name) AS member_name
FROM positions
INNER JOIN Children ON Children.id = positions.child_id
WHERE child_id IS NOT NULL
ORDER BY created_at DESC
Any help appreciated ! :)

Postgres - how to get proper count with join

Sorry as a newcomer to sql (postgres in this case) I can't tease out the right answer from similar questions. So here's an example
I have two tables:
records:
id | status
----------------
1 | open
2 | open
3 | close
4 | open
events:
id | record_id | role | something_else
---------------------------------------------
1 | 2 | admin | stringA
2 | 1 | user | stringB
3 | 4 | admin | stringC
4 | 2 | admin | stringD
5 | 2 | admin | stringE
6 | 2 | user | stringF
7 | 3 | user | stringG
I basically would like to have a count(status) that reflects how many records have at least one events.role = 'admin' in the events table
in the above example it would be:
status | count
---------------
open | 2
close | 0
Any help much appreciated!
No need for nested queries. You can just use conditional aggregation:
select r.status, count(distinct r.id) filter(where e.role = 'admin') cnt
from records r
inner join events e on e.record_id = r.id
group by r.status
Demo on DB Fiddle:
status | cnt
:----- | --:
close | 0
open | 2
I basically would like to have a count(status) that reflects how many records have at least one events.role = 'admin' in the events table.
I would suggest:
select r.status, count(*) filter (where has_admin)
from (select r.*,
(exists (select 1 from events e where e.record_id = r.id and e.role = 'admin')) as has_admin
from records r
) r
group by r.status;
For your small data sample, the difference between exists and a join doesn't matter. With more data, though, the exists does not multiply the number of rows, which should make it a bit faster. Also, this guarantees that all statuses are included, even those with no events.

Unique rows from PostgreSQL database and ordering

how can I get unique rows from PostgreSQL (8.4.20), at this moment my query looks like:
SELECT
DISTINCT ON(student.id) student.id,
student.*,
programme_stage.*
FROM
person AS student
INNER JOIN
programme ON (student.id = programme.person_id)
RIGHT JOIN
programme_stage ON (programme.id = programme_stage.programme_id)
ORDER BY
student.id,
student.last_name ASC,
student.first_name ASC
LIMIT 10 OFFSET 0
The above query works correctly, but I want to sort by the last and first name in first order.
Sample data:
| id | first_name | last_name | programme_id | programme_stage _id |
|----|------------|-----------|--------------|---------------------|
| 1 | Michał | Nowak | 1 | 1 |
| 2 | Jan | Kowalski | 2 | 2 |
| 3 | Tomasz | Thomas | 2 | 1 |
Expected output:
| id | first_name | last_name | programme_id | programme_stage _id |
|----|------------|-----------|--------------|---------------------|
| 2 | Jan | Kowalski | 2 | 2 |
| 1 | Michał | Nowak | 1 | 1 |
| 3 | Tomasz | Thomas | 2 | 1 |
If I try to remove from order statment student.id column, I getting error:
..SELECT DISTINCT ON expressions must match the expressions .. ORDER BY LINE 2: DISTINCT ON(student_person.id) student_person.id,
^
Use a subquery:
SELECT s.*
FROM (SELECT DISTINCT ON (s.id) s.*, ps.*
FROM programme_stage ps LEFT JOIN
programme p
ON programme.id = ps.programme_id LEFT JOIN
person s
ON s.id = p.person_id)
ORDER BY s.id
LIMIT 10 OFFSET 0
) s
ORDER BY s.last_name ASC, s.first_name ASC;
Notes:
I prefer shorter table aliases so the query is easier to write and to read.
I prefer LEFT JOIN to RIGHT JOIN. However, because you are using the person table for aggregation, you probably want inner joins.
There is no need to select the id twice.
You may need to select explicit columns instead of * if columns have the same name.

Find number of rows identical one some, but different on another column

Say I have the following table:
CREATE TABLE data (
PROJECT_ID VARCHAR,
TASK_ID VARCHAR,
REF_ID VARCHAR,
REF_VALUE VARCHAR
);
I want to identify rows where
PROJECT_ID, REF_ID, REF_VALUE are the same
but TASK_ID are different.
The desired output is a list of TASK_ID_1, TASK_ID_2 and COUNT(*) of such conflicts. So, for example,
DATA
+------------+---------+--------+-----------+
| PROJECT_ID | TASK_ID | REF_ID | REF_VALUE |
+------------+---------+--------+-----------+
| 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 2 |
| 1 | 2 | 1 | 1 |
| 1 | 2 | 1 | 2 |
+------------+---------+--------+-----------+
OUTPUT
+-----------+-----------+----------+
| TASK_ID_1 | TASK_ID_2 | COUNT(*) |
+-----------+-----------+----------+
| 1 | 2 | 2 |
| 2 | 1 | 2 |
+-----------+-----------+----------+
would mean that there are two entries with TASK_ID == 1 and two entries with TASK_ID == 2 that share the same values for the other three columns. The inherent symmetry in the output is fine.
How would I go about finding this information? I've tried joining the table onto itself and grouping, but this turned up more results for a single task than the table had rows altogether, so it's clearly wrong.
The database used is PostgreSQL, though a solution that applies to most common SQL systems would be preferable.
You want a self join and aggregation:
select d1.task_id as task_id_1, d2.task_id as task_id_2, count(*)
from data d1 join
data d2
on d1.project_id = d2.project_id and
d1.ref_id = d2.ref_id and
d1.ref_value = d2.ref_value and
d1.task_id <> d2.task_id
group by d1.task_id, d2.task_id;
Notes:
Add the condition d1.task_id < d2.task_id if you want each pair to occur only once in the result set.
This does not handle NULL values, although that is easy enough to handle. Use is not distinct from instead of =.
You can also simplify this a bit with the using clause:
select d1.task_id as task_id_1, d2.task_id as task_id_2, count(*)
from data d1 join
data d2
using (project_id, ref_id, ref_value)
where d1.task_id <> d2.task_id
group by d1.task_id, d2.task_id;
You can get an idea of how many rows might be returned by using:
select d.project_id, d.ref_id, d.ref_value, count(distinct d.task_id), count(*)
from data d
group by d.project_id, d.ref_id, d.ref_value;
This is how I understand your question. This assume there are only two task for the same combination.
SQL DEMO
SELECT "PROJECT_ID", "REF_ID", "REF_VALUE",
MIN("TASK_ID") as TASK_ID_1,
MAX("TASK_ID") as TASK_ID_2,
COUNT(*) as cnt
FROM Table1
GROUP BY "PROJECT_ID", "REF_ID", "REF_VALUE"
HAVING MIN("TASK_ID") != MAX("TASK_ID")
-- COUNT(*) > 1 also should work
OUTPUT
I add more column to make clear what are the same elements:
| PROJECT_ID | REF_ID | REF_VALUE | task_id_1 | task_id_2 | cnt |
|------------|--------|-----------|-----------|-----------|-----|
| 1 | 1 | 2 | 1 | 2 | 2 |
| 1 | 1 | 1 | 1 | 2 | 2 |

how to bake in a record count in a sql query

I have a query that looks like this:
select id, extension, count(distinct(id)) from publicids group by id,extension;
This is what the results looks like:
id | extension | count
-------------+-------------------------+-------
18459154909 | 12333 | 1
18459154909 | 9891114 | 1
18459154919 | 43244 | 1
18459154919 | 8776232 | 1
18766145025 | 12311 | 1
18766145025 | 1122111 | 1
18766145201 | 12422 | 1
18766145201 | 14141 | 1
But what I really want is for the results to look like this:
id | extension | count
-------------+-------------------------+-------
18459154909 | 12333 | 2
18459154909 | 9891114 | 2
18459154919 | 43244 | 2
18459154919 | 8776232 | 2
18766145025 | 12311 | 2
18766145025 | 1122111 | 2
18766145201 | 12422 | 2
18766145201 | 14141 | 2
I'm trying to get the count field to show the total number of records that have the same id.
Any suggestions would be appreciated
I think you want to count distincts extentions, not ids.
Run this query:
select id
, extension
(select count(*) from publicids p1 where p.id = p1.id ) distinct_id_count
from publicids p
group by id,extension;
This is more or less the same as Pastor's answer. Depending on what the optimizer does it might be faster with higher record count source tables.
select p.id, p.extension, p2.id_count
from publicids p
inner join (
select id, count(*) as id_count
from publicids group by id
) as p2 on p.id = p2.id