SELECT COUNT(*) GROUP BY out zero rows - sql

Tables in my db:
CREATE TABLE items (
id serial NOT NULL,
user_id integer NOT NULL,
status smallint
);
CREATE TABLE phones (
phone character varying,
users integer[]
);
My Query for find phone numbers where status = 1:
SELECT phones.phone, COUNT(*) AS "count"
FROM phones,items
WHERE phones.phone = ANY (Array['7924445544', '8985545444'])
AND items.user_id = ALL (phones.users) AND items.status = 1
GROUP BY phones.phone;
Query out:
phone | count
------------+------
7924445588 | 3
Need out with ZERO count:
phone | count
------------+-------
8985545444 | 0
7924445544 | 3
How to get that?

It's a bit tricky to create non-existing rows. (There are billions of them, at least...)
Do a UNION ALL with a select providing the result you want if that phone no doesn't exist.
<current query>
UNION ALL
select '8985545444',0
from one_row_table where not exists (select 1 from phones
where phones.phone = '8985545444')
EDIT:
If the phone numbers do exists, but not fulfill the WHERE clause conditions, use a correlated sub-select to do the count:
SELECT phones.phone,
(select count(*) from items
where items.status = 1
and items.user_id = phones.users) as "Count"
FROM phones
WHERE phones.phone = ANY (Array['7924445544', '8985545444'])

You shouldn't do that in the query. However it is rather easy to do it if you want to:
WITH phone_seek AS(
SELECT '8985545444' AS phone
UNION ALL
SELECT '7924445588 '
)
SELECT phone_seek.phone, COUNT(items.id) AS "count"
FROM
phones_seek
JOIN phones
ON phones_seek.phone = phones.phones
CROSS JOIN items
WHERE
items.user_id = ALL (phones.users) AND items.status = 1
GROUP BY phones.phone;

Count is an aggregate function and generally db engines produces count 0 when no row matches the criteria.
The reason why your query produces no results is inclusion of your phones.phone field in db and the group by.
1) A cheap solution to this is select only count(*) as your application
already be knowing the phone number:
SELECT COUNT(*) AS "count"
FROM phones, items
WHERE phones.phone = '8985545444'
AND items.user_id = ALL (phones.users) AND items.status = 1 ;
2) An ideal solution to this would be your application to handle 0 result
returned by db.
3) And if you need db to do all job you may use something like:
SELECT phones.phone, COUNT(*) AS "count"
FROM phones, items
WHERE phones.phone = '8985545444'
AND items.user_id = ALL (phones.users) AND items.status = 1
GROUP BY phones.phone;
UNION ALL
select '8985545444',0
from one_row_table where not exists (select 1 from phones
where phones.phone = '8985545444')

I'm not well versed with PostreSQL's array syntax, but this seems to be a simple outer join:
SELECT phones.phone, COUNT(items.user_id) AS "count"
FROM phones LEFT JOIN items
ON items.user_id = ALL (phones.users)
AND items.status = 1
WHERE phones.phone = ANY (Array['7924445544', '8985545444'])
GROUP BY phones.phone;

Related

How to get all the TOP rows that are equal

I am trying to write a query to answer the question "What Pop Tart flavor is sold in the most stores?"
Here is my schema:
I have this attempt:
SELECT COUNT(*) AS CountOfStores, PTF.PopTartFlavor AS PopTartFlavor
FROM tKrogerStore_PopTartFlavor KSPTF
INNER JOIN tPopTartFlavor PTF
ON KSPTF.PopTartFlavorID = PTF.PopTartFlavorID
GROUP BY PTF.PopTartFlavor
ORDER BY CountOfStores DESC
but the query gives me this:
and I just want the first 2 rows because they both have a count of 2, but if I say TOP 1 I just get the first row with a count of 2 and I don't get both rows that have a count of 2. How do I get both rows that have the same count (2)?
you could try uisng having the count(*) = to the max result
SELECT COUNT(*) AS CountOfStores, PTF.PopTartFlavor AS PopTartFlavor
FROM tKrogerStore_PopTartFlavor KSPTF
INNER JOIN tPopTartFlavor PTF
ON KSPTF.PopTartFlavorID = PTF.PopTartFlavorID
GROUP BY PTF.PopTartFlavor
HAVING COUNT(*) = (
select max(CountOfStores)
from (
SELECT COUNT(*) AS CountOfStores, PTF.PopTartFlavor AS PopTartFlavor
FROM tKrogerStore_PopTartFlavor KSPTF
INNER JOIN tPopTartFlavor PTF
ON KSPTF.PopTartFlavorID = PTF.PopTartFlavorID
GROUP BY PTF.PopTartFlavor
) t
)
I would capture the binned count and the overall max count in the same subquery query (the latter using an aggregated windowed function), then in the outer query filter for when they're equal.
select CountOfStores, PopTartFlavor
from (
select CountOfStores = count(*),
maxCountOfStores = max(count(*)) over(),
PTF.PopTartFlavor
from #tKrogerStore_PopTartFlavor KSPTF
join #tPopTartFlavor PTF ON KSPTF.PopTartFlavorID = PTF.PopTartFlavorID
group by PTF.PopTartFlavor
) c
where countOfStores = maxCountOfStores

