I have some queries that return lists of products for a time period, and I want to find out which products appear in all of those time periods.
WeekEnding | Product
07/07/14 | A
07/07/14 | B
07/07/14 | C
14/07/14 | A
14/07/14 | B
21/07/14 | A
21/07/14 | B
21/07/14 | C
So in the above example data I would have products A, and B which are in all 3 weeks, and I could imagine running a query like
SELECT Product FROM ProductWeek
GROUP BY Product
HAVING COUNT(*) = (
SELECT COUNT(distinct weekending) from ProductWeek )
Unfortunately I am writing this query in MSAccess, so count distinct is not available, but at any rate it feels like there should be a more elegant solution to this problem
You can do it with a subquery:
SELECT Product
FROM ProductWeek
GROUP BY Product
HAVING COUNT(*) = (SELECT COUNT(*) from (SELECT distinct weekending from ProductWeek ) as t);
Related
I have the below table in SQL Access and I want to find out which products have 2 distinct categories.
For example the product abc has only one category, so I don't want it to show up in my query, but product def has both categories, so I want it to show up.
+----+---------+----------+
| ID | Product | Category |
+----+---------+----------+
| 1 | abc | A |
| 2 | abc | A |
| 3 | def | B |
| 4 | def | A |
| 5 | abc | A |
+----+---------+----------+
The answer ultimately depends on whether you are looking for products which are assigned to more than one category, or exactly 2 categories as you state in your question:
I want to find out which products have 2 distinct categories.
For the latter, you might use something like the following:
select t.product
from (select distinct product, category from YourTable) t
group by t.product
having count(*) = 2
For the former, there are many possible options - you can simply change the equality operator = in the above query to a greater than or equal to operator >= yielding:
select t.product
from (select distinct product, category from YourTable) t
group by t.product
having count(*) >= 2
Or you could use a where exists clause to test whether there exists at least one other record for the same product assigned to a different category:
select distinct t.product
from YourTable t
where exists
(select 1 from YourTable u where u.product = t.product and u.category <> t.category)
Or you could use aggregation with a min/max test within the having clause, as per query suggested by #forpas.
In all of the above examples, change YourTable to the name of your table.
Access does not support COUNT(DISTINCT ...) so for your sample data a HAVING clause where you set the condition that the minimum Category is different than the maximum Category will do:
select Product
from tablename
group by Product
having min(Category) <> max(Category)
I wonder if there is a way to build a query without joins or/and having clause that would return the same result as the query below? I already found similar question (select and count rows) but didn't find the answer.
SELECT ID, CATEGORY, PRODUCT, DESC
FROM SALES s
JOIN (SELECT ID, COUNT(CATEGORY)
FROM SALES
GROUP by ID
HAVING count(CATEGORY)=1) S2 ON S.ID=S2.ID;
So the table looks like
ID | Country | Product | DESC
1 | USA | Cream | Super cream
1 | Canada | Toothpaste| Great Toothpaste
2 | Germany | Beer | Tasty Beer
and the result I would like to get is
ID | Country | Product | DESC
2 | Germany | Beer | Tasty Beer
because id=1 has 2 different countries assigned
I'm using SQL Server
In general I'm interested in the 'fastest' solution. The table is huge and I just wonder if there is a way to do it smarter.
you may want to consider this query.
select t2.id, t2.category, t2.product, t2.desc from (
select id, category, product,
case when (select count(1) from sales where id=t1.id group by id) as ct
,desc
from sales t1) t2 where t2.ct = 1
You can try this Query:
SELECT ID, CATEGORY, PRODUCT, DESC
FROM SALES s
WHERE 1 = (
SELECT COUNT(*)
FROM SALES x
WHERE x.ID = s.ID
);
One method uses window functions:
SELECT ID, CATEGORY, PRODUCT, DESC
FROM (SELECT s.*, COUNT(*) OVER (PARTITION BY ID) as cnt
FROM SALES s
) s
WHERE cnt = 1;
However, the fastest solution would require a unique id and an index. That would be:
select s.*
from sales s
where not exists (select 1
from sales s2
where s2.id = s.id and
s2.<unique key> <> s.<unique key>
);
This can take advantage of an index on (id, <unique key>).
Note: This particular formulation assumes that category is never null.
I am attempting to build a query that reduces a GROUP BY group to a single row, including a value for a column based on the max value of another column. In this case, I want an item id, total qty ordered and most-used supplier.
I've successfully built a query that sums the qty ordered and groups by item and supplier, yielding:
| id | qty | supplier |
| 1 | 20 | S&S Activewear |
| 1 | 10 | J&J Textiles |
| 2 | 5 | AB Footwear |
| 2 | 10 | CD Shoes |
and the intended result would be total qty ordered (for all suppliers) and most used supplier, so:
| id | total_qty | most_used_supplier |
| 1 | 30 | S&S Activewear |
| 2 | 15 | CD Shoes |
Conceptually, I imagine doing a subquery, grouping the above results by id alone, then sum(qty) and somehow choose the supplier value by ranking the GROUP BY by qty.
I have read many related posts but I am failing to apply any of those methods successfully to this end, including use of ROW_NUMBER and PARTITION_BY.
I am doing this in Elixir with Ecto on a Postgres DB, but to keep it generalized so anyone can respond, I am just looking to understand how this would be done in SQL. Please let me know if I can provide more detail, thank you.
There are several approaches and it sounds like you've played with this one a bit even:
with data as (
select *,
row_number() over (partition by id order by qty desc) as rn
from T
)
select id, sum(qty) as total_qty,
(select d2.supplier from data d2
where d2.id = d.id and rn = 1) as most_used_supplier
from data d
group by id;
I'm going to suggest multiple subqueries:
select id, sum(qty),
(select t2.supplier
from t t2
where t2.id = t.id
order by t2.qty desc
fetch first 1 row only
) as supplier
from t
group by id;
This uses standard syntax for returning one row. Your database may have another syntax for the equivalent of fetch first 1 row only.
First find biggest quantities for each id.
Then find appropriate suppliers which provide those biggest quantities. Here issue may appear if there are more then one "biggest", and you have to see how to deal with it.
Finally, just join it once more to same table, adding appropriate quantity sums.
SELECT item.id, sum(item.qty) total_qty, biggestSupplier.supplier most_used_supplier
from item join
(
SELECT item.id, supplier
from item
JOIN
(
SELECT id, max(qty) maxqty
FROM item
GROUP BY id
) maxQtyForId ON item.id = maxQtyForId.id AND item.qty = maxQtyForId.maxqty
) biggestSupplier ON item.id = biggestSupplier.id
group by item.id, biggestSupplier.supplier
I Divide the problem in 2. First, finding the max qty and then adding up the qty. Finally, Join the table to get the answers.
SELECT T4.ID, T5.sumQty AS total_qty,T4.supplier AS most_used_supplier
FROM [Test].[dbo].[Test] AS T4 LEFT JOIN
(
SELECT ID,SUM(QTY) as sumQty
FROM [Test].[dbo].[Test]
GROUP BY ID
)AS T5
ON T4.ID = T5.ID
WHERE supplier IN
(
SELECT supplier
FROM [Test].[dbo].[Test] AS T1 LEFT JOIN
(
SELECT MAX(qty) AS maxQty, ID
FROM [Test].[dbo].[Test] AS T
GROUP BY id
) AS T2
ON T1.ID = T2.ID
AND T1.qty = T2.maxQty
WHERE T2.ID IS NOT NULL
)
I solemnly swear I did my best to find an existing question, may I'm not sure how to phrase it correctly.
I would like to return records for users that have quota for only one product type.
| user_id | product |
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | B |
| 3 | B |
| 3 | C |
| 3 | D |
In the example above I'd like a query that only returns users who carry quota for only one product type - doesn't really matter which product at this point.
I tried using select user_id, product from table group by 1,2 having count(user) < 2 but this does not work, nor does select user_id, product from table group by 1,2 having count(*) < 2
Any help is appreciated.
Your having clause is good; the issue's with your group by. Try this:
select user_id
, count(distinct product) NumberOfProducts
from table
group by user_id
having count(distinct product) = 1
Or you could do this; which is closer to your original:
select user_id
from table
group by user_id
having count(*) < 2
The group by clause can't take ordinal arguments (like, e.g., the order by clause can). When grouping by a value like 1, you're in fact grouping by the literal value 1, which would just be the same for any row in the table, and thus will group all the rows in the table to one group. Since there are more than one product in the entire table, no rows will be returned.
Instead, you should group by the user_id:
SELECT user_id
FROM mytable
GROUP BY user_id
HAVING COUNT(*) = 1
If you want the product, then do:
select user_id, max(product) as product
from table
group by user_id
having min(product) = max(product);
The having clause could also be:
having count(distinct product) = 1
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
Suppose I have a Table such that:
|ID | product |orderid | brand |number of product cust ord|
|----|---------|--------|-------|--------------------------|
| 1 | 123 | 111 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 1 | 234 | 111 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 1 | 345 | 333 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 2 | 123 | 211 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 2 | 456 | 212 | br | 2 |
|----|---------|--------|-------|--------------------------|
| 3 | 567 | 213 | br | 1 |
|----|---------|--------|-------|--------------------------|
What I'd like to do is group them as:
|ID | brand |number of product cust ord|
|----|---------|--------------------------|
| 1 | br | 3 |
|----|---------|--------------------------|
| 2 | br | 4 |
|----|---------|--------------------------|
further to that i'd like to classify them and tried a case...when but can't seem to get it right.
if ID purchases more than 3 unique products and orders more than twice- i'd like to call them a frequent buyer (in the above example, ID '1' would be a 'frequent buyer'), if the average number of products they purchase is higher than the average number of that product sold - i'd like to call them a 'merchant', else just a purchaser.
I've renamed the last field to qty for brevity and called the table test1.
To get frequent flyers use below query. Note that I used >= instead of >. I changed this based on your example where ID 1 is a "frequent flyer" even though he only bought 3 products, not more than 3.
SELECT ID, count(distinct product) as DistinctProducts, count(distinct orderid) DistinctOrders
FROM test1
GROUP BY ID
HAVING count(distinct product) >= 3 and count(distinct orderid) >= 2
Not sure if I understood the merchant logic correctly. Below is the query which will give you customers that on average purchased more than overall average of product for any given product. There are none in the data.
SELECT DISTINCT c.ID
FROM
(select ID, product, avg(qty) as AvgQty
FROM test1
GROUP BY ID, product) as c
FULL OUTER JOIN
(select product, avg(qty) as AvgQty
FROM test1
GROUP BY product) p ON p.product = c.product
WHERE c.AvgQty > p.AvgQty;
To get "purchasers" do EXCEPT between all customer and the UNION of merchants and frequent buyers:
select distinct ID from test1
EXCEPT
(SELECT ID FROM (
select ID, count(distinct product) as DistinctProducts, count(distinct orderid) DistinctOrders
FROM test1
GROUP BY ID
HAVING count(distinct product) >= 3 and count(distinct orderid) >= 2) t
UNION
SELECT DISTINCT c.ID
FROM
(select ID, product, avg(qty) as AvgQty
FROM test1
GROUP BY ID, product) as c
FULL OUTER JOIN
(select product, avg(qty) as AvgQty
FROM test1
GROUP BY product) p ON p.product = c.product
WHERE c.AvgQty > p.AvgQty
);
This is one way that you could do it. Note that according to the description you gave, buyers could be constantly being reclassified between 'Merchant' and 'Purchaser' as the average goes up and down. That might not be what you want.
With cte As (
Select ID,
Brand,
DistinctOrders = Count(Distinct OrderID), -- How many separate orders by this customer for the brand?
DistinctProducts = Count(Distinct Product), -- How many different products by this customer for the brand?
[number of product cust ord] = Sum(CountOfProduct), -- Total number of items by this customer for the brand.
AverageCountOfProductPerBuyer =
Sum(Sum(CountOfProduct)) Over () * 1.0 / (Select Count(*) From (Select Distinct ID, Brand From #table) As tbl)
-- Average number of items per customer (for all customers) for this brand
From #table
Group By ID, Brand)
Select ID, Brand, DistinctOrders, DistinctProducts, [number of product cust ord],
IsFrequentBuyer = iif(DistinctOrders > 1 And DistinctProducts > 2, 'Frequent Buyer', NULL),
IsMerchant = iif(AverageCountOfProductPerBuyer < [number of product cust ord], 'Merchant', 'Purchaser')
From cte;
This query could be written without the common-table expression, but was written this way to avoid defining expressions multiple times.
Note that I have the first ID as a 'Frequent Buyer' based on your description, so I'm assuming that when you say 'more than 3 unique products' you mean 3 or more. Likewise with two or more distinct orders.