Count Customers based on item master - sql

I need you to help me on writing two queries in SQL Server 2008 that shows the following information based on item master:
Brand wise count on customer master plus customer who purchased the brand
Item Wise count of customer master plus customer who purchased the item
Here the link that shows the table information and the query which I tried.
Click here to view the table in SQL Fiddle
SELECT
brandname,
division,
route,
DivisionTotalCustomersCount = MAX(DivisionTotalCustomersCount),
RouteTotalCustomersCount = MAX(RouteTotalCustomersCount),
PurchasedCustomersCount = SUM(PurchasedCustomersCount)
FROM
(SELECT
i.brandname,
c.division,
c.route,
DivisionTotalCustomersCount =
(SELECT COUNT(distinct x.CustomerID)
FROM CustomerMaster x
WHERE x.division = c.division),
RouteTotalCustomersCount =
(SELECT COUNT(distinct x.CustomerID)
FROM CustomerMaster x
WHERE x.Route = c.route),
PurchasedCustomersCount = count(distinct C.CustomerID)
FROM CustomerMaster c
LEFT OUTER JOIN SalesData s on c.CustomerID = s.CustomerID
right outer join ItemMaster i on s.item = i.itemcode
GROUP BY i.brandname, c.division, c.route) A
GROUP BY
brandname, division, route
ORDER BY 1
Result Should as below
Excelsheet

I think you need to go reconsider the report and maybe splitting it out into multiple reports.
It does not make sense to have a route count as well as a divisional count if they are counting things at different levels of aggregation. So have a route count and division count report.
Either way, division and route is going to be null for 100PLUS because there are no customers for that brand which means there is no route or division info available.
--Division Count
SELECT BrandName, Division, COUNT(CustomerMaster.CustomerID) [Customer Count]
FROM ItemMaster LEFT OUTER JOIN
SalesData ON ItemMaster.BrandName = SalesData.Brand LEFT OUTER JOIN
CustomerMaster ON SalesData.CustomerID = CustomerMaster.CustomerID
GROUP BY BrandName, Division
--Route Count
SELECT BrandName, Route, Division, COUNT(CustomerMaster.CustomerID) [Customer Count]
FROM ItemMaster LEFT OUTER JOIN
SalesData ON ItemMaster.BrandName = SalesData.Brand LEFT OUTER JOIN
CustomerMaster ON SalesData.CustomerID = CustomerMaster.CustomerID
GROUP BY BrandName, Route, Division

Using your sqlfiddle data there are 25 sales records & 18 distinct brand/ division/ route/ customer records and there are no sales invloving 100PLUS
select
B.BrandName
, V.Division
, coalesce(Brand_count,0) as Brand_count
, coalesce(Division_count,0) as Division_count
from (select distinct BrandName from ItemMaster) as B
cross join (select distinct Division from CustomerMaster) as V
left join (
select
Brand
, Division
, sum(cust_count) over (partition by Brand) as Brand_count
, sum(cust_count) over (partition by Division) as Division_count
from (
select
S.Brand
, C.Division
, count(distinct S.CustomerID) cust_count
from salesdata as S
inner join CustomerMaster as C
on S.CustomerID = C.CustomerID
inner join ItemMaster as I
on S.item = I.ItemCode
group by
S.Brand
, C.Division
) as S
) as D
on B.BrandName = D.Brand
and V.Division = D.Division
order by
B.BrandName
, V.Division
;
| BRANDNAME | DIVISION | BRAND_COUNT | DIVISION_COUNT |
|-----------|----------|-------------|----------------|
| 100PLUS | Dubai | 0 | 0 |
| 100PLUS | RAK | 0 | 0 |
| KITCO | Dubai | 9 | 11 |
| KITCO | RAK | 9 | 7 |
| Red Bull | Dubai | 9 | 11 |
| Red Bull | RAK | 9 | 7 |
http://sqlfiddle.com/#!3/fecb0/27

