Get total count and filtered count from single subquery - sql

product table
product_id
name
1
t-shirt
2
shirt
users table
user_id
name
1
sphinx
favorite table with a UNIQUE constraint on (user_id, product_id):
user_id
product_id
1
2
What is best way to query this table for user_id = 1
favorite_count: how many users added this product in favorite?
isfavorite: did user with user_id = 1 add this product as favorite?
Expected result:
product_id
product_name
isfavorite
favorite_count
1
t-shirt
false
0
2
shirt
true
1

Typically, it's cheapest to aggregate rows in the n-table (favorite in this case) before the join:
SELECT p.*
, COALESCE(f.is_favorite, false) AS is_favorite
, COALESCE(f.favorite_count, 0) AS favorite_count
FROM product p
LEFT JOIN (
SELECT product_id
, count(*) AS favorite_count -- total count
, bool_or(true) FILTER (WHERE user_id = 1) AS is_favorite -- for given user
FROM favorite
GROUP BY 1
) f USING (product_id);
We don't need the users table for the query.
LEFT JOIN keeps all products in the result, while only adding counts for products that have favorite entries.
About the FILTER clause:
Aggregate columns with additional (distinct) filters
Since not every product has entries in table favorite, use COALESCE() in the outer query to replace resulting NULL values with your defaults.

Related

Aggregate and count after left join

I am aggregating columns of a table to find the count of unique values. For example, aggregating
the status shows that out of 5 alerts there are 2 in open status and 3 that are closed. The simplified table looks like this:
create table alerts (
id,
status,
owner_id
);
The query below uses grouping sets to aggregate multiple columns at once. This approach works well.
with aggs as (
select status
from alerts
where alerts.owner_id = 'x'
)
select status, count(*)
from aggs
group by grouping sets(
(),
(status)
);
the output at its simplest could look like this:
status | count
--------+-------
| 1
new | 1
However, now I need to aggregate additional columns from another table. This table (shown below) can have zero or more rows associated to the first table (alerts:users 1:N).
create table users (
id,
alert_id,
name
);
I have tried updating the query to use a left join but this approach incorrectly inflates the counts of the alert columns.
with aggs as (
select alerts.status, users.name
from alerts
left join users on alerts.id = users.alert_id
where alerts.owner_id = 'x'
-- and additional filtering by columns in the users table
)
select status, name, count(*)
from aggs
group by grouping sets(
(),
(status),
(name)
);
Below is an example of the incorrect results. Since there are 3 rows in the user table the count
for the status column is now 3 but should be 1.
status | name | count
--------+-------------------------+-------
| | 3
| user1 | 1
| user2 | 1
| user3 | 1
new | | 3
How can I perform this aggregation to include the columns from the table with a many-to-one relationship without inflating the counts? In the future I will likely need to aggregate more columns from other tables with a many-to-one relationship and need a solution that will still work with several left joins. All help is much appreciated.
edit: link to db-fiddle https://www.db-fiddle.com/f/buGD2DuJiqf9LGF9rw5EgT/2
Do you just want to count the number of alerts? If so, use count(distinct):
count(distinct alert_id)
Of course, you need this in aggs, so the select would include:
alerts.id as alert_id

SQL Join - get rows that meet criteria from foreign key table

I need to find the rows in one table that meet specific attributes specified in a foreign key table.
I have these two tables:
ITEM
PK: ItemId
Text: nvarchar
ITEMATTRIBUTE
PK: ItemAttributeId
FK: ItemId
AttributeText: nvarchar
Value: int
ItemAttribute has a foreign key to Items and defines dynamic attributes given for an item.
e.g:
ITEM:
ItemId Text
1 ItemA
2 ItemB
3 ItemC
ITEMATTRIBUTE:
ItemAttributeID ItemId AttributeText Value
1 1 AttributeA 10
2 1 AttributeB 10
3 2 AttributeA 8
I need to dynamically find items which have certain itamAttributes.
For example the desired query shall return items that have
AttributeA AND AttributeB: result should show only ItemA.
When I (left) join both tables I get a row for each item with the joined itemattribute:
select * from item i left join itemattribute a on i.itemid=a.itemid
However I need a dynamic way to get items where defined itemattributes are set:
get all items that have AttributeA with a value > 5 and AttributeB with value > 5: result ItemA
As I don't know how many itemAttributes are set I can't do the query with stacked subselects or hardcoded as in:
select distinct i2.itemid from item i2, itemattribute a2,
(select i.itemid, a.attributetext from item i left join itemattribute a on i.itemid=a.itemid where AttributeText='AttributeA' and value > 5) ii
where i2.itemid=a2.itemid and a2.attributetexT='AttributeB' and value > 5
You need to aggregate your values to the Item level, then do the logic to filter for the Items you want.
In some dbs (like Snowflake) you can run a PIVOT, but in most you need to do conditional aggregations.
SELECT item_id, att_a_value, att_b_value
FROM (
SELECT
i.item_id
,max(CASE WHEN ia.AttributeText = 'Attribute A' THEN ia.value END) as att_a_value
,max(CASE WHEN ia.AttributeText = 'Attribute B' THEN ia.value END) as att_b_value
FROM Items i
LEFT JOIN ItemAttributes ia ON i.item_id = ia.item_id
GROUP BY i.item_id
) z
WHERE z.att_a_value > 5 AND z.att_b_value > 5

