Multiple counts and group by - sql

My table looks like this:
ID ProductName ProductCode
1 abc 123
2 abc 123
3 abc 456
4 def 789
5 ghi 246
6 jkl 369
7 jkl 369
8 jkl 369
9 jkl 468
10 jkl 468
And I wish to create a summary table that looks like this:
ProductName ProductCode Total
abc 123 2
abc 456 1
jkl 369 3
jkl 468 2
In other words I'm not interested in Products "def" and "ghi" because they only appear once in the original table. For everything else I want to do a group by ProductName and ProductCode and display counts.
I've tried playing about with group by clauses and where in (select...) but I've just ended up going round in circles.
The table has around 50,000 rows and is on SQL Server 2008 R2.

This is it:
SELECT
ProductName,
ProductCode,
COUNT(*) as Total
FROM Table1
WHERE ProductName IN (SELECT ProductName FROM Table1 GROUP BY ProductName HAVING COUNT(*) > 1)
GROUP BY ProductName, ProductCode
http://www.sqlfiddle.com/#!3/c79ad/9

To filter on an aggregate you need to use the HAVING clause:
SELECT
ProductName,
ProductCode,
COUNT(*) as Total
FROM T
GROUP BY ProductName, ProductCode
HAVING COUNT(*) > 1

;WITH cte AS
(
SELECT ID, ProductName, ProductCode,
COUNT(*) OVER (PARTITION BY ProductName, ProductCode) AS Total,
COUNT(*) OVER (PARTITION BY ProductName) AS PCount,
ROW_NUMBER() OVER (PARTITION BY ProductName, ProductCode ORDER BY ID) AS rn
FROM Products
)
SELECT ID, ProductName, ProductCode, Total
FROM cte
WHERE PCount > 1 AND rn = 1

You can't use WHERE on a column that you are aggregating on. You need to use HAVING instead.
SELECT ProductName,
ProductCode,
COUNT(*) AS Total
FROM Products p
GROUP BY p.ProductName,
p.ProductCode
HAVING COUNT(p.ProductName) > 1

Related

How to show the count of all items in cross joined table in SQL Server

I have a table that has all Items in the inventory, table called CI
CI has 2 columns (ProdID and Price), and it looks like this
ProdID Price
-------------
A8373 700
G8745 900
J7363 300
K7222 800
Y6311 350
I have another table for documents called Docs with columns DocID, CustID and InvoiceID.
DocID, CustID, InvoiceID
------------------------
1 1001 751
2 1001 752
3 1001 753
4 1002 831
5 1002 832
6 1003 901
7 1003 902
Another table for purchases called Purchase with DocID, ProdID, ProdSize.
In the same invoice, ProdID can be repeated as it can be in different sizes
DocID, ProdID, ProdSize
------------------------
1 A8373 41
1 A8373 42
1 A8373 43
1 G8745 35
1 G8745 36
2 A8373 44
2 A8373 45
Now I want to get the quantity of of products for all customer and invoice, but for highest priced products
So it should be like this
CustID, InvoiceID, ProdID, Quantity
-----------------------------------
1001 751 A8373 3
1001 751 G8745 2
1001 751 K7222 0
1001 752 A8373 2
1001 752 G8745 0
1001 752 K7222 0
and to show 0 for the products that do not exist in that invoice
I wrote this query, but it is extremely slow. I wonder if there is an easier fast way to get this results
DECLARE #Features AS TABLE
(
CustID varchar(100),
InvoiceID varchar(100)
INDEX IX3 CLUSTERED(CustID, InvoiceID),
ProdID varchar(100),
Quantity bigint
)
INSERT INTO #Features (CustID, InvoiceID, ProdID, Quantity)
SELECT
R.CustID, R.InvoiceID, T.ProdID, COUNT(*) AS Quantity
FROM
Docs R
CROSS JOIN
(SELECT TOP 1000 * FROM CIs ORDER BY Price DESC) C
INNER JOIN
Purchase T ON T.DocID = R.DocID
GROUP BY
R.CustID, R.InvoiceID, T.ProdID
SELECT TOP 100 *
FROM #Features
ORDER BY CustID, InvoiceID, ProdID
SELECT COUNT(*) FROM #Features
UPDATE F
SET Quantity = Cnt
FROM #Features F
INNER JOIN
(SELECT R.CustID, R.InvoiceID, COUNT(*) Cnt
FROM Purchase T
INNER JOIN Docs R ON T.DocID = R.DocID
GROUP BY R.CustID, R.InvoiceID ) X ON F.CustID = X.CustID
AND F.InvoiceID = X.InvoiceID
SELECT * FROM #Features
here is a way to do this. I filter out the 1000 products first and then perform the join as follows..
Also there isn't a need for update query, all could be obtained in the SQL itself.
Filter early join late
with top_product
as (select prodid,price, rownumber() over(order by price desc) as rnk
from ci
)
,invoice_product
as(select d.docid,d.custid,d.invoiceid,p.prodid
from top_product
join docs d
on 1=1
and rnk<=1000
)
select a.CustID, a.InvoiceID, a.ProdID,count(b.prodid) as qty
from invoice_product a
left join purchase b
on a.DocID=b.docid
and a.ProdID=b.prodid
group by a.CustID, a.InvoiceID, a.ProdID
You can use the DENSE_RANK as follows:
select CustID, InvoiceID, ProdID, sum(qty) as qty
from (select d.CustID, d.InvoiceID, ci.ProdID, p.prodid as qty,
dense_rank() over (order by ci.price desc) as rn
from ci cross join docs d
left join purchase p on d.docid = p.docid and ci.prodid = p.prodid) t
where rn <= 1000
group by CustID, InvoiceID, ProdID
Can you please try following SQL Select statement where I used Common Table Expression SQL CTEs
with topproducts as (
select top 3 ProdID from CI order by Price desc
), sales as (
select
CustID,
InvoiceID,
ProdId,
count(ProdId) as cnt
from (
select
d.CustID,
d.InvoiceID,
p.ProdId
from Docs d
inner join Purchase p
on p.DocID = d.DocID
where p.ProdId in (select ProdId from topproducts)
) t1
group by
CustID,
InvoiceID,
ProdId
)
select
t.*, isnull(ss.cnt,0) as Qty
from (
select
distinct s.CustID, s.InvoiceID, p.ProdId
from sales s, topproducts p
) t
left join sales ss on ss.InvoiceID = t.InvoiceID and ss.ProdId = t.ProdId