All Credit to #kevriley
select
A.BrandName,
B.Division,
B.Route,
B.DivisionTotalCustomers,
B.RouteTotalCustomers,
isnull(C.PurchasedCustomersCount,0) as PurchasedCustomersCount
from
(
select distinct
BrandName, Route, Division
from dbo.ItemMaster
cross join dbo.CustomerMaster
) A
join
(
select distinct
Division,
Route,
DENSE_RANK() over (partition by Division order by c.CustomerID asc) + DENSE_RANK() over (partition by Division order by c.CustomerID desc) - 1 as DivisionTotalCustomers ,
DENSE_RANK() over (partition by ROUTE order by c.CustomerID asc) + DENSE_RANK() over (partition by ROUTE order by c.CustomerID desc) - 1 as RouteTotalCustomers
from CustomerMaster c
left join SalesData s on c.CustomerID = s.CustomerID
) B on B.Division = A.Division and B.Route = A.Route
left join
(
select
s.brand,
c.division,
c.route,
PurchasedCustomersCount = count(distinct C.CustomerID)
FROM CustomerMaster c
JOIN SalesData s on c.CustomerID = s.CustomerID
--join ItemMaster i on s.item = i.itemcode
GROUP by s.brand, c.division, c.route
) C on A.Brandname = C.Brand and C.Division = A.Division and C.Route = A.Route
See the same on SQL Fiddle

Select B.Brandname,B.Division,C AS DivisionTotalCustomerCount,
ISNULL(T.Count,0) AS PURCHASEDCUSTOMERSCOUNT
from
(
Select CM.Division,M.BrandName,COUNT(distinct CM.CustomerID) AS C
from dbo.CustomerMaster CM
CROSS JOIN ItemMaster M
GROUP BY CM.Division,M.BrandName
)B
LEFT JOIN
(Select Division,Brand,COUNT(Distinct C.CustomerID) As Count from CustomerMaster C
JOIN salesdata D
On C.CustomerID=D.CustomerID
where D.Brand='Red Bull'
GROUP BY Division,Brand
)T
ON B.Brandname=T.Brand
and B.Division=T.Division
Order by 1,2

Related

FULL OUTER JOIN of two INNER JOINs

I have three tables.
My Table Order has an Id and a Date.
Likewise my table Delivery has an Id and a Date.
My third table Part has an Id, and two foreign keys to Order and Delivery: OrderId, DeliveryId.
I want to create a query that gives me an overview over the count of orders and deliveries per month, like this:
+------+-------+------------+---------------+
| Year | Month | OrderCount | DeliveryCount |
+------+-------+------------+---------------+
| 2021 | 2 | 10 | 12 |
+------+-------+------------+---------------+
| 2021 | 1 | 234 | 213 |
+------+-------+------------+---------------+
| ... | ... | ... | ... |
+------+-------+------------+---------------+
So I created a query that gives me the orders per month:
SELECT
MONTH(o.[Date]) AS [Month],
YEAR(o.[Date]) AS [YEAR],
COUNT(p.Id) AS OrderCount
FROM
Part AS p
INNER JOIN
[Order] AS o ON o.Id = p.OrderId
GROUP BY
MONTH(o.[Date]), YEAR(o.[Date])
and one for the deliveries per month:
SELECT
MONTH(d.[Date]) AS [Month],
YEAR(d.[Date]) AS [YEAR],
COUNT(p.Id) AS DeliveryCount
FROM
Part AS p
INNER JOIN
Delivery AS d ON d.Id = p.DeliveryId
GROUP BY
MONTH(d.[Date]), YEAR(d.[Date])
But now I am struggling with joining them. I think I need a FULL OUTER JOIN, because I need the Month/Year/DeliveryCount when there is no order but deliveries and vice versa.
What I have tried is
SELECT
MONTH(o.Date) AS [Month]
YEAR(o.Date) as [Year]
COUNT(p.Id) as OrderCount
FROM
Part AS p
INNER JOIN
[Order] AS o ON o.Id = p.OrderId
GROUP BY
MONTH(o.Date), Year(o.Date)
FULL OUTER JOIN
(SELECT
MONTH(d.Date) AS [Month]
YEAR(d.Date) as [Year]
COUNT(pp.Id) as DeliveryCount
FROM
Part AS pp
INNER JOIN
Delivery AS d ON d.Id = pp.DeliveryId
GROUP BY
MONTH(d.Date), YEAR(d.Date)) AS d ON d.[Month] = MONTH(o.Date) AND d.[Year] = YEAR(o.Date)
But that's not how FULL OUTER JOINS work.
How do I get a full outer of these two inner joins?
Or is this approach of outer joining two inner joins wrong in the first place?
You have to treat your two queries as sub-queries.
This should give you the desired result
Select Orders.OrderCount, Deliveries.DeliveryCount
from (
SELECT
MONTH(o.Date) AS [Month]
YEAR(o.Date) AS [YEAR]
COUNT(p.Id) AS OrderCount
FROM
Part AS p
INNER JOIN
[Order] AS o ON o.Id = p.OrderId
GROUP BY
MONTH(o.Date), YEAR(o.Date)
) Orders
FULL OUTER JOIN
(
SELECT
MONTH(d.Date) AS [Month]
YEAR(d.Date) AS [YEAR]
COUNT(p.Id) AS DeliveryCount
FROM
Part AS p
INNER JOIN
Delivery AS d ON d.Id = p.DeliveryId
GROUP BY
MONTH(d.Date), YEAR(d.Date) ) Deliveries
on Orders.Month = Deliveries.Month
and Orders.Year = Deliveries.Year
This would solve your problem.
In case you have months/year missing, use other join.
WITH OrdersMonth AS (
SELECT
MONTH(o.Date) AS [Month]
YEAR(o.Date) AS [YEAR]
COUNT(p.Id) AS OrderCount
FROM
Part AS p
INNER JOIN
[Order] AS o ON o.Id = p.OrderId
GROUP BY
MONTH(o.Date), YEAR(o.Date)
), DeliveriesMonth AS (
SELECT
MONTH(d.Date) AS [Month]
YEAR(d.Date) AS [YEAR]
COUNT(p.Id) AS DeliveryCount
FROM
Part AS p
INNER JOIN
Delivery AS d ON d.Id = p.DeliveryId
GROUP BY
MONTH(d.Date), YEAR(d.Date)
)
SELECT om.YEAR
,om.Month
,om.OrderCount
,dm.DeliveryCount
FROM OrdersMonth om
LEFT JOIN DeliveriesMonth dm ON om.om.YEAR = dm.YEAR AND om.Month = dm.Month