SQL: Get count of rows for values in another column even when those values do not exist

I have a table named 'products' with two columns: name and status.
I would like to get the count of rows in the table with statuses as Draft, Published and Rejected.
This is the query I tried,
select count(*), status from products where status in ('Draft', 'Published') group by status;
At the moment the table does not have any row with the status as Published or Rejected.
So the above query just returns one row with status and Draft along with its count
count | status
-------+--------
24 | Draft
However, I would like to the query result with the other statuses as zero.
count | status
-------+--------
24 | Draft
0 | Published
0 | Rejected
How should I write the query so that I get the results as above?
You need a list of the statuses and a left join:
select v.status, count(p.status)
from (values ('Draft'), ('Published'), ('Rejected')
) v(status) left join
products p
on p.status = v.status
group by v.status;

Search data in sql on behalf of list of parameters ,

Hi All i am unable to create a query in sql i want to get all employee who's contain the product which i passed in parameter
Ex, i if passed product id 11,12 then its should be return only DeliveryBoyId1 and 2 and if passed the product id 11 then its should be return only DeliveryBoyId 1,2,3 and if i passed productid 11,12,16 then its should return 0 record because there is no delivery boy assigned the product id 11,12,16
I don't know what your table is called so I'm calling it dbo.Delivery. Try this out:
;with CTE as (
select distinct DeliveryBoyId --get all the boys who delivered product 11
from dbo.Delivery
where ProductId = 11
union all --combine the above results with the below
select distinct DeliveryBoyId --get all the boys who delivered product 12
from dbo.Delivery
where ProductId = 12
)
select DeliveryBoyId
from CTE
group by DeliveryBoyId
having count(1) = 2 --get all the boys who appear twice in the above table, once for each product
If you want to match additional products, you can add to the CTE section for other product IDs.
If you want a single solution for any number of products, you can use this instead (which may be a little less readable but more concise and easier to maintain):
select DeliveryBoyId
from (
select distinct DeliveryBoyId, ProductId
from dbo.Delivery
where ProductId in (11, 12)
) s
group by DeliveryBoyId
having count(1) = 2 --number matches number of products you need to match

How do I use a join to query two tables and get all rows from one, and related rows from the other?

Simplified for example, I have two tables, groups and items.
items (
id,
groupId,
title
)
groups (
id,
groupTitle,
externalURL
)
The regular query I'm goes something like this:
SELECT
i.`id`,
i.`title`,
g.`id` as 'groupId',
g.`groupTitle`,
g.`externalURL`
FROM
items i INNER JOIN groups g ON (i.`groupId` = g.`id`)
However I need to modify this now, because all the groups which specify an externalURL will not have any corresponding records in the items table (since they're stored externally). Is it possible to do some sort of join so that the output looks kinda like this:
items:
id title groupId
----------------------
1 Item 1 1
2 Item 2 1
groups
id groupTitle externalURL
-------------------------------
1 Group 1 NULL
2 Group 2 something
3 Group 3 NULL
Query output:
id title groupId groupTitle externalURL
---------------------------------------------------
1 Item 1 1 Group 1 NULL
2 Item 2 1 Group 1 NULL
NULL NULL 2 Group 2 something
-- note that group 3 didn't show up because it had no items OR externalURL
Is that possible in one SQL query?
This is exactly what an outer join is for: return all the rows from one table, whether or not there is a matching row in the other table. In those cases, return NULL for all the columns of the other table.
The other condition you can take care of in the WHERE clause.
SELECT
i.`id`,
i.`title`,
g.`id` as 'groupId',
g.`groupTitle`,
g.`externalURL`
FROM
items i RIGHT OUTER JOIN groups g ON (i.`groupId` = g.`id`)
WHERE i.`id` IS NOT NULL OR g.`externalURL` IS NOT NULL;
Only if both i.id and g.externalURL are NULL, then the whole row of the joined result set should be excluded.