Grouping in SQL Table [closed] - sql

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.

Related

Select row with smallest number on multiple groups of same ids

I have the following table as an output from a sql statement
user | product | price
…
123 | 12 | 451.29
373 | 12 | 637.28
623 | 12 | 650.84
672 | 16 | 356.87
123 | 16 | 263.90
…
Now I want to get only the row with the smallest price for each product_id
THE SQL is fairly easy
SELECT user, product, price
FROM t
WHERE product IN (
SELECT product_id
FROM p
WHERE typ LIKE 'producttyp1'
)
)
but adding MIN(price) does not work how it usually do. I think its because there are several groups of the same product_ids in the same table. Is there an easy to use solution or do I have to rewrite the whole query?
Edit: when I delete user from the query I can get the product and the smallest price:
12 | 451.29
16 | 263.90
But now I would have to join the user, which I am trying to avoid.
You can use row_number():
select p.*
from (select p.*,
row_number() over (partition by product order by price asc) as seqnum
from p
) p
where seqnum = 1;

SQL Access QUERY to show duplicate

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)

Finding a percentage per row of a grouped table

I have a table containing orders of many different products. Currently I can count the number of orders per product ID using a query like:
SELECT ProductId, COUNT(*) as NumOrders from Orders
GROUP BY ProductId;
This gives a table listing all product id's and the number of orders of that product. I can also count the number of orders that fit a certain criteria by adding a simple where clause:
SELECT ProductId, COUNT(*) as NumCriteria from Orders
WHERE HasCriteria = 1
GROUP BY ProductId;
This gives me a similar table of product id's and number of orders of that product, but smaller number due to not all orders fitting that criteria.
What I need to do is run both counts in a single query so that it displays both the number of orders that fit the criteria and the total number of orders per product ID, then also calculate the percentage of the orders that fit the criteria per product ID. Essentially something like this:
SELECT Product ID,
COUNT( *meets criteria* ),
COUNT( *total* ),
( COUNT( *meets criteria* ) * 100 / COUNT( *total* ) )
So that my output gives me something like this:
ProductID | NumCriteria | NumOrders | Percent
0001 | 5 | 10 | 50
0002 | 4 | 20 | 20
0003 | 2 | 6 | 33
So far all methods I've seen to calculate a percentage of something are for counting what percentage of rows in a table meet a criteria, but I need the percentage per each different product ID in the table. Is there any way to do this in one query?
EDIT: The raw table is orders containing and order id, product id, and customer information. The same products show up in multiple orders. A brief example would be:
Order ID | Product ID | Other info...
001 | 0002 | ...
002 | 0002 | ...
003 | 0001 | ...
004 | 0003 | ...
005 | 0001 | ...
006 | 0002 | ...
007 | 0004 | ...
So the result of the first query on this table would be:
Product ID | NumOrders
0001 | 2
0002 | 3
0003 | 1
0004 | 1
Something similar to below may get what you are looking for. When the criteria isnt met for some productID's then the row will appear null in the column.
SELECT
A.Product ID,
B.NumCriteria,
A.count(*) as total,
((B.NumCriteria*100)/A.count(*))
FROM
Orders as A
LEFT JOIN (SELECT
ProductID,
count(*) as NumCriteria
FROM
Orders
WHERE
HasCriteria = 1
GROUP BY ProductID) as B
ON A.ProductID = B.ProductID
GROUP BY ProductID
Could be this using cros join with a temp table for getting the total
Could be this using iiner join with a temp table for getting the total
SELECT a.`Product ID`
, COUNT(a*) AS `count`
, t.cnt as Total
FROM Orders a
INNER JOIN ( select `Product ID`, count(distinct Order ID) as total
from orders
group by `Product ID`) t on a.`Product ID` = t.`Product ID`
GROUP BY a.Product ID`

Select columns with and without group by

Having Table1
id | productname | store | price
-----------------------------------
1 | name a | store 1 | 4
2 | name a | store 2 | 3
3 | name b | store 3 | 6
4 | name a | store 3 | 4
5 | name b | store 1 | 7
6 | name a | store 4 | 5
7 | name c | store 3 | 2
8 | name b | store 6 | 5
9 | name c | store 2 | 1
I need to get all columns but only the rows with the
lowest price.
Result needed:
id | productname | store | price
-----------------------------------
2 | name a | store 2 | 3
8 | name b | store 6 | 5
9 | name c | store 2 | 1
My best try is:
SELECT ProductName, MIN(Price) AS minPrice
FROM Table1
GROUP BY ProductName
But then I need the ID and STORE for each row.
Try this
select p.* from Table1 as p inner join
(SELECT ProductName, MIN(Price) AS minPrice FROM Table1 GROUP BY ProductName) t
on p.productname = t.ProductName and p.price = t.minPrice
Select ID,ProductName,minPrice
from
(
SELECT ProductName, MIN(Price) AS minPrice
FROM Table1
GROUP BY ProductName
) t
join Table1 t1 on t.ProductName = t1.ProductName
You didn't mention your SQL dialect, but most DBMSes support Standard SQL's "Windowed Aggregate Functions":
select *
from
( select t.*,
RANK() OVER (PARTITION BY ProductName ORDER BY Price) as rnk
from table1 as t
) as dt
where rnk = 1
If multiple stores got the same lowest price all of them will be returned. If you want only a single shop you have to switch to ROW_NUMBER instead of RANK or add column(s) to the ORDER BY.
I think this query should do:
select min(t.id) id
, t.productname
, t.price
from table1 t
join
( select min(price) min_price
, productname
from table1
group
by productname
) v
on v.productname = t.productname
and v.price = t.min_price
group
by t.productname
, t.price
It determines the lowest price per product and fetches every line in the base table (t). This avoids duplicates by grouping on the productname and selecting the lowest id.
This should work for you:
SELECT * FROM `Table1` AS `t1`
WHERE (
SELECT count(*) FROM `Table1` AS `t2` WHERE `t1`.`productName` = `t2`.`productName` AND `t2`.`price` < `t1`.`price`) < 1
Check SqlFiddle
But if you have same products with same minimum price in two stores, you will get both of them in result output

Select row that has max total value SQL Server

I have the following scheme (2 tables):
Customer (Id, Name) and
Sale (Id, CustomerId, Date, Sum)
How to select the following data ?
1) Best customer of all time (Customer, which has Max Total value in the Sum column)
For example, I have 2 tables (Customers and Sales respectively):
id CustomerName
---|--------------
1 | First
2 | Second
3 | Third
id CustomerId datetime Sum
---|----------|------------|-----
1 | 1 | 04/06/2013 | 50
2 | 2 | 04/06/2013 | 60
3 | 3 | 04/07/2013 | 30
4 | 1 | 03/07/2013 | 50
5 | 1 | 03/08/2013 | 50
6 | 2 | 03/08/2013 | 30
7 | 3 | 24/09/2013 | 20
Desired result:
CustomerName TotalSum
------------|--------
First | 150
2) Best customer of each month in the current year (the same as previous but for each month in the current year)
Thanks.
Try this for the best customer of all times
SELECT Top 1 WITH TIES c.CustomerName, SUM(s.SUM) AS TotalSum
FROM Customer c JOIN Sales s ON s.CustomerId = c.CustomerId
GROUP BY c.CustomerId, c.CustomerName
ORDER BY SUM(s.SUM) DESC
One option is to use RANK() combined with the SUM aggregate. This will get you the overall values.
select customername, sumtotal
from (
select c.customername,
sum(s.sum) sumtotal,
rank() over (order by sum(s.sum) desc) rnk
from customer c
join sales s on c.id = s.customerid
group by c.id, c.customername
) t
where rnk = 1
SQL Fiddle Demo
Grouping this by month and year should be trivial at that point.