How to return 1 row instead of 3 when one column has different data

I have 3 rows of data returned from a query
OrderId | OtherId
--------+---------
1234 | 444
1234 | 555
1234 | 666
How to return data in this format
OrderId | OtherId | OtherId2 | OtherId
--------+---------+----------+--------
1234 | 444 | 555 | 666
Can I use Distinct for this problem?
EDIT
The result set comes from a query such as
left join (
Select distinct
o.id
,OL2.db
,case
when i.cust = 'cust2' then
case
when
lower(pdf.dept) not like 'abc%' or pdf.dept is null
then 'Yes'
else'No'
end
else 'Yes'
end as 'Show'
,otherId
from order o with(nolock)
Join instance i with(nolock) on i.id = o.id
Join orderLine ol with(nolock) on ol.id = o.SocialNetworker_Order_InstanceId
and ol.id = o.id
join product p with(nolock) on o.id = p.id
and ol.id = p.id
left join productEx pdf with(nolock) on p.id = pdf.id
and o.id = pdf.id
Where i.cust in ('cust1')
) ol2 on OL2.id = sno.id
and OL2.id2 = sno.id2
and i.db = cc.db
How to work solution into the above code?
You can use row_number() and conditional aggregation:
select orderid,
max(case when seqnum = 1 then otherid end) as otherid_1,
max(case when seqnum = 2 then otherid end) as otherid_2,
max(case when seqnum = 3 then otherid end) as otherid_3
from (select t.*, row_number() over (partition by orderid order by otherid) as seqnum
from t
) t
group by orderid;
Use an outer join with cascading join conditions:
select t1.OrderId, t1.OtherId OtherId1, t2.OtherId OtherId2, t3.OtherId OtherId3
from orders t1
left join orders t2 on t2.OrderId = t1.OrderId
and t2.OtherId > t1.OtherId
left join orders t3 on t3.OrderId = t2.OrderId
and t3.OtherId > t2.OtherId
If there are less than 3 other ids, the right-most columns will be null.
This query will work on pretty much any database.

