Total of Totals in SQL Server - sql

I have 3 tables -
Books -
BookNo BookName BookType
123 ABC 1
555 XYZ 0
Shelf
Shelf ShelfNo BookNo BookQuantity
XB XB01 123 5
XB XB02 555 3
XB XB03 123 8
BooksIssued
ShelfNo BookName IssuedDate QuantityIssued
XB01 ABC 11/21/2022 2
XB02 XYZ 11/20/2022 1
XB03 ABC 11/21/2022 5
My goal is to find out total number of books stock we have. The output should be grouped by book. And I have to combine all shelfNo which contain the same book and sum their Shelf.BookQuantity and then add it to BooksIssued.QuantityIssued for that particular book. Booktype should be displayed as Children for 0 and 1 for adults.
For example,
Output
BookNo BookName BookType Total Stock
123 ABC adults 20 //(5+8+2+5)
555 XYZ children 4 //(3+1)
So far, I have written this. I know I have chosen extra columns in my query than what I have mentioned in my output format. It is so because I was going step by step to understand the flow. I wanted to first group the data by book and sum the quantity but it isn't grouping the data by bookno . It is also not summing the bi.quantityissued.
select s.bookno, b.booktype, s.shelfno, b.bookname, s.bookquantity,
sum(bi.quantityissued), bi.issueddate
from Shelf s
left outer join BooksIssued bi on s.shelfno = bi.shelfno
left outer join Books b on s.bookno=b.bookno
where s.shelf = 'XB'
and bi.issueddate between '11/01/2022' and '11/07/2022'
group by s.bookno, s.shelfno, b.booktype, b.bookname, s.bookquantity, bi.issueddate
Please guide me what do I do next. Thank you.

This should do it:
WITH baseData As
(
SELECT BookNo, BookQty As Qty
FROM Shelf s
WHERE s.shelf = 'XB'
UNION ALL
SELECT b0.BookNo, QtyIssued As Qty
FROM BooksIssued bi
INNER JOIN Books b0 on b0.BookName = bi.BookName
WHERE bi.IssuedDate >= '20221101' AND bi.IssuedDate < '20221108'
),
grouped As
(
SELECT BookNo, Sum(Qty) As [Total Stock]
FROM baseData
GROUP BY BookNo
)
SELECT b.BookNo, b.BookName, b.BookType, g.[Total Stock]
FROM grouped g
INNER JOIN Books b ON b.BookNo = g.BookNo
This also shows one of the reasons (among several) the BooksIssued table should use BookNo instead of BookName: it would save you a join.
We can also write this with nested SELECT queries instead of the common table expressions (CTEs), but I find the CTE much easier to reason about:
SELECT b.BookNo, b.BookName, b.BookType, g.[Total Stock]
FROM (
SELECT BookNo, Sum(Qty) As [Total Stock]
FROM (
SELECT BookNo, BookQty As Qty
FROM Shelf s
WHERE s.shelf = 'XB'
UNION ALL
SELECT b0.BookNo, QtyIssued As Qty
FROM BooksIssued bi
INNER JOIN Books b0 on b0.BookName = bi.BookName
WHERE bi.IssuedDate >= '20221101' AND bi.IssuedDate < '20221108'
) baseData
GROUP BY BookNo
) g
INNER JOIN Books b ON b.BookNo = g.BookNo
This is still missing the adults vs children book type. You can fix this with a CASE expression, or using a Table Value Constructor. I prefer the latter here, because you can add many more types over time in an efficient way and because it sets you up to eventually use a real table, which is what you should have had in the first place:
WITH baseData As
(
SELECT BookNo, BookQty As Qty
FROM Shelf s
WHERE s.shelf = 'XB'
UNION ALL
SELECT b0.BookNo, QtyIssued As Qty
FROM BooksIssued bi
INNER JOIN Books b0 on b0.BookName = bi.BookName
WHERE bi.IssuedDate >= '20221101' AND bi.IssuedDate < '20221108'
),
grouped As
(
SELECT BookNo, Sum(Qty) As [Total Stock]
FROM baseData
GROUP BY BookNo
)
SELECT b.BookNo, b.BookName, bt.Name As BookType, g.[Total Stock]
FROM grouped g
INNER JOIN Books b ON b.BookNo = g.BookNo
INNER JOIN (VALUES (0, 'Childrens'), (1 'Adults') ) AS bt(Type, Name)
ON b.BookType = bt.Type;