Oracle SQL - Get records with latest date on non-unique data

Following is the example of records in the table -
ITEM_NAME STORAGE_CODE STOCK DATE
ABC 2233 170 27/09/2017
ABC 2233 270 15/09/2017
DEF 2233 120 23/09/2017
DEF 2233 110 11/09/2017
GHI 2233 50 15/09/2017
Expected result:
ITEM_NAME STORAGE_CODE STOCK DATE
ABC 2233 170 27/09/2017
DEF 2233 120 23/09/2017
GHI 2233 50 15/09/2017
I've tried using the below query:
Select ITEM_NAME, STORAGE_CODE, STOCK, MAX(DATE)
FROM ITEM_TABLE
WHERE ITEM_NAME IN ('ABC','DEF','GHI' .........)
GROUP BY ITEM_NAME, STORAGE_CODE, STOCK
This didn't work as the stock value is not unique.
Please note: I'm using ITEM_NAME IN (), because I need the output for some specific items.
You can add a select top and order by to your query if you just want to get the max date, like this:
SELECT TOP 1
ITEM_NAME,
STORAGE_CODE,
STOCK,
MAX(DATE)
FROM ITEM_TABLE
WHERE ITEM_NAME IN('ABC', 'DEF', 'GHI')
GROUP BY ITEM_NAME,
STORAGE_CODE,
STOCK
ORDER BY DATE DESC
EDIT: Using row_num
SELECT T.ITEM_NAME,
T.STORAGE_CODE,
T.STOCK,
T.DATE
FROM
(
SELECT ITEM_NAME,
STORAGE_CODE,
STOCK,
DATE,
ROW_NUMBER() OVER(PARTITION BY ITEM_NAME,
STORAGE_CODE ORDER BY DATE DESC) AS part
FROM ITEM_TABLE
WHERE ITEM_NAME IN('ABC', 'DEF', 'GHI')
) T
WHERE part = 1;
I think a query like this should work:
select
*
from (
select *,
row_number() over (partition by ITEM_NAME, STORAGE_CODE order by DATE desc) as seq
from ITEM_TABLE
where ITEM_NAME in ('ABC','DEF','GHI' .........)
) t
where seq = 1
You can use Oracle row_number() over(partition by) like following
select ITEM_NAME
, STORAGE_CODE
, STOCK
, DATE
from
(
select ITEM_NAME
, STORAGE_CODE
, STOCK
, DATE
, row_number() over(partition by ITEM_NAME order by DATE desc) as rn
from ITEM_TABLE
) s
where rn = 1
One way could be to get the max value from DATE1 column in inner query and corresponding stock value in outer query using join as below.
SELECT t.*
,t1.STOCK
FROM (
SELECT ITEM_NAME
,STORAGE_CODE
,MAX(DATE1) AS DATE1
FROM table1
GROUP BY ITEM_NAME
,STORAGE_CODE
ORDER BY ITEM_NAME
) t
INNER JOIN table1 t1 ON t.ITEM_NAME = t1.ITEM_NAME
AND t.STORAGE_CODE = t1.STORAGE_CODE
AND t.DATE1 = t1.DATE1
Result:
ITEM_NAME STORAGE_CODE DATE1 STOCK
------------------------------------------------------
ABC 2233 27.09.2017 00:00:00 170
DEF 2233 23.09.2017 00:00:00 120
GHI 2233 15.09.2017 00:00:00 50
DEMO

Find all products were purchased along with specific item