Selecting the MIN(date) and the MAX(date) with the MAX(date) Freight Values

I need the MIN(Orderdate) and MAX(Orderdate) with the MAX(Orderdate) Freight values.
I've tried using a CTE and I am struggling to eliminate duplicate rows for the CustomerID
USE Northwind
GO
WITH CTE AS (
SELECT a.customerID,
MAX(b.OrderDate) AS LastOrder,
MIN(b.OrderDate) AS FirstOrder
FROM Orders AS b
INNER JOIN Customers AS a
ON b.CustomerID = a.CustomerID
GROUP BY a.CustomerID
)
SELECT CTE.customerID, CTE.FirstOrder, d.OrderDate as LastOrder, d.Freight
FROM CTE
INNER JOIN Orders as d
On CTE.CustomerID = d.CustomerID
GROUP BY CTE.CustomerID, CTE.FirstOrder, d.Freight, d.OrderDate
HAVING d.OrderDate = MAX(d.OrderDate)
I am trying to get these results which should display 89 records.
CustomerID FirstOrder LastOrder Freight
| ALFKI | 1997-08-25 | 1998-04-09 | 1.21 |
| ANATR | 1996-09-18 | 1998-03-04 | 39.92 |
| ANTON | 1996-11-27 | 1998-01-28 | 58.43 |
| AROUT | 1996-11-15 | 1998-04-10 | 33.80 |
| BERGS | 1996-08-12 | 1998-03-04 | 151.52 |
Just to Keep it simple and in line with the question. The Subquery CTE already has the customerID and the LastOrder(Max Order Date). Joining the LastOrder column in CTE to the Order tables OrderDate will give the expected results.
and d.OrderDate = CTE.LastOrder
Full Query:
WITH CTE AS (
SELECT a.customerID,
MAX(b.OrderDate) AS LastOrder,
MIN(b.OrderDate) AS FirstOrder
FROM Orders AS b
INNER JOIN Customers AS a
ON b.CustomerID = a.CustomerID
GROUP BY a.CustomerID
)
SELECT CTE.customerID, CTE.FirstOrder, d.OrderDate as LastOrder, d.Freight
FROM CTE
INNER JOIN Orders as d
On CTE.CustomerID = d.CustomerID
and d.OrderDate = CTE.LastOrder
Just use conditional aggregation:
SELECT o.customerID,
MAX(o.OrderDate) AS LastOrder,
MIN(o.OrderDate) AS FirstOrder,
MAX(CASE WHEN seqnum = 1 THEN o.freight END) as lastFreight
FROM (SELECT o.*,
ROW_NUMBER() OVER (PARTITION BY o.customerID ORDER BY o.OrderDate DESC) as seqnum
FROM Orders o
) o
GROUP BY o.CustomerID;
Note that you do not appear to need the Customer table. All the information you need is in Orders (I doubt you are using Customer to remove rows.)

How to use PIVOT in SQL for this sample