Filter Count Clause - One to Many relationship

I currently have two table where a versionLog can contain many ProductVersions.
Following Sql Query:
SELECT
versionlog.[Name],
(
SELECT COUNT(*)
FROM dbo.ProductVersions productVersion
WHERE productVersion.VersionLogId = versionLog.Id
) AS ProductVersions
FROM dbo.Versionlog versionLog
produces a result set similar to
Name | ProductVersions
Log1 | 12
Log2 | 6
Log3 | 0
etc..
How can I limit the result set to only return Versionlogs with a ProductVersion Count = 0?
According to google I'll need to use the Having clause?
Using HAVING:
SELECT versionlog.[Name]
FROM dbo.Versionlog versionLog
LEFT JOIN dbo.ProductVersions productVersion
ON productVersion.VersionLogId = versionLog.Id
GROUP BY versionlog.[Name]
HAVING COUNT(productVersion.VersionLogId) = 0;
And without grouping:
SELECT versionlog.[Name]
FROM dbo.Versionlog versionLog
LEFT JOIN dbo.ProductVersions productVersion
ON productVersion.VersionLogId = versionLog.Id
WHERE productVersion.VersionLogId IS NULL
another way subquery
select * from
(
SELECT
versionlog.[Name],
(
SELECT COUNT(*)
FROM dbo.ProductVersions productVersion
WHERE productVersion.VersionLogId = versionLog.Id
) AS ProductVersions
FROM dbo.Versionlog versionLog
) a where a.ProductVersions=0

Rewrite a query with GROUP BY ALL

