FULL OUTER JOIN of two INNER JOINs - sql

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

Related

SQL JOIN omits some fields

I have the following tables:
Product_T with columns:
ProductID,
ProductDescription
OrderLine_T with columns:
OrderID,
ProductID,
OrderedQuantity
Order_T with columns:
OrderID,
CustomerID,
Customer_T with columns:
CustomerID,
CustomerName
I want to list the product ID and description, along with the customer ID and name for the customer who has bought the most of that product and also show the total quantity ordered by that customer.
I came up with following query, to list the max quantity product per order:
SELECT o1.OrderID, o1.ProductID, SUM(o1.OrderedQuantity) AS A
FROM OrderLine_T o1
GROUP BY
o1.ProductID,
o1.OrderID
HAVING SUM(o1.OrderedQuantity) = (
SELECT MAX(s.d)
FROM (
SELECT
o1.OrderID,
o1.ProductID,
SUM(o1.OrderedQuantity) AS d
FROM OrderLine_T o1
GROUP BY
o1.ProductID,
o1.OrderID
) s
WHERE o1.ProductID = s.ProductID
)
And that gave me a correct output of:
50 20 1
48 17 5
32 14 10
59 13 2
1 10 9
2 8 2
69 7 4
4 6 3
32 5 10
55 4 2
2 3 12
1 2 18
26 1 5
But then, when I tried joining it with other tables, so I could select CustomerName and CustomerID, like so:
SELECT
o1.ProductID,
s.CustomerName,
s.CustomerID,
SUM(o1.OrderedQuantity) AS A
FROM OrderLine_T o1
INNER JOIN (
SELECT
c1.CustomerName,
c1.CustomerID,
p1.ProductID
FROM Product_T p1
INNER JOIN OrderLine_T o3 ON p1.ProductID = o3.ProductID
INNER JOIN Order_T o2 ON o3.OrderID = o2.OrderID
INNER JOIN Customer_T c1 ON o2.CustomerID = c1.CustomerID
) s ON s.ProductID = o1.ProductID
GROUP BY
o1.ProductID,
s.CustomerName,
s.CustomerID
HAVING SUM(o1.OrderedQuantity) = (
SELECT MAX(s.d)
FROM (
SELECT
o1.OrderID,
o1.ProductID,
SUM(o1.OrderedQuantity) AS d
FROM OrderLine_T o1
GROUP BY
o1.ProductID,
o1.OrderID
) s
WHERE o1.ProductID = s.ProductID
) ;
The output shrunk to:
17 Contemporary Casuals 1 5
8 Home Furnishings 3 2
7 Eastern Furniture 4 4
10 Eastern Furniture 4 9
20 Dunkins Furniture 8 1
13 Ikards 13 2
Why could that be?
It seems you should be using window functions here, such as ROW_NUMBER, along with conditional aggregation
SELECT
o.ProductID,
p.Description,
CustomerID = MAX(CASE WHEN o.rn = 1 THEN c.CustomerID END),
CustomerName = MAX(CASE WHEN o.rn = 1 THEN c.CustomerName END),
SUM(CASE WHEN o.rn = 1 THEN o.TotalQty END) AS QtyForTopCustomer
SUM(o.TotalQty) AS TotalQty
FROM (
SELECT
o.ProductID,
o.CustomerID,
TotalQty = SUM(oi.OrderedQuantity),
rn = ROW_NUMBER() OVER (PARTITION BY oi.ProductId ORDER BY SUM(oi.OrderedQuantity) DESC)
FROM OrderLine_T ol
INNER JOIN Order_T o ON o.OrderID = ol.OrderID
GROUP BY
o.ProductID,
o.CustomerID
) o
INNER JOIN Customer_T c ON c.CustomerID = o.CustomerID
INNER JOIN Product_T p ON p.ProductID = ol.ProductID
GROUP BY
o.ProductID,
p.Description;
If you only wanted the data for that one customer, you could remove the conditional aggregation and just filter by row-number
SELECT
o.ProductID,
p.Description,
o.CustomerID,
o.CustomerName,
o.TotalQty
FROM (
SELECT
p.ProductID,
p.Description,
o.CustomerID,
TotalQty = SUM(oi.OrderedQuantity),
rn = ROW_NUMBER() OVER (PARTITION BY oi.ProductId ORDER BY SUM(oi.OrderedQuantity) DESC)
FROM OrderLine_T ol
INNER JOIN Order_T o ON o.OrderID = ol.OrderID
GROUP BY
p.ProductID,
p.Description,
o.CustomerID
) o
INNER JOIN Customer_T c ON c.CustomerID = o.CustomerID
INNER JOIN Product_T p ON p.ProductID = ol.ProductID
WHERE o.rn = 1;

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]

Count Customers based on item master

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

Get Total Branch wise in SQL Server

SELECT b.BranchName ,
pm.AgreementValue
FROM dbo.Member AS m
INNER JOIN dbo.PlanMaster AS pm ON ( m.PlanId = pm.PlanId )
INNER JOIN dbo.Branch AS b ON ( b.BranchId = m.BranchId )
this is the result of above query
BranchName AgreementValue
------------------------------
abc 60000.00
abc 36000.00
abc 36000.00
xyz 20000.00
xyz 10000.00
now i want to get to total of AgreementValue BranchName wise..thanks for help
GROUP BY b.BranchName with SUM like so:
SELECT b.BranchName ,
SUM(pm.AgreementValue) TotalValue
FROM dbo.Member AS m
INNER JOIN dbo.PlanMaster AS pm ON ( m.PlanId = pm.PlanId )
INNER JOIN dbo.Branch AS b ON ( b.BranchId = m.BranchId )
GROUP BY b.BranchName;
If you are trying to get the total on each row, then use the window function for sum():
SELECT b.BranchName ,
pm.AgreementValue,
sum(pm.AgreementValue) over (partition by b.BranchName) as BranchTotal
FROM dbo.Member AS m
INNER JOIN dbo.PlanMaster AS pm ON ( m.PlanId = pm.PlanId )
INNER JOIN dbo.Branch AS b ON ( b.BranchId = m.BranchId )