How do I pivot a results query?
Currently it looks like this
| Date | Count | BankName |
+---------+----------+----------+
| 970401 | 87 | Saderat |
| 970401 | 25 | Melli |
| 970401 | 11 | Sina |
into this
|Date | Saderat | Melli | Sina |
+---------+----------+----------+----------+
|970401 | 87 | 25 | 11 |
I tried the following but it's not working
SELECT
PayDate AS [Date],
COUNT(*) AS [Count], b.BankName
FROM
Payments p
INNER JOIN
dbo.Accounts a ON a.AccountId = p.CashAccountId
INNER JOIN
dbo.Banks b ON b.BankId = a.BankId
WHERE
PayTypeId = 21.101
AND PayDate BETWEEN '970401' AND '970412'
GROUP BY
PayDate, b.BankName
ORDER BY
paydate
or
SELECT
x.PayDate AS 'Date',
b.BankName
FROM
(SELECT
p.PayDate, p.PaymentId, p.CashAccountId
FROM
Payments p
WHERE
PayTypeId = 21.101
AND PayDate BETWEEN '970401' AND '970412') AS x
INNER JOIN
dbo.Accounts a ON a.AccountId = x.CashAccountId
INNER JOIN
dbo.Banks b ON b.BankId = a.BankId
PIVOT
(COUNT(PaymentId) FOR PayDate IN (bankid)) AS Pivotable
You can try following SQL for required results:
SELECT PayDate, Saderat, Melli, Sina
FROM
(SELECT PayDate , COUNT(*) AS [Count] , b.BankName
FROM Payments p INNER JOIN dbo.Accounts a ON a.AccountId = p.CashAccountId
INNER JOIN dbo.Banks b ON b.BankId = a.BankId
WHERE PayTypeId = 21.101 AND PayDate BETWEEN '970401' AND '970412'
GROUP BY PayDate , b.BankName
ORDER BY paydate) AS SourceTable
PIVOT
(
SUM([Count])
FOR BankName IN (Saderat, Melli, Sina)
) AS PivotTable;
You can do aggregation :
SELECT PayDate AS [Date],
SUM(CASE WHEN b.BankName = 'Saderat' THEN 1 ELSE 0 END) AS Saderat,
. . .
FROM Payments p INNER JOIN
dbo.Accounts a
ON a.AccountId = p.CashAccountId INNER JOIN
dbo.Banks b
ON b.BankId = a.BankId
WHERE PayTypeId = 21.101 AND PayDate BETWEEN '970401' AND '970412'
GROUP BY PayDate
ORDER BY paydate;
You can use PIVOT function in SQL Server, try following query:
SELECT date, [Saderat],[Melli],[Sina]
FROM YourTableName
PIVOT( MAX(count)
FOR BankName IN ([Saderat],[Melli],[Sina])) AS p
You could PIVOT
SELECT *
FROM
(
SELECT
p.PayDate AS [Date],
b.BankName
FROM dbo.Payments p
JOIN dbo.Accounts a ON a.AccountId = p.CashAccountId
JOIN dbo.Banks b ON b.BankId = a.BankId
WHERE p.PayTypeId = 21.101
AND p.PayDate BETWEEN CAST('1997-04-01' AS DATE) AND CAST('1997-04-12' AS DATE)
) src
PIVOT
(
COUNT(*)
FOR BankName IN (...) -- put quoted list of bank names here
) pvt
ORDER BY [Date]

SQL Server - Get the Total Count with the TOP 1 product