I want to write a query to find all products that were purchased along with specific item such as coffee in my Order table, I have an order table as below:
OrderID ItemCode ItemName Price
-------------------------------------------------------------------
1000001 100 Apple 5
1000001 101 Salad 15
1000001 102 Coffee 5.5
1000002 110 Bread 2.5
1000002 120 Banana 7.5
1000003 105 Meat 115
1000003 108 Fish 75
1000004 115 Cake 3.5
1000004 102 Coffee 5.5
1000004 144 CupCake 10
So how am suppose to get the result, keeping in mind OrderID such as "1000001" is one order and so on?!
Here is one way to do it..
Select * from
(
Select *,
cofExistence = max(case when ItemName = 'Coffee' then ItemName end)
Over(Partition by OrderID)
from yourtable
) a
where cofExistence = 'Coffee'
My first thought is a self join:
select tother.itemName, count(*) as NumOrders
from t join
t tother
on t.orderid = tother.orderid and
t.itemName = 'Coffee' and
tother.itemName <> 'Coffee'
group by tother.itemName
order by count(*) desc;
For a single product, you can do the same thing using window functions:
select t.itemName, count(*) as NumOrders
from (select t.*,
max(case when itemName = 'Coffee' then 1 else 0 end) as hasCoffee
from t
) t
where t.itemName <> 'Coffee' -- probably not interested in coffee in the output
group by t.itemName
order by count(*) desc;
The self join generalizes more easily to more than one product.
Another option (just for fun) is a dynamic pivot.
Example
Declare #Fetch varchar(100) = 'Coffee'
Declare #SQL varchar(max) = '
Select *
From (
Select OrderID -- << Remove if you want a 1 line Total
,ItemName
,Value = 1
From YourTable A
) A
Pivot (Sum([Value]) For [ItemName] in (' + Stuff((Select Distinct ','+QuoteName(ItemName)
From YourTable
Where OrderID in (Select Distinct OrderID from YourTable Where ItemName =#Fetch)
Order By 1
For XML Path('')),1,1,'') + ') ) p
Where '+quotename(#Fetch)+' is not null
'
Exec(#SQL);
--Print #SQL
Returns

SQL group by with NULL

I have a table something like this:
ID ProductID ProductName Price
== ========= =========== =====
1 XX1 TShirt 10
2 XX1 TShirt 10
3 NULL TShirt 10
4 XX2 Shirt 20
5 XX3 Shirt1 30
Now I want this to group by ProductName and results will be as follows
ID ProductID ProductName Price
== ========= =========== =====
1 XX1 TShirt 30
4 XX2 Shirt 20
5 XX3 Shirt1 30
Thanks
ProductID seems to be irrelevant for the group, so don't use it. To get all columns you could use a CTE and a ranking function like ROW_NUMBER:
WITH CTE AS(
SELECT ID,
ProductID,
ProductName,
Price = SUM(Price) OVER (PARTITION BY ProductName),
RN = ROW_NUMBER() OVER (PARTITION BY ProductName ORDER BY ID)
FROM dbo.TableName
)
SELECT CTE.* FROM CTE
WHERE RN = 1
If you want to take the row which contains the ProductID(where it is not NULL) modify the ORDER BY:
WITH CTE AS(
SELECT ID,
ProductID,
ProductName,
Price = SUM(Price) OVER (PARTITION BY ProductName),
RN = ROW_NUMBER() OVER (PARTITION BY ProductName
ORDER BY CASE WHEN ProductID IS NOT NULL
THEN 0 ELSE 1 END, ID)
FROM dbo.TableName
)
SELECT CTE.* FROM CTE
WHERE RN = 1

Limit number of occurances in output group-by sql query

I have this query
select rep, companyname,count(companyname) as [count], Commission from customers
group by repid,companyname,Commission
It returns lets say
rep companyname count commision
1 ABC 1 10%
2 XYZ 2 10%
2 XYZ 1 20%
3 JKL 4 10%
3 JKL 1 30%
Desire output is
rep companyname count commision
2 XYZ 2 10%
2 XYZ 1 20%
3 JKL 4 10%
3 JKL 1 30%
I would like to have an output so that I show the only those companies who are repeated twice or more in the result. How do I modify the above query. I made the query simple (remove where clause).
I would use a subquery to get the non-unique company names like this.
select rep, companyname,count(companyname) as [count], Commission from customers
where companyname in (
select c1.companyname from customers c1
group by c1.companyname having count(*) >= 2
)
group by repid,companyname,Commission
I think this will match your requirements. I couldn't think of a way of doing it without some sort of sub query or CTE:
select
rep, companyname, [count], commission
from (
select
rep, companyname,count(companyname) as [count], Commission,
count(1) over (PARTITION by companyname) as [companycount]
from customers
group by repid,companyname,Commission
) sub
where companycount > 1
select rep
, companyname
, count(*) as [count] --- equivalent to count(companyname)
, Commission
from customers c
where exists
( select *
from customers c2
where c2.companyname = c.companyname
and ( c2.repid <> c.repid
or c2.Commission <> c.Commission
)
and ( extra-conditions )
)
and ( extra-conditions )
group by repid, companyname, Commission
Add a HAVING clause after your group by, e.g. HAVING count(companyName) > 1
You're looking for the HAVING keyword, which is essentially a WHERE condition for your GROUP BY
select rep, companyname,count(companyname) as [count], Commission from customers
group by repid,companyname,Commission
having count(companyname) > 1