Try this:
select bookno BookNo,
bookname BookName,
case booktype
when 1 then 'adults'
when 0 then 'children'
end BookType
SUM(TotalStock) TotalStock
from (
select b.bookno,
b.bookname,
b.booktype,
sum(bookquantity) TotalStock
from ..books b
inner join ..shelf s
ON b.bookno = s.bookno
group by b.bookno, b.bookname, b.booktype
UNION ALL
select b.bookno,
b.bookname,
b.booktype,
sum(QuantityIssued) TotalStock
from ..books b
inner join ..shelf s
ON b.bookno = s.bookno
inner join ..booksissued bi
ON s.shelfno = bi.shelfno
and b.bookname = bi.bookname
group by b.bookno, b.bookname, b.booktype
) s
group by bookno, bookname, booktype

Related

SQL count in select query

I have a sql query that creates a label in a cab file for a shopping company. I want to include the amount of packages in an order, some have multiple.
Each returns line in my select query has an I’d and contains a package but I need to count them.
So I have:
Name Email Weight Price ID
Joe B J#.com 10 12.5. 1
Joe B J#.com 10 12.5. 1
Joe C JC#.com 10 14.5. 2
How can I count the ID’s to return a column called pieces and in this example it would be 2 for ID 1 and 1 for ID 2
Thanks
James
enter code here
Select
'WPX' As 'Product Code',
delivery_header.dh_datetime As 'Shipment Date',
'G' As 'Shipment Type',
order_header_detail.ohd_delivery_email As 'Receiver Email Address',
variant_detail.vad_weight As 'Shipment Weight in KG',
(lots of other fields....)
delivery_header.dh_number As 'Shippers Reference',
(SELECT Count(*)
FROM delivery_header
WHERE
dh_number = OU.dh_number
) As 'Number of Pieces',
From delivery_line_item Inner Join
delivery_header On delivery_header.dh_id = delivery_line_item.dli_dh_id
Inner Join
order_line_item On delivery_line_item.dli_oli_id = order_line_item.oli_id
Inner Join
variant_detail On variant_detail.vad_id = order_line_item.oli_vad_id
Inner Join
order_header On order_header.oh_id = order_line_item.oli_oh_id Inner Join
stock_location On stock_location.sl_id = order_line_item.oli_sl_id Inner Join
customer_detail On customer_detail.cd_id = order_header.oh_cd_id Inner Join
order_header_detail On order_header.oh_id = order_header_detail.ohd_oh_id
Left Join
order_header_analysis On order_header.oh_id = order_header_analysis.oha_oh_id
Left Join
order_customer_analysis On order_header.oh_id =
order_customer_analysis.oca_oh_id Left Join
order_delivery_analysis On order_header.oh_id =
order_delivery_analysis.oda_oh_id Left Join
order_line_analysis On order_line_item.oli_id = order_line_analysis.ola_oli_id
Left Join
order_line_product_analysis On order_line_item.oli_id =
order_line_product_analysis.olpa_oli_id Left Join
order_line_variant_analysis On order_line_item.oli_id =
order_line_variant_analysis.olva_oli_id Inner Join
product_detail On product_detail.pd_id = variant_detail.vad_pd_id Inner Join
delivery_method On delivery_method.dm_id = order_header_detail.ohd_dm_id
Inner Join
delivery_method [Delivery Method] On [Delivery Method].dm_id =
order_header_detail.ohd_dm_id Inner Join
currency On currency.c_id = order_header.oh_c_id And currency.c_id =
delivery_method.dm_c_id And currency.c_id = [Delivery Method].dm_c_id
Where
delivery_header.dh_number IN (199364,199363,199362,199360)
Order By delivery_header.dh_number
You can use GROUP BY with COUNT like this:
SELECT ID, COUNT(*) as count FROM tbl GROUP BY ID
What I understood from your question is that, you are looking for output like
Name Email Weight Price ID Pieces
Joe B J#.com 10 12.5. 1 2
Joe B J#.com 10 12.5. 1 2
Joe C JC#.com 10 14.5. 2 1
Using following query you should get the desired output in most of the DBMS.
SELECT NAME,
EMAIL,
WEIGHT,
PRICE,
ID,
(SELECT Count(*)
FROM <YOUR_TABLE_NAME>
WHERE ID= OU.ID) AS Pieces
FROM <YOUR_TABLE_NAME> OU
Similar Query for MS SQL Server can be
SELECT NAME, EMAIL,WEIGHT , Price, ID,CT.Pieces
FROM <YOUR_TABLE_NAME> OU
CROSS APPLY
(
SELECT COUNT(*) AS Pieces FROM <YOUR_TABLE_NAME> IU WHERE OU.ID=IU.ID
)CT