I need to be able to find the total count of orders placed by a customer, but also find the top product in one query. For example in the following structure,
CREATE TABLE #Cust (CustId INT, CustName VARCHAR(50))
CREATE TABLE #Product (ProductId INT, ProductName VARCHAR(10) )
CREATE TABLE #Orders (CustId INT, ProductId INT, OrderTaken BIT)
INSERT #Cust
( CustId, CustName )
VALUES ( 1, 'Paul' ),
( 2, 'F' ),
( 3, 'Francis' )
INSERT #Product
( ProductId, ProductName )
VALUES ( 1, 'Table' ),
( 2, 'Chair' )
INSERT #Orders
( CustId, ProductId, OrderTaken )
VALUES ( 1, 1, 1 ),
( 1, 1, 1 ),
( 1, 2, 1 ),
( 2, 1, 1 )
I have come up with a query,
SELECT * FROM #Cust AS C OUTER APPLY
(
SELECT TOP 1 SQ.ProductId, SUM(SQ.TotalCount) AS TotalQty FROM
(
SELECT O.ProductId, COUNT(*) TotalCount
FROM #Orders AS O WHERE O.CustId = C.CustId
GROUP BY O.CustId , O.ProductId
) SQ
GROUP BY SQ.ProductId
) X
But, that is not giving me the result I am looking for, for Paul it is giving me the correct ProductId, but a count of that product alone.
I want the a Query to return,
CustId | CustName | ProductId | TotalQty
--------+---------------+---------------+------------
1 | Paul | 1 | 3
2 | F | 1 | 1
3 | Francis | NULL | NULL
One option is with the WITH TiES clause
Select Top 1 with ties
CustID
,CustName
,ProductId
,TotalQty
From (
Select C.CustID
,C.CustName
,O.ProductId
,TotalQty = count(O.CustId) over (Partition By O.CustID)
,ProdCount = count(O.CustId) over (Partition By O.CustID,O.ProductID)
From #Cust C
Left Join #Orders O on C.CustID=O.CustId
) A
Order by Row_Number() over (Partition By CustID Order by ProdCount Desc)
Returns
CustID CustName ProductId TotalQty
1 Paul 1 3
2 F 1 1
3 Francis NULL 0
Try
SELECT c.*, ProductId, CustProdTotal, CustTotal
FROM #Cust AS C
OUTER APPLY (
select top(1) with ties ProductId, CustProdTotal, CustTotal
from (
select *, count(OrderTaken) over() as CustTotal
, count(OrderTaken) over(partition by ProductId) as CustProdTotal
from #Orders o
where O.CustId = C.CustId) x
order by row_number() over(order by CustProdTotal desc)
) z
Similar question has been answered with a nice explanation here
(Benefit here : common concept of join used).
(disadvantage : query may not be efficient for large records)
I have modified the solution for your scenario
SELECT s.CustId, s.CustName, s.ProductId, m.TotalOrderTaken
FROM (SELECT p.CustId, p.CustName, t.ProductId, COUNT(*) AS ProductIdCount
FROM #Cust AS p
JOIN #Orders AS t
ON p.CustId = t.CustId
GROUP BY p.CustId, p.CustName, t.ProductId
) AS s
JOIN (SELECT s.CustId, MAX(s.ProductIdCount) AS MaxProductIdCount, sum(s.ProductIdCount) TotalOrderTaken
FROM (
SELECT p.CustId,p.CustName, t.ProductId, COUNT(*) AS ProductIdCount
FROM #Cust AS p
JOIN #Orders AS t
ON p.CustId = t.CustId
GROUP BY p.CustId, p.CustName, t.ProductId
) AS s
GROUP BY s.CustId
) AS m
ON s.CustId = m.CustId AND s.ProductIdCount = m.MaxProductIdCount
Works on SQL Server 2005 onwards.
;with cte1 as (select c.custid, c.custname, o.productid, count(*) as TotalQty
from #cust c
left join #orders o on c.custid=o.custid
left join #product p on p.productid=o.productid
group by c.custid, c.custname, o.productid)
,cte2 as (select custid, max(TotalQty) as TopQty
from cte1
group by custid)
Select cte1.*
from cte1
inner join cte2 on cte1.custid=cte2.custid and cte1.TotalQty=cte2.Topqty
If you can't use the over clause, this would work (obviously, a lot more work compared to an over clause):
SELECT custOrderAll.CustId
, custOrderAll.CustName
, MaxOrder.ProductId
, MAX(custOrderAll.cntAll) TotalQty
FROM (
SELECT c.CustId
, c.CustName
, COUNT(O.ProductId) cntAll
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
) custOrderAll
LEFT JOIN (
SELECT custOrderMAX.CustId
, custOrderMAX.CustName
, custOrderMAX.ProductId
FROM (
SELECT c.CustId
, c.CustName
, O.ProductId
, COUNT(O.ProductId) cntMax
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
, O.ProductId
) custOrderMAX
INNER JOIN (
SELECT mxCnt.CustId
, mxCnt.CustName
, MAX(mxCnt.cntMax) mxCnt
FROM (
SELECT c.CustId
, c.CustName
, O.ProductId
, COUNT(O.ProductId) cntMax
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
, O.ProductId
) mxCnt
GROUP BY mxCnt.CustId
, mxCnt.CustName
) custOrderMAXCnt
ON custOrderMAXCnt.CustId = custOrderMAX.CustId
AND custOrderMAXCnt.mxCnt = custOrderMAX.cntMax
) MaxOrder
ON MaxOrder.CustId = custOrderAll.CustId
AND MaxOrder.CustName = custOrderAll.CustName
GROUP BY custOrderAll.CustId
, custOrderAll.CustName
, MaxOrder.ProductId
Result:
+--------+----------+-----------+----------+
| CustId | CustName | ProductId | TotalQty |
+--------+----------+-----------+----------+
| 1 | Paul | 1 | 3 |
| 2 | F | 1 | 1 |
| 3 | Francis | NULL | 0 |
+--------+----------+-----------+----------+