Microsoft has deprecated GROUP BY ALL and while the query might work now, I'd like to future-proof this query for future SQL upgrades.
Currently, my query is:
SELECT qt.QueueName AS [Queue] ,
COUNT ( qt.QueueName ) AS [#ofUnprocessedEnvelopes] ,
COUNT ( CASE WHEN dq.AssignedToUserID = 0 THEN 1
ELSE NULL
END
) AS [#ofUnassignedEnvelopes] ,
MIN ( dq.DocumentDate ) AS [OldestEnvelope]
FROM dbo.VehicleReg_Documents_QueueTypes AS [qt]
LEFT OUTER JOIN dbo.VehicleReg_Documents_Queue AS [dq] ON dq.QueueID = qt.QueueTypeID
WHERE dq.IsProcessed = 0
AND dq.PageNumber = 1
GROUP BY ALL qt.QueueName
ORDER BY qt.QueueName ASC;
And the resulting data set:
<table><tbody><tr><td>Queue</td><td>#ofUnprocessedEnvelopes</td><td>#ofUnassignedEnvelopes</td><td>OldestEnvelope</td></tr><tr><td>Cancellations</td><td>0</td><td>0</td><td>NULL</td></tr><tr><td>Dealer</td><td>26</td><td>17</td><td>2018-04-06</td></tr><tr><td>Matched to Registration</td><td>93</td><td>82</td><td>2018-04-04</td></tr><tr><td>New Registration</td><td>166</td><td>140</td><td>2018-03-21</td></tr><tr><td>Remaining Documents</td><td>2</td><td>2</td><td>2018-04-04</td></tr><tr><td>Renewals</td><td>217</td><td>0</td><td>2018-04-03</td></tr><tr><td>Transfers</td><td>296</td><td>245</td><td>2018-03-30</td></tr><tr><td>Writebacks</td><td>53</td><td>46</td><td>2018-04-09</td></tr></tbody></table>
I've tried various versions using CTE's and UNION's but I cannot get result set to generate correctly - the records that have no counts will not display or I will have duplicate records displayed.
Any suggestions on how to make this work without the GROUP BY ALL?
Below is one attempt where I tried a CTE with a UNION:
;WITH QueueTypes ( QueueTypeID, QueueName )
AS ( SELECT QueueTypeID ,
QueueName
FROM dbo.VehicleReg_Documents_QueueTypes )
SELECT qt.QueueName AS [Queue] ,
COUNT ( qt.QueueName ) AS [#ofUnprocessedEnvelopes] ,
COUNT ( CASE WHEN dq.AssignedToUserID = 0 THEN 1
ELSE NULL
END
) AS [#ofUnassignedEnvelopes] ,
CONVERT ( VARCHAR (8), MIN ( dq.DocumentDate ), 1 ) AS [OldestEnvelope]
FROM QueueTypes AS qt
LEFT OUTER JOIN dbo.VehicleReg_Documents_Queue AS dq ON dq.QueueID = qt.QueueTypeID
WHERE dq.IsProcessed = 0
AND dq.PageNumber = 1
GROUP BY qt.QueueName
UNION ALL
SELECT qt.QueueName AS [Queue] ,
COUNT ( qt.QueueName ) AS [#ofUnprocessedEnvelopes] ,
COUNT ( CASE WHEN dq.AssignedToUserID = 0 THEN 1
ELSE NULL
END
) AS [#ofUnassignedEnvelopes] ,
CONVERT ( VARCHAR (8), MIN ( dq.DocumentDate ), 1 ) AS [OldestEnvelope]
FROM QueueTypes AS qt
LEFT OUTER JOIN dbo.VehicleReg_Documents_Queue AS dq ON dq.QueueID = qt.QueueTypeID
GROUP BY qt.QueueName
But the results are not close to being correct:
Your current query doesn't work as it seems to work, because while you outer join table VehicleReg_Documents_Queue you dismiss all outer joined rows in the WHERE clause, so you are where you would have been with a mere inner join. You may want to consider either moving your criteria to the ON clause or make this an inner join right away.
It is also weird that you join queue type and queue not on the queue ID or the queue type ID, but on dq.QueueID = qt.QueueTypeID. That's like joining employees and addresses on employee number matching the house number. At least that's what it looks like.
(Then why does your queue type table have a queue name? Shouldn't the queue table contain the queue name instead? But this is not about your query, but about your data model.)
GROUP BY ALL means: "Please give us all QueueNames, even when the WHERE clause dismisses them. I see two possibilities for your query:
You do want an outer join actually. Then there is no WHERE clause and you can simply make this GROUP BY qt.QueueName.
You don't want an outer join. Then we want a row per QueueName in the table, which we might not get with simply changing GROUP BY ALL qt.QueueName to GROUP BY qt.QueueName.
In that second case we want all QueueNames first and outer join your query:
select
qn.QueueName AS [Queue],
q.[#ofUnassignedEnvelopes],
q.[OldestEnvelope]
FROM (select distinct QueueName from VehicleReg_Documents_QueueTypes) qn
LEFT JOIN
(
SELECT qt.QueueName,
COUNT ( qt.QueueName ) AS [#ofUnprocessedEnvelopes] ,
COUNT ( CASE WHEN dq.AssignedToUserID = 0 THEN 1
ELSE NULL
END
) AS [#ofUnassignedEnvelopes] ,
MIN ( dq.DocumentDate ) AS [OldestEnvelope]
FROM dbo.VehicleReg_Documents_QueueTypes AS [qt]
JOIN dbo.VehicleReg_Documents_Queue AS [dq] ON dq.QueueID = qt.QueueTypeID
WHERE dq.IsProcessed = 0
AND dq.PageNumber = 1
) q ON q.QueueName = qn.QueueName
GROUP BY ALL qn.QueueName
ORDER BY qn.QueueName ASC;
I think the best corollary here for a 'GROUP BY ALL' into something more ANSI compliant would be a CASE statement. Without knowing your data, it's hard to say for sure if this is 1:1, but I'm betting it's in the ballpark.
SELECT qt.QueueName AS [Queue]
,COUNT(CASE
WHEN dq.IsProcessed = 0
AND dq.PageNumber = 1
THEN qt.QueueName
END) AS [#ofUnprocessedEnvelopes]
,COUNT(CASE
WHEN dq.AssignedToUserID = 0
AND dq.IsProcessed = 0
AND dq.PageNumber = 1
THEN 1
ELSE NULL
END) AS [#ofUnassignedEnvelopes]
,MIN(CASE
WHEN dq.IsProcessed = 0
AND dq.PageNumber = 1
THEN dq.DocumentDate
END) AS [OldestEnvelope]
FROM dbo.VehicleReg_Documents_QueueTypes AS [qt]
LEFT OUTER JOIN dbo.VehicleReg_Documents_Queue AS [dq] ON dq.QueueID = qt.QueueTypeID
GROUP BY qt.QueueName
ORDER BY qt.QueueName ASC;
That's a bit uglier because every aggregate has to have the WHERE conditions inside a case statement, but at least you are future proof.

SQL Query to check if student1 has a course with student 2

I have one table and I need to check if two users, for whom I have the IDs (e.g. 20 and 21) share the same course, just true or false.
Table: jos_gj_users
Columns: id_user, id_group
Data Example: (20; 4)
(20; 5)
(20; 6)
(21; 6)
(21; 7)
The data above shows that user 20 and user 21 share the course 6 but how do I get this with SQL just by entering the IDs and without looping through the results with PHP?
Try a self-join:
SELECT T1.id_group
FROM jos_gj_users T1
JOIN jos_gj_users T2
ON T1.id_group = T2.id_group
WHERE T1.id_user = 20
AND T2.id_user = 21
To just get a "true or false" result you can check from the client to see if at least one row exists in the result set rather than fetching the entire results.
Alternatively you can do it in SQL by wrapping the above query in another SELECT that uses EXISTS:
SELECT CASE WHEN EXISTS
(
SELECT T1.id_group
FROM jos_gj_users T1
JOIN jos_gj_users T2
ON T1.id_group = T2.id_group
WHERE T1.id_user = 20
AND T2.id_user = 21
) THEN 1 ELSE 0 END AS result
This query returns either 0 (false) or 1 (true).
The idea is that you have to join the table to itself. In the first half you look for user 1 and in the second half you look for user 2. And of course only those rows that have the same id_group in both half are relevant:
SELECT count(*)
FROM jos_gj_users As j1, jos_gj_users As j2
WHERE j1.id_user = 20 AND j2.id_user = 21
AND j1.id_group = j2.id_group
This will always return one row with one column: The number of shared courses. If it is 0, they don't share any courses.
You could do it with a subselect:
select id_group
from jos_gj_users
where (id_user = 20)
and id_group in (select id_group from jos_gj_users where id_user = 21)
SELECT COUNT(*) > 0 FROM jos_gj_users WHERE id_user=54321 AND id_group IN ( SELECT id_group FROM jos_gj_users WHERE id_user = 1345 )
This is query that shows users from same groups.
SELECT
*
FROM
jos_gj_users T1
INNER JOIN jos_gj_users T2 ON T1.id_group = T2.id_group
Give this a try - it accepts the input parameters in the first bolded area, and returns a value of TRUE or FALSE via a CASE statement based on the values in the second bolded areas.
SELECT DISTINCT CASE WHEN
(SELECT DISTINCT COUNT(id_group) FROM jos_gj_users WHERE id_user IN (20, 21) GROUP BY id_group
HAVING COUNT(DISTINCT id_user) = 2) IS NOT NULL THEN 'TRUE'
ELSE 'FALSE'
END
FROM jos_gj_users

Using (IN operator) OR condition in Where clause as AND condition

Please look at following image, I have explained my requirements in the image.
alt text http://img30.imageshack.us/img30/5668/shippment.png
I can't use here WHERE UsageTypeid IN(1,2,3,4) because this will behave as an OR condition and fetch all records.
I just want those records, of first table, which are attached with all 4 ShipmentToID .
All others which are attached with 3 or less ShipmentToIDs are not needed in result set.
Thanks.
if (EntityId, UsageTypeId) is unique:
select s.PrimaryKeyField, s.ShipmentId from shipment s, item a
where s.PrimaryKeyField = a.EntityId and a.UsageTypeId in (1,2,3,4)
group by s.PrimaryKeyField, s.ShipmentId having count(*) = 4
otherwise, 4-way join for the 4 fields,
select distinct s.* from shipment s, item a, item b, item c, item d where
s.PrimaryKeyField = a.EntityId = b.EntityId = c.EntityId = d.EntityId and
a.UsageTypeId = 1 and b.UsageTypeId = 2 and c.UsageTypeId = 3 and
d.UsageTypeId = 4
you'll want appropriate index on (EntityId, UsageTypeId) so it doesn't hang...
If there will never be duplicates of the UsageTypeId-EntityId combo in the 2nd table, so you'll never see:
EntityUsageTypeId | EntityId | UsageTypeId
22685 | 4477 | 1
22687 | 4477 | 1
You can count matching EntityIds in that table.
WHERE (count(*) in <tablename> WHERE EntityId = 4477) = 4
DECLARE #numShippingMethods int;
SELECT #numShippingMethods = COUNT(*)
FROM shippedToTable;
SELECT tbl1.shipmentID, COUNT(UsageTypeId) as Usages
FROM tbl2 JOIN tbl1 ON tbl2.EntityId = tbl1.EntityId
GROUP BY tbl1.EntityID
HAVING COUNT(UsageTypeId) = #numShippingMethods
This way is preferred to the multiple join against same table method, as you can simply modify the IN clause and the COUNT without needing to add or subtract more tables to the query when your list of IDs changes:
select EntityId, ShipmentId
from (
select EntityId
from (
select EntityId
from EntityUsage eu
where UsageTypeId in (1,2,3,4)
group by EntityId, UsageTypeId
) b
group by EntityId
having count(*) = 4
) a
inner join Shipment s on a.EntityId = s.EntityId