SQL JOIN omits some fields - sql

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;

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

T-Sql select and group by MIN()

I Have 3 tables like:
ProductCategory [1 - m] Product [1-m] ProductPrice
a simple script like this :
select pc.CategoryId ,pp.LanguageId , pp.ProductId ,pp.Price
from ProductCategory as pc
inner join Product as p on pc.ProductId = p.Id
inner join ProductPrice as pp on p.Id = pp.ProductId
order by CategoryId , LanguageId , ProductId
shows these tabular data :
CategoryId LanguageId ProductId Price
----------- ----------- ----------- ---------------------------------------
1 1 1 55.00
1 1 2 55.00
1 2 1 66.00
1 2 2 42.00
2 1 3 76.00
2 1 4 32.00
2 2 3 89.00
2 2 4 65.00
4 1 4 32.00
4 1 5 77.00
4 2 4 65.00
4 2 5 85.00
now what I need is:
for each category, get full row as is but only with the product that has the minimum price.
I just wrote a simple query that does this like :
with dbData as
(
select pc.CategoryId ,pp.LanguageId , pp.ProductId ,pp.Price
from ProductCategory as pc
inner join Product as p on pc.ProductId = p.Id
inner join ProductPrice as pp on p.Id = pp.ProductId
)
select distinct db1.*
from dbData as db1
inner join dbData as db2 on db1.CategoryId = db2.CategoryId
where db1.LanguageId = db2.LanguageId
and db1.Price = (select Min(Price)
from dbData
where CategoryId = db2.CategoryId
and LanguageId = db2.LanguageId)
and its result is correct:
CategoryId LanguageId ProductId Price
----------- ----------- ----------- ---------------------------------------
1 1 1 55.00
1 1 2 55.00
1 2 2 42.00
2 1 4 32.00
2 2 4 65.00
4 1 4 32.00
4 2 4 65.00
Is there a cooler way for doing this ?
Note: The query must be compliant with Sql-Server 2008 R2+
You could use windowed function like RANK():
WITH cte AS
(
select pc.CategoryId, pp.LanguageId, pp.ProductId, pp.Price,
rnk = RANK() OVER(PARTITION BY pc.CategoryId ,pp.LanguageId ORDER BY pp.Price)
from ProductCategory as pc
join Product as p on pc.ProductId = p.Id
join ProductPrice as pp on p.Id = pp.ProductId
)
SELECT CategoryId, LanguageId, ProductId, Price
FROM cte
WHERE rnk = 1;
LiveDemo
you can add languageid to partition if you need product prices per categoryid and languageid
select top 1 with ties pc.CategoryId ,pp.LanguageId , pp.ProductId ,pp.Price
from ProductCategory as pc
inner join Product as p on pc.ProductId = p.Id
inner join ProductPrice as pp on p.Id = pp.ProductId
order by row_number() over (partition by pc.categoryid order by price)
You are not using the Product table in your query, so it doesn't seem necessary. I would right this as:
select ppc.*
from (select pc.CategoryId, pp.LanguageId , pp.ProductId, pp.Price,
row_number() over (partition by pc.CategoryId order by pp.Price) as seqnum
from ProductCategory pc inner join
ProductPrice pp
on pc.ProductId = pp.ProductId
) ppc
where seqnum = 1
order by CategoryId, LanguageId, ProductId;

Get top n occurences based on related table value

I have a table Orders (Id, OrderDate, CreatorId) and a table OrderLines (Id, OrderId, OwnerIdentity, ProductId, Amount)
Scenario is as follows: Someone opens up an Order and other users can then place their product orders on that order. Those users are the OwnerId of OrderLines.
I need to retrieve the top 3 latest orders that a user has placed an order on and display all of his orders placed, to give him an insight in his personal recent orders.
So my end result would be something like
OrderId | ProductId | Amount
----------------------------
1 | 1 | 2
1 | 7 | 1
1 | 2 | 5
4 | 4 | 3
4 | 1 | 2
8 | 4 | 1
8 | 9 | 2
Select o.Id as OrderId, ol.ProductId, ol.Amount from Orders o
inner join OrderLines ol
on o.Id = ol.OrderId where o.Id in
(Select top 3 OrderId from Orders where OwnerId = #OwnerId)
Order By o.OrderDate desc
You can add date time column to OrderLines table to query latest personal orders and then update the code by moving "order by OrderDate desc" section to sub select query.
select * from
(
select OrderId, ProductId, Amount
row_number() over (partition by OrderID order by Orders.OrderDate) as rn
from OrderLines
join Orders
on OrderLines.OrderId = Orders.Id
where OwnerIdentity = x
) lskdfj
where rn <= 3
Try the below query:
SELECT OL.OrderId, OL.ProductID, OL.Amount
FROM OrderLines OL WHERE OL.OrderId IN
(
SELECT TOP 3 O.OrderID FROM orders O LEFT JOIN OrderLines OL2
ON OL2.orderId=O.OrderID
WHERE OL2.OwnerIdentity =...
ORDER BY O.OrderDate DESC
) AND WHERE OL.OwnerIdentity =...
;WITH cte AS (
SELECT ol.OrderId, ol.ProductId, ol.Amount,
ROW_NUMBER()OVER (PARTITION BY ol.OrderId ORDER BY o.OrderDate DESC) rn
FROM OrderLines ol
JOIN Orders o ON ol.OrderId = o.Id
WHERE OwnerIdentity = #OwnerId
)
SELECT OrderId, ProductId, Amount
FROM cte
WHERE rn <= 3

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 orders where order lines meet certain requirements

I have the following simplified tables:
tblOrders
orderID date
---------------------
1 2013-10-04
2 2013-10-05
3 2013-10-06
tblOrderLines
lineID orderID ProductCategory
--------------------------------------
1 1 10
2 1 3
3 1 10
4 2 3
5 3 3
6 3 10
7 3 10
I want to select records from tblOrders ONLY if any order line has ProductCategory = 10. So, if none of the lines of a particular order has ProductCategory = 10, then do not return that order.
How would I do that?
This should do:
SELECT *
FROM tblOrders O
WHERE EXISTS(SELECT 1 FROM tblOrderLines
WHERE ProductCategory = 10
AND OrderID = O.OrderID)
You can use exists for this
Select o.*
From tblOrders o
Where exists (
Select 1
From tblOrderLines ol
Where ol.ProductCategory = 10
And ol.OrderId = o.OrderId
)
try this
SELECT DISTINCT orderId
FROM tblOrders t1
INNER JOIN tblOrderLines t2 ON t1.orderId = t2.orderId
WHERE t2.ProductCategory = 10
Try this:
SELECT *
FROM tblOrders O
JOIN tblOrderLines L
ON O.orderID = L.orderID
WHERE L.OrderID in (SELECT orderID FROM tblOrderLines WHERE ProductCategory = 10)