I have a table of subscription records for people subscribed to groups. There's a groupid column and a userid column (in addition to the primary key id). I want to find all the subscription rows of people subscribed to group 1 that do not also have a subscription record for group 2. I thought I could use an anti-join pattern, but I can't get it to work. I've tried:
SELECT
*
FROM subs s1
LEFT JOIN subs s2 ON s1.groupid=1 AND s2.groupid=2 AND s1.userid=s2.userid
WHERE s2.id IS NULL;
But that does not work (it returns subscription records with groupids that are not 1).
I would use group by for this:
select s.userid
from subs s
group by s.userid
having sum(case when s.groupid = 1 then 1 else 0 end) > 0 and
sum(case when s.groupid = 2 then 1 else 0 end) = 0;
Each condition in the having clause counts records for one of the groups. The first condition says there is at least one row with groupid = 1. The second says there are no records with groupid = 2.
You can do what you want with a left join as well. Here is the approach:
SELECT s1.*
FROM subs s1 LEFT JOIN
subs s2
ON s1.userid = s2.userid AND s2.groupid = 2
WHERE s1.groupid = 1 AND s2.id IS NULL;
Because it is a left join, the condition on the first table should go in the where clause.
Related
Let's start with such example:
I have 2 tables: Therapists and their Sessions.
I have such query
select therapists.id, s1.completed_count, s2.canceled_count
from therapists
inner join (select therapist_id, count(*) as completed_count
from sessions
where sessions.status = 'complete'
group by therapist_id) s1 on s1.therapist_id = therapists.id
inner join (select therapist_id, count(*) as canceled_count
from sessions
where sessions.status = 'canceled'
group by therapist_id) s2 on s2.therapist_id = therapists.id;
Is it possible to count sessions like this with single subquery join? Because I have several statuses and don't think that is ok to performance to write so many joins.
If you have a static number of statuss, numerous JOINs are fine. Alternatively, you could consider something along the lines of crosstab to "pivot" a row result to a column result. See PostgreSQL: tablefunc Module.
If you have a dynamic number of statuss, you could perhaps leverage array_agg to convert row results to an array. See PostgreSQL: Array Functions and Operators.
Simple
SELECT
therapists.id,
COUNT(
CASE WHEN status = 'complete' THEN 1 END
) AS completed_count,
COUNT(
CASE WHEN status = 'canceled' THEN 1 END
) AS canceled_count
FROM therapists t
JOIN sessions s ON s.therapist_id = t.id
GROUP BY t.id
;
I have two tables: invitees and invitee_information. invitees belong to events (third table event_id is a foreign key in invitees table).
I want to get the total count of invitees per event for which there is no information available yet (no record in the invitee_information table).
When I write the query using only one event_id in the where clause and anti-join pattern (IS NULL for invitee_id column to filter for rows having no information saved) the count is returned as expected for that given event_id:
SELECT COUNT(i.id)
FROM invitees i
LEFT JOIN invitee_information ii
ON ii.invitee_id = i.id
WHERE ii.invitee_id IS NULL AND i.`event_id` = 18571 AND i.`invitation_sent` = 1;
Now in order to optimize the query to get this count data at DB level for multiple event_ids instead of getting this in a loop for each event_id, I used IN and passed multiple event_ids and used GROUP_BY event_id.
Expected result should be (if there is no data saved in invitee_information table for event_id 18569 :
event_id | count(i.id)
18569 | 0
But query result is always empty. My updated query is:
SELECT i.`event_id`, COUNT(i.id)
FROM invitees i
LEFT JOIN invitee_information ii
ON i.id = ii.invitee_id
WHERE ii.invitee_id IS NULL AND i.`event_id` IN(18569,18571) AND i.`invitation_sent` = 1
GROUP BY i.`event_id`;
The only way I could imagine this happening is if you have internationalization settings such that , means a decimal point. You can easily test this using:
SELECT i.`event_id`, COUNT(i.id)
FROM invitees i LEFT JOIN
invitee_information ii
ON i.id = ii.invitee_id
WHERE ii.invitee_id IS NULL AND
(i.`event_id` = 18569 OR i.`event_id` = 18571) AND
i.`invitation_sent` = 1
GROUP BY i.`event_id`;
If this works, then at least we have identified the problem. I think a space then works:
SELECT i.`event_id`, COUNT(i.id)
FROM invitees i LEFT JOIN
invitee_information ii
ON i.id = ii.invitee_id
WHERE ii.invitee_id IS NULL AND
i.`event_id` IN (18569, 18571) AND
i.`invitation_sent` = 1
GROUP BY i.`event_id`;
I should note that you probably want COUNT(ii.invitee_id) in the SELECT rather than COUNT(i.id). The latter will never return 0.
EDIT:
Perhaps the two events are not in the invitees table. Assuming you have an events table, then something like this:
SELECT e.event_id, COUNT(i.id)
FROM events e LEFT JOIN
invitees i
ON i.event_id = e.event_id AND
i.invitation_sent = 1 LEFT JOIN
invitee_information ii
ON i.id = ii.invitee_id
WHERE ii.invitee_id IS NULL AND
e.event_id IN (18569, 18571) AND
GROUP BY e.event_id;
I want to make a query on a SQL Compact 4.0 DB-Table, with 2 COUNT()-columns. The first column shall count all rows ( COUNT(*) ) and the second one shall only count the row, when the decimal-value of a specific column is higher as or equal to 3.0
I got this far:
SELECT COUNT(a.number) AS Participant, COUNT(b.specificColumn) AS Approved
FROM person AS a
LEFT OUTER JOIN test AS b
ON b.number = a.number
This way the second COUNT() will obviously only count rows, that actually have a value != NULL
I don't think you can do it using a count. Try using a case statement. Not tested:
SELECT COUNT(a.number) AS Participant,
SUM(case when b.specificColumn >3 then 1 else 0 end) as Approved
FROM person AS a
LEFT OUTER JOIN test AS b
ON b.number = a.number
SELECT COUNT(a.number) AS Participant,
SUM(CASE WHEN b.specificColumn IS NULL THEN 0
WHEN b.specificColumn >= 3 THEN 1
ELSE 0) AS Approved
FROM person AS a
LEFT OUTER JOIN test AS b
ON b.number = a.number
I have a little wired issue.
I have to select two count from query Likes and Collects but when I add second query instead of 2 likes and 10 collects I get 10 likes and 10 collects.
What am I doing wrong here?
select COUNT(tl.ItemLikeId) as a, COUNT(tib.PacketId) as b
from Items i
left join ItemLikes il
on il.ItemId = i.ItemId
left join ItemsInPackets iip
on iip.ItemId = i.ItemId
where i.ItemId = 14591
Try SELECT COUNT(DISTINCT tl.ItemLikeId) AS a, COUNT(DISTINCT tib.PacketId) as b.
Your join gives you ten rows, so you have ten IDs from each table. However, not all of the IDs are unique. You're looking for unique IDs.
Count returns the number of rows. Not the number of rows with a value, and not the number of distinct rows.
To get number row rows with a value
select SUM(CASE WHEN tl.ItemLikeId IS NOT NULL THEN 1 ELSE 0 END) as a,
SUM(CASE WHEN tib.PacketId IS NOT NULL THEN 1 ELSE 0 END) as b
To get the number of distinct values, do what zimdanen suggested and use COUNT(DISTINCT)
select COUNT(DISTINCT tl.ItemLikeId) as a, COUNT(DISTINCT tib.PacketId) as b
Another approach, if all you are using ItemLikes and ItemsInPackets for are the counts
select
(
SELECT COUNT(ItemLikeId)
FROM ItemLikes
WHERE ItemId = i.ItemId
) as a,
(
SELECT COUNT(PacketId)
FROM ItemsInPackets
WHERE ItemId = i.ItemId
) as b
from Items i
where i.ItemId = 14591
I'm stuck on a query with a join. The client's site is running mysql4, so a subquery isn't an option. My attempts to rewrite using a join aren't going too well.
I need to select all of the contractors listed in the contractors table who are not in the contractors2label table with a given label ID & county ID. Yet, they might be listed in
contractors2label with other label and county IDs.
Table: contractors
cID (primary, autonumber)
company (varchar)
...etc...
Table: contractors2label
cID
labelID
countyID
psID
This query with a subquery works:
SELECT company, contractors.cID
FROM contractors
WHERE contractors.complete = 1
AND contractors.archived = 0
AND contractors.cID NOT IN (
SELECT contractors2label.cID FROM contractors2label
WHERE labelID <> 1 AND countyID <> 1
)
I thought this query with a join would be the equivalent, but it returns no results. A manual scan of the data shows I should get 34 rows, which is what the subquery above returns.
SELECT company, contractors.cID
FROM contractors
LEFT OUTER JOIN contractors2label ON contractors.cID = contractors2label.cID
WHERE contractors.complete = 1
AND contractors.archived = 0
AND contractors2label.labelID <> 1
AND contractors2label.countyID <> 1
AND contractors2label.cID IS NULL
When doing a LEFT JOIN, you need to put all conditions of the JOIN into the ON clause.
In your example you get NULL for left joined columns that do not exist, but you then compare them to values again (<> 1) which does not work.
SELECT c.company, c.cID
FROM contractors c
LEFT JOIN contractors2label c2
ON ( c2.cID = c.cID AND c2.labelID <> 1 AND c2.countyID <> 1 )
WHERE c.complete = 1
AND c.archived = 0
AND c2.cID IS NULL
BTW: Using aliases (like c in my example) makes reading and writing your queries easier.
When you restrict on a where clause using the columns in a table that's LEFT joined, you are effectively removing the LEFT OUTER part of the join, because you're filtering on columns that have to be there. Try this instead:
SELECT company, contractors.cID
FROM contractors
LEFT OUTER JOIN contractors2label
ON (contractors.cID = contractors2label.cID
AND contractors2label.labelID <> 1
AND contractors2label.countyID <> 1)
WHERE contractors.complete = 1
AND contractors.archived = 0
AND contractors2label.cID IS NULL
This does the restriction as part of the join, so nulls can still be used in the larger query.