Avoiding Multiple Joins

I have a table (Manufacturers):
Manufacturer ID
------------------
Lagun 1
Hurco 2
Mazak 3
Haas 4
Then another table (inventory):
Shop Lathe DrillPress CNC Mill ID
-------------------------------------------------
ABC Inc 2 1 3 3 1
VECO 4 2 1 2 2
I need to end up with:
Shop Lathe DrillPress CNC Mill
--------------------------------------------
ABC Inc Hurco Lagun Mazak Mazak
VECO Haas Hurco Lagun Hurco
I have this:
SELECT
Shop, M1.Manufacturer AS Lathe, M2.Manufacturer AS DrillPress,
M3.Manufacturer AS CNC, M4.Manufacturer AS Mill
FROM Inventory I
LEFT JOIN Manufacturers M1 ON M1.ID = I.LstFlowMan
LEFT JOIN Manufacturers M2 ON M2.ID = I.LstFiltFlowMan
LEFT JOIN Manufacturers M3 ON M3.ID = I.LstFilterMan
LEFT JOIN Manufacturers M4 ON M4.ID = I.LstEmitMan
I'm probably missing a better way with a PIVOT or CROSS APPLY or something.
Thanks to #LauDec, here is the SQLServer Version:
select * from (
select SHOP, KEYS, MANUFACTURER from
(select SHOP, LATHE,DRILLPRESS,CNC,MILL from inventory) a
unpivot (val for keys in (LATHE,DRILLPRESS,CNC,MILL)) as unpvt
JOIN Manufacturers M ON M.ID=VAL
) a
PIVOT (
MAX(MANUFACTURER)
FOR keys in (LATHE,DRILLPRESS,CNC,MILL)
) as pp
Here is one way to do it.
SELECT i.shop,
lathe = Max(CASE WHEN i.lathe = m.id THEN m.manufacturer END),
drillpress = Max(CASE WHEN i.drillpress = m.id THEN m.manufacturer END),
mill = Max(CASE WHEN i.mill = m.id THEN m.manufacturer END),
cnc = Max(CASE WHEN i.cnc= m.id THEN m.manufacturer END)
FROM manufacturers m
JOIN inventory i
ON m.id IN ( i.lathe, i.drillpress, i.cnc, i.mill )
GROUP BY i.shop
Consider changing the table structure of inventory table.
inventory : Shop,MachineType,ManufacturerID
Then you can use Pivot/Cross tab to get the result
You can UNPIVOT JOIN and RE-PIVOT
SELECT * FROM (
select SHOP, KEYS, MANUFACTURER from
inventory unpivot ( val for keys in ("LATHE","DRILLPRESS","CNC","MILL"))
JOIN Manufacturers M ON M.ID=VAL
) PIVOT (
MAX(MANUFACTURER)
FOR keys in ('LATHE','DRILLPRESS','CNC','MILL')
)

Return details of the latest bill linked to the user

I have two different sources of information and I am trying to marry them together to get information relating to a customer and their last bill. I have managed to get details of each customer and their max billing period end date but I am unsure as to how I can then get the details from the associated bill. I have the following query:
SELECT new_mprnnumber,
new_customernumber,
MAX(b.billingPeriodEndDate) as 'Billed up to date'
FROM [CRM].[crm4_MSCRM].[dbo].[AccountExtensionBase] as a
inner join Billing.dbo.bill as b
on a.new_mprnnumber = b.MPRN
where new_accountstage = 7
and new_accounttype = 2
group by new_mprnnumber,
new_customernumber
GO
The bill has fields like amount due etc but I only want to return details of those from the max dated bill, any help would be greatly appreciated
Use a CTE with row_number()
with CTE as
(
select a.new_mprnnumber,
a.new_customernumber,
b.*,
row_number()
over (partition by new_customernumber -- add additional partitions as you would group bys
order by billingPeriodEndDate desc) as r_ord
from AccountExtensionBase a
inner join bill b
on a.new_mprnnumber = b.MPRN
where new_accountstage = 7
and new_accounttype = 2
)
select *
from CTE
where r_ord = 1
Put your query into a CTE and then link back to table bill:
WITH CTE AS (
SELECT new_mprnnumber,
new_customernumber,
MAX(b.billingPeriodEndDate) as MaxBillDate
FROM [CRM].[crm4_MSCRM].[dbo].[AccountExtensionBase] as a
inner join Billing.dbo.bill as b
on a.new_mprnnumber = b.MPRN
where new_accountstage = 7
and new_accounttype = 2
group by new_mprnnumber,
new_customernumber
)
SELECT b.*
FROM CTE c
INNER JOIN Billing.dbo.bill b ON c.MaxBillDate = b.billingPeriodEndDate AND c.new_mprnnumber = b.MPRN

FIFO match first stock buys to first sells sql

I have data that looks like this:
Stock buys and sells
I need a query to apply the FIFO method to the Buys and Sells so I get a table that looks like this:
FIFO Buys and Sells
I want to be able to match the first buy/s to the first sells with buys on the left and sells on the right. If there is no sell then Nulls should be applied on the right and if there is no buy then nulls should be applied on the left. The brokerage transaction key can be used as the order in which the trades occurred. This is what I've tried so far. Any help would be much appreciated!
SELECT a.ACCT_ID, a.Trade_Date_Key, a.Brokerage_Transaction_Key, a.Buy_Sell_Code, a.Principal_Amt, a.Security_Quantity
, (a.Security_Quantity + b.Security_Quantity) CUMULATIVE_POSITION
, a.SHARE_PRICE
, (A.Principal_Amt + B.Principal_Amt) CUMULATIVE_VALUE
from #TRANSACTIONS_WITH_RANK a
left join #TRANSACTIONS_WITH_RANK b
on a.acct_id = b.acct_id and a.rank = b.rank + 1
ORDER BY BROKERAGE_TRANSACTION_KEY
In your question you mention matching the first buy(s) with the first sell(s), but your example output seems to ignore that part. Here's an example of how to do if you want to match the first buy(s) to first sell(s) based on the Acct_ID and Trade_Date
SELECT buy.*, sell.*
FROM #TRANSACTIONS_WITH_RANK buy
INNER JOIN (
SELECT MIN(Trade_Date) Trade_Date
FROM #TRANSACTIONS_WITH_RANK
WHERE Buy_Sell_Code = 'B'
GROUP BY Acct_ID
) TDateBuy
ON buy.Trade_Date = TDateBuy.Trade_Date
FULL OUTER JOIN #TRANSACTIONS_WITH_RANK sell
INNER JOIN (
SELECT MIN(Trade_Date) Trade_Date
FROM #TRANSACTIONS_WITH_RANK
WHERE Buy_Sell_Code = 'S'
GROUP BY Acct_ID
) TDateSell
ON sell.Trade_Date = TDateSell.Trade_Date
ON buy.Acct_ID = sell.Acct_ID
EDIT: after seeing OP's comment I have changed the query
SELECT
buy.Acct_ID, buy.Trade_Date, buy.Brokerage_Transaction_Key, buy.Buy_Sell_Code, buy.Principal_Amt, buy.Security_Quantity,
sell.Acct_ID, sell.Trade_Date, sell.Brokerage_Transaction_Key, sell.Buy_Sell_Code, sell.Principal_Amt, sell.Security_Quantity
FROM (
SELECT wr.*, MIN(TransKey) TransKey -- This is the value of the Sell to be joined
FROM #TRANSACTIONS_WITH_RANK wr
LEFT OUTER JOIN (
SELECT MIN(Brokerage_Transaction_Key) TransKey, Acct_ID
FROM (
SELECT
tr.*,
(
SELECT MAX(Brokerage_Transaction_Key) --Purpose is to give outer query value to GROUP on
FROM #TRANSACTIONS_WITH_RANK
WHERE Buy_Sell_Code = 'B'
AND Acct_ID = tr.Acct_ID
AND Brokerage_Transaction_Key < tr.Brokerage_Transaction_Key
) MaxLesserKey
FROM #TRANSACTIONS_WITH_RANK tr
) data
WHERE Buy_Sell_Code = 'S'
GROUP BY Acct_ID, MaxLesserKey
) MinSell
ON wr.Acct_ID = MinSell.Acct_ID
AND wr.Brokerage_Transaction_Key < MinSell.TransKey
WHERE Buy_Sell_Code = 'B'
GROUP BY wr.Acct_ID, Trade_Date, Brokerage_Transaction_Key, Buy_Sell_Code, Principal_Amt, Security_Quantity
) buy
FULL OUTER JOIN (
SELECT wr.*, MIN(MinBuy.TransKey) TransKey -- This is the value of the Buy to be joined
FROM #TRANSACTIONS_WITH_RANK wr
LEFT OUTER JOIN (
SELECT MIN(Brokerage_Transaction_Key) TransKey, Acct_ID
FROM (
SELECT
tr.*,
(
SELECT MAX(Brokerage_Transaction_Key) --Purpose is to give outer query a value to GROUP on
FROM #TRANSACTIONS_WITH_RANK
WHERE Buy_Sell_Code = 'S'
AND Brokerage_Transaction_Key < tr.Brokerage_Transaction_Key
) MaxLesserKey
FROM #TRANSACTIONS_WITH_RANK tr
) data
WHERE Buy_Sell_Code = 'B'
GROUP BY Acct_ID, MaxLesserKey
) MinBuy
ON wr.Acct_ID = MinBuy.Acct_ID
AND wr.Brokerage_Transaction_Key < MinBuy.TransKey
WHERE Buy_Sell_Code = 'S'
GROUP BY wr.Acct_ID, Trade_Date, Brokerage_Transaction_Key, Buy_Sell_Code, Principal_Amt, Security_Quantity
) sell
ON buy.TransKey = sell.Brokerage_Transaction_Key
OR sell.TransKey = buy.Brokerage_Transaction_Key
Basically what this does is grab all Buy(s) and their matching Sell Brokerage_Transaction_Key (TransKey) and does a FULL OUTER JOIN (NULLs out the Buy or Sell side when there are no opposite matching transactions) to the set of Sell(s) and their matching Buy Brokerage_Transaction_Key (TransKey).
The TransKey is the smallest Brokerage_Transaction_Key of the opposite Buy_Sell_Code for each group of Buy(s)/Sell(s). This will give you first Sell to first Buy(s) or first Buy to first Sell(s) per group of transactions for a particular Acct_ID. The MaxLesserKey field is there to just to give the TransKey query a value to GROUP on

