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

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
;

Related

Total of Totals in SQL Server

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

How to write Multiple Sum amount query in select statement using SQL for fast performance

I have writen a query below which is working fine for my report
declare #orgId int=3,
#Year int=2022
;with cte as (
select t.requestNo,year(timeStamp) dispensedInYear from txnhistory(NOLOCK) t where t.categoryNo=411 and t.statusNO=76 and t.companyId=#orgId and
(#Year=0 or (year(t.Timestamp)=#Year ))
)
select distinct p.personid as personId,p.firstName+' '+p.lastname Fitter,
(SELECT ISNULL( ROUND(SUM(amountPaid),3),0) PaidAmount FROM commissionPaid where commissionType='fitter' and personid=cp.personid and statusNo=452 and (#Year=0 or (year(dispensedSignDate)=#Year ))) as total,
(select ISNULL( ROUND(SUM(plt.allowedPrice),3),0) from commissionPaid cp1
inner join ProfitOrLossTable plt on cp1.doNumber=plt.doid where
cp1.personId=cp.personId)BillableAmount,
from cte as r
join productrequestall pr on r.requestNo=pr.requestId --and cp.paymentType='Sales Commission'
join commissionPaid cp on cp.doNumber=pr.doId and cp.commissionType='fitter' and cp.statusNo=452
join commissionratepaid crp on crp.doNumber=cp.doNumber and crp.commissionType=cp.commissionType
join employeecheck ec on ec.employeeCheckId=cp.checkNumber
join person p on p.personId=cp.personId
join fitterCommissionRates fr(NOLOCK) on fr.fitterId=cp.personId and fr.organizationId=#orgId
But is this correct way to write in below line in query because I have other same 3 sum amount counts
(SELECT ISNULL( ROUND(SUM(amountPaid),3),0) PaidAmount FROM commissionPaid where commissionType='fitter' and personid=cp.personid and statusNo=452 and (#Year=0 or (year(dispensedSignDate)=#Year ))) as total
or other way to write above line using left join or sub query to fast report performance and avoid to query timeout issue.
I have modified your query then it is working.
declare
#orgId int = 3,
#Year int = 2021;
select
p.personid,
max( p.firstName +' '+ p.lastname ) Fitter,
coalesce( sum( cp.PaidAmount ), 0 ) PaidAmount
from
txnhistory r
join productrequestall pr
on r.requestNo = pr.requestId
-- and cp.paymentType='Sales Commission'
join
(
SELECT
personid,
doNumber,
ROUND( SUM( amountPaid ), 3) PaidAmount
FROM
commissionPaid
where
commissionType = 'fitter'
and statusNo = 452
and #Year in ( 0, year(dispensedSignDate))
group by
personId,
doNumber,commissionType ) cp
on pr.doId = cp.doNumber
AND pr.doId = cp.doNumber
join commissionratepaid crp
on cp.doNumber = crp.doNumber
and crp.commissionType='fitter'
join person p
on cp.personId = p.personId
join fitterCommissionRates fr
on cp.personId = fr.fitterId
and fr.organizationId = #orgId
where
r.categoryNo = 411
and r.statusNO = 76
and r.companyId = #orgId
and #Year in ( 0, year(r.Timestamp))
group by
p.personid
It APPEARS you are trying to get total commissions (and/or other amounts you are trying to aggregate) based on each given person (fitter). You want to see if there is a way to optimize vs writing 3 separate queries for the respective 3 columns you want aggregated, but all appear to be coming from the same commission paid table (which makes sense). By doing repeated conditional querying per column can be very resource intense, especially with larger data.
What you should probably do is a pre-aggregate of the commission based on the given person AND item based on its qualified criteria. This is done once for the whole query, THEN apply a join based on both parts matching the preceeding ProductRequestALl table content.
The outer query can apply a SUM() of those pre-queried amounts as the outer total is per single person, not based on the person and each "DoID" entry they had in their underlying commission records.
For the outermost query, since I am doing a group by person, but you want to see the name, I am applying a MAX() of the person's name. Since that will never change based on the ID, ID = 1 for "Steve" will always be "Steve", so applying a MAX() eliminates the need of adding the name portion to the GROUP BY clause.
Something like below, and obviously, I dont know your other columns you want aggregated, but think you can get the concept of it.
declare
#orgId int = 3,
#Year int = 2022;
select
p.personid,
max( p.firstName +' '+ p.lastname ) Fitter,
coalesce( sum( cp.PaidAmount ), 0 ) PaidAmount,
coalesce( sum( cp.SecondAmount ), 0 ) SecondAmount,
coalesce( sum( cp.ThirdAmount ), 0 ) ThirdAmount
from
txnhistory r
join productrequestall pr
on r.requestNo = pr.requestId
-- and cp.paymentType='Sales Commission'
join
(
SELECT
personid,
doNumber,
ROUND( SUM( amountPaid ), 3) PaidAmount,
ROUND( SUM( OtherAmount2 ), 3) SecondAmount,
ROUND( SUM( ThirdAmoundColumn ), 3) ThirdAmount
FROM
commissionPaid
where
commissionType = 'fitter'
and statusNo = 452
and #Year in ( 0, year(dispensedSignDate))
group by
personId,
doNumber ) cp
on pr.personid = cp.personid
AND pr.doId = cp.doNumber
join commissionratepaid crp
on cp.doNumber = crp.doNumber
and cp.commissionType = crp.commissionType
join employeecheck ec
on cp.checkNumber = ec.employeeCheckId
join person p
on cp.personId = p.personId
join fitterCommissionRates fr
on cp.personId = fr.fitterId
and fr.organizationId = #orgId
where
r.categoryNo = 411
and r.statusNO = 76
and r.companyId = #orgId
and #Year in ( 0, year(r.Timestamp))
group by
p.personid
Now, you also had an IFNULL() consideration, such as someone does not have any commission. If that is the case, do you still need to see that person even if they never earned a commission? If so, then a left-join MIGHT be applicable.
This query also does away with the "WITH CTE" construct.

TSQL: Join tables based on criteria

The resultset I am aiming to receive is this:
My table structure is like:
The INVOICE table has INVTYPE column which defines whether the invoice is a sales invoice or a purchase invoice by holding 101 for sale and 201 for purchase.
My query will use a where statement to also filter down the STOCK table to a particular STOCKCODE.
I am trying to filter out products (STOCK) with a STOCKCODE = "XYZ"
If I run
SELECT *
FROM STOCK
WHERE STOCKCODE = 'XYZ'
I get 152 results. So I have 152 products with a STOCKCODE of 'XYZ' for sure..
My problem is when I try to join these tables. For simplicity, I have only tried to get INVTYPE 101 to keep it simple but my desired outcome is still with the 201.
I have tried joins and subqueries such as:
SELECT
S.STOCKNO, S.STOCKNAME, ISNULL(SUM(IIT.QTYSOLD), 0) AS QTY,
ISNULL(SUM(IIT.TOTAL), 0) AS TOTAL
FROM
STOCK S
LEFT JOIN
INVOICE_ITEM IIT ON S.STOCKID = IIT.STOCKID
LEFT JOIN
INVOICE INV ON IIT.INVID = INV.INVID
WHERE
S.STOCKCODE = 'XYZ' AND INV.TRANSTYPE = 101
GROUP BY
S.STOCKNO, S.STOCKNAME
This query returns 122 results. So I know that there are 30 items in the stock table that have not been sold, which I need to show them in the resultset with 0 (zero) in the relevant columns.
I also tried this:
SELECT
S.STOCKNO, S.STOCKNAME,
(SELECT ISNULL(SUM(TOTAL), 0)
FROM INVOICE_ITEM IIT
WHERE IIT.STOCKID = S.STOCKID) AS TOTAL,
(SELECT ISNULL(SUM(QTYSOLD), 0)
FROM INVOICE_ITEM IIT
WHERE IIT.STOCKID = S.STOCKID) AS QTY
FROM
STOCK S
WHERE
S.STOCKCODE = 'XYZ'
This query return the full 152 results because I did not filter by INVTYPE it returned the items which were purchased as well.
I am using SQL Server 2014
So my question is how can I achieve my desired resultset? and what am I doing wrong with my joins?
Thanks
You need a conditional aggregation
SELECT S.STOCKNO,
S.STOCKNAME,
ISNULL(SUM(CASE WHEN INV.TRANSTYPE = 201 THEN IIT.TOTAL END), 0) AS TOTAL_PURCHASED,
ISNULL(SUM(CASE WHEN INV.TRANSTYPE = 101 THEN IIT.TOTAL END), 0) AS TOTAL_SOLD,
ISNULL(SUM(CASE WHEN INV.TRANSTYPE = 201 THEN IIT.QTYSOLD END),0) AS QTY_PURCHASED,
ISNULL(SUM(CASE WHEN INV.TRANSTYPE = 101 THEN IIT.QTYSOLD END),0) AS QTY_SOLD
FROM STOCK S
LEFT JOIN INVOICE_ITEM IIT ON S.STOCKID = IIT.STOCKID
LEFT JOIN INVOICE INV ON IIT.INVID= INV.INVID
WHERE S.STOCKCODE= 'XYZ'
GROUP BY S.STOCKNO, S.STOCKNAME
since you are not joining your inner tables (INVOICE_ITEM) with their primary key this is an expected behavior.
you either need to use INVITEMID column for your join, you need to add STOCKID and INVID together.
SELECT ISNULL(SUM(TOTAL),0) FROM INVOICE_ITEM IIT
WHERE IIT.STOCKID = S.STOCKID and IIT.INVID = INV.INVID
For performance, it is tempting to try:
select s.stockno, s.stockname,
coalesce(i.total_purchased, 0) as total_purchased,
coalesce(i.total_sold, 0) as total_sold,
coalesce(i.qty_purchased, 0) as qty_purchased,
coalesce(i.qty_sold, 0) as qty_sold
from stock s outer apply
(select sum(case when i.transtype = 201 then ii.total end) as total_purchased,
sum(case when i.transtype = 101 then ii.total end) as total_sold,
sum(case when i.transtype = 201 then ii.qtysold end) as qty_purchased,
sum(case when i.transtype = 101 then ii.qtysold end) as qty_sold
from invoice_item ii join
invoice i
on ii.invid = i.invid
where s.stockid = ii.stockid
) i
where stockcode = 'XYZ'

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')
)

How could I combine these 3 T-SQL statements into one?

I have 3 queries that I want to combine. 1 query is for total sales, 1 is for canceled orders, and 1 is for orders that don't include specific product types. Just need the total sales $$ to output in a table format as they are now. The only thing that changes between the 3 is the where statement. Thanks!
Edit: I realize I said "Just need the total sales $$" ... what I meant was I just need the sales $$ for each query in one table. So $x, $y, $z ... x is the total sales, y is the sales dollars that got cancelled, and z is the sales dollars for the specific items.
SELECT Sum((Items.Total+Items.Shipping)*OrderDetails.Quantity) AS Total
FROM Promo INNER JOIN (Orders INNER JOIN (Items INNER JOIN OrderDetails ON Items.ItemCode = OrderDetails.ItemCode) ON Orders.OrderNumber = OrderDetails.OrderNumber) ON Promo.Promo = Orders.Promo
WHERE ((Promo.OfferType)='Sale') AND ((Items.Date) Between '6/1/2010' And '12/31/2011');
SELECT Sum((Items.Total+Items.Shipping)*OrderDetails.Quantity) AS Canceled
FROM Promo INNER JOIN (Orders INNER JOIN (Items INNER JOIN OrderDetails ON Items.ItemCode = OrderDetails.ItemCode) ON Orders.OrderNumber = OrderDetails.OrderNumber) ON Promo.Promo = Orders.Promo
WHERE (((Promo.OfferType)='Sale') AND ((Items.Date) Between '6/1/2010' And '12/31/2011') AND ((Items.Status)="Canceled"));
SELECT Sum((Items.Total+Items.Shipping)*OrderDetails.Quantity) AS BadItems
FROM Promo INNER JOIN (Orders INNER JOIN (Items INNER JOIN OrderDetails ON Items.ItemCode = OrderDetails.ItemCode) ON Orders.OrderNumber = OrderDetails.OrderNumber) ON Promo.Promo = Orders.Promo
WHERE (((Promo.OfferType)='Sale') AND ((Items.Date) Between '6/1/2010' And '12/31/2011') AND ((Items.ProductType)<>2) AND ((Items.ProductType)<>6));
Thanks!
If you want the results on 1 row:
SELECT Total, Canceled, BadItems
FROM Query1, Query2, Query3
And if you want the results in 1 column, use a UNION:
Query1
UNION
Query2
UNION
Query3
Updated to reflect question clarification:
SELECT Sum(CASE WHEN Items.Status <> 'Canceled' AND Items.ProductType = 4 THEN (Items.Total+Items.Shipping)*OrderDetails.Quantity ELSE 0 END) AS Total ,
Sum(CASE WHEN Items.Status = 'Canceled' AND Items.ProductType = 4 THEN (Items.Total+Items.Shipping)*OrderDetails.Quantity ELSE 0 END) AS Canceled,
Sum(CASE WHEN Items.ProductType <> 4 THEN (Items.Total+Items.Shipping)*OrderDetails.Quantity ELSE 0 END) AS BadItems,
FROM Promo INNER JOIN (Orders INNER JOIN (Items INNER JOIN OrderDetails ON Items.ItemCode = OrderDetails.ItemCode) ON Orders.OrderNumber = OrderDetails.OrderNumber) ON Promo.Promo = Orders.Promo
WHERE ((Promo.OfferType)='Sale') AND ((Items.Date) Between '9/1/2010' And '12/31/2010');
try to use a procedure which will accept three values as input which will suite your were statements and it will give results in one table as want. if you are stuck let me know , i will help.