SQL: How to create new columns depending on other column's value in the same table

I have 2 tables, Main and Units.
Main table:
Todate Unit CategoryID Quantity
1/7/2012 1 S 300
1/7/2012 1 U 350
2/7/2012 2 S 220
3/7/2012 2 S 50
3/7/2012 2 U 330
4/7/2012 1 S 200
4/7/2012 1 U 180
S = Sales, U = Upgrades
Units table:
UnitNum UnitName
1 Measures
2 Performance
I need to get this result:
Todate UnitNum UnitName Sales Upgrades
1/7/2012 1 Measures 300 350
2/7/2012 2 Performance 220
3/7/2012 2 Performance 50 330
4/7/2012 1 Measures 200 180
Meaning i need to create 2 columns - sales and upgrades, depending on the value in CategoryID, and i need them to be in the same row.
What i have so far is this
select Todate, Main.Unit, UnitName,
case when CategoryID = 'S' then Quantity end as Sales,
case when CategoryID = 'U' then Quantity end as Upgrades
from Main join Units on Main.UnitNum = Units.UnitNum
group by Todate, Main.Unit, UnitName
It gives me 2 new columns but they are in two separate rows..
I would really appreciate any help resolving this!
Thank you
You just need an aggregate query around the case statements.
select m.todate
, m.unit
, u.unitname
, sum(case when m.categoryid = 'S' then quantity end ) as sales
, sum(case when m.categoryid = 'U' then quantity end ) as upgrages
from main m
join units u
on m.unit = u.unitnum
group by m.todate, m.unit, u.unitname
You need something like this:
SELECT
Todate, m.Unit, UnitName,
Sales = (SELECT SUM(Quantity)
FROM dbo.Main m2
WHERE m.Todate = m2.Todate AND CategoryID = 'S'),
Updates = (SELECT SUM(Quantity)
FROM dbo.Main m2
WHERE m.Todate = m2.Todate AND CategoryID = 'U')
FROM
dbo.Main m
INNER join
dbo.Units u on m.Unit = u.UnitNum
GROUP BY
Todate, m.Unit, UnitName
ORDER BY
Todate, m.Unit, UnitName
This seems to be returning the output you're looking for:
You need to do a self-join of your main table with itself:
SELECT m1.todate, m1.unit AS unitnum, u.unitname,
SUM(m1.quantity) AS sales, SUM(m2.quantity) AS upgrades
FROM (SELECT todate, unit, quantity FROM Main WHERE category = 'S') AS m1
FULL OUTER JOIN
(SELECT todate, unit, quantity FROM Main WHERE category = 'U') AS m2
ON m1.todate = m2.todate AND m1.unit = m2.unit
JOIN units AS u
ON m1.unit = u.unitnum
GROUP BY m1.todate, m1.unit, u.unitname
ORDER BY m1.todate, m1.unit, u.unitname;
There are many other equivalent ways of writing the same query; this one preserves the symmetry of the problem fairly well, though. (Updated to use a FULL OUTER JOIN between the m1 and m2 sub-queries, to deal with cases where there are sales with no upgrades or upgrades with no sales.)
You could use PIVOT for this too:
WITH data AS (
SELECT
m.Todate,
m.UnitNum,
u.UnitName,
Category = CASE m.CategoryID
WHEN 'S' THEN 'Sales'
WHEN 'U' THEN 'Upgrades'
END,
Quantity
FROM Main m
INNER JOIN Units u
ON m.UnitNum = u.UnitNum
)
SELECT
Todate,
UnitNum,
UnitName,
Sales,
Upgrades
FROM data
PIVOT (
SUM(Quantity) FOR Category IN (Sales, Upgrades)
) p
;