SQL Server - Struggling to group/sum values across multiple related tables - sql-server

I'm running this query:
SELECT
g.PartNum,
g.Supplier,
(g.InitialQuantityToInventory + SUM(COALESCE(t.AmountInventoryAdjusted + t.AmountReturned, 0))) AS StockLevel
FROM
GoodsIn g
LEFT JOIN
Transfers t ON g.GoodsInNumber = t.GoodsInNumber
WHERE
g.PartNum = '123'
GROUP BY
g.PartNum, g.Supplier, g.InitialQuantityToInventory
And it's returns these results:
123,SUP1,67
123,NULL,18
123,NULL,0
123,NULL,45
123,NULL,0
However I would like the StockLevel (in the 3rd column) to SUM on the supplier name, even when it's null, so that my expected result should be:
123,SUP1,67
123,NULL,63
What am I doing wrong? The query should (across all GoodsIn Numbers that have the same PartNumber & Supplier) Sum the InitialQuantities and their Amounts Adjusted & Returned for each transfer associated with the GoodsIn record.
This is the data for that part & supplier in the GoodsIn Table:
GINum Part Num Supplier InitialQuantityToInventory
73367 123 NULL 81
73570 123 NULL 18
74154 123 NULL 320
74835 123 Sup1 0
74836 123 NULL 500
75738 123 Sup1 0
And this is the corresponding rows from the Transfers table (T being short for TransferNum):
GINum T Adj Ret
73367 1 -81 0
74154 1 -200 0
74154 2 -120 45
74835 1 67 0
74836 1 -500 0
75738 1 -300 0
75738 2 300 0

Do you need to not group by InitialQuantityToInventory?
;WITH Logs (Supplier, Initial, StockChange)
AS
(
SELECT
g.Supplier,
g.InitialQuantityToInventory,
(SUM(COALESCE(t.AmountInventoryAdjusted + t.AmountReturned,0))) AS StockChange
FROM
GoodsIn g
LEFT JOIN
Transfers t ON g.GoodsInNumber = t.GoodsId
GROUP BY
g.Supplier, g.InitialQuantityToInventory
)
SELECT
Supplier,
SUM(Initial) + SUM(StockChange) AS StockLevel
FROM
Logs
GROUP BY
Supplier
What might be happening is you're getting a row per unique InitialQuantityToInventory, but from what I gather, you want these to be summed, right?
This could probably be optimised further (and probably doesn't need a CTE), but hopefully it at least returns the data you expect.

I don't think you need to GROUP BY InitialQuantityToInventory. Try following query and let me know if this works for what you need.
SELECT
g.PartNum,
g.Supplier,
MAX(g.InitialQuantityToInventory) + SUM(COALESCE(t.AmountInventoryAdjusted + t.AmountReturned,0)) AS StockLevel
FROM
GoodsIn g
LEFT JOIN
Transfers t ON g.GoodsInNumber = t.GoodsInNumber
WHERE
g.PartNum = '123'
GROUP BY
g.PartNum, g.Supplier

Try this, group the Transfers before joining and grouping
SELECT
g.PartNum,
g.Supplier,
SUM(g.InitialQuantityToInventory + COALESCE(t.adjRet, 0))
FROM Goodsin g
LEFT JOIN
(
SELECT GINum, SUM(AmountInventoryAdjusted + AmountReturned) AS adjRet
FROM Transfers
GROUP BY GINum
) T
ON t.GINum = g.GiNum
GROUP BY g.PartNum, g.Supplier

Related

Join in SQL Server when some query doesn't have results

I have a table with stock product movements (MOVSTOCKS) in two warehouses (CodAlm). For simplify the question, I will focus on a single product (with CodArt = C5):
CodArt
DescArt
CodAlm
UnidadesStock
EntranStock
SalenStock
FecDoc
TipDoc
C5
Palet
1
16
16
0
2021-12-31
IN
C5
Palet
2
0
0
0
2021-12-31
IN
C5
Palet
1
3
0
3
2022-01-11
SL
C5
Palet
1
4
0
4
2022-01-20
SL
C5
Palet
1
7
7
0
2022-02-01
EN
C5
Palet
1
6
0
6
2022-02-14
SL
C5
Palet
1
9
9
10
2022-05-01
IN
C5
Palet
2
1
1
0
2022-05-01
IN
C5
Palet
1
2
0
2
2022-06-10
SL
I need to get the stock on a certain day. For this, is necessary obtain stock quantity of the last inventory (TipDoc = IN) and add it purchases quantities (TipDoc = EN) and subtract the sales (TipDoc = SL).
I tried this query:
SELECT MV.CODART, MV.DESCART, MV.CODALM, SC.UNIDADESSTOCK + SUM(MV.ENTRANSTOCK) - SUM(MV.SALENSTOCK) as STOCK
FROM MOVSTOCKS MV
JOIN ( SELECT MV1.CODART, MV1.CODALM, MV2.FECDOC, MV1.UNIDADESSTOCK
FROM MOVSTOCKS MV1
JOIN ( SELECT CODART, CODALM, MAX(FECDOC) FECDOC
FROM MOVSTOCKS
WHERE TIPDOC = 'IN'
GROUP BY CODART, CODALM) MV2
ON MV1.CODART = MV2.CODART AND MV1.CODALM = MV2.CODALM AND MV1.FECDOC = MV2.FECDOC
WHERE MV1.TIPDOC = 'IN' ) SC
ON MV.CODART = SC.CODART AND MV.CODALM = SC.CODALM AND MV.FECDOC > SC.FECDOC
WHERE MV.CODART = 'C5' and MV.FECDOC <= '2022-06-01'
GROUP BY MV.CODART, MV.DESCART, MV.CODALM, SC.UNIDADESSTOCK
ORDER BY MV.CODART, MV.CODALM
With above data example and the query I expected to get following results:
CodArt
DescArt
CodAlm
Stock
C5
Palet
1
9
C5
Palet
2
1
The problem is that after the last inventory (2022-05-01) there have been no movements and then the join query get 0 rows because the filter MV.FECDOC <= '2022-06-01' in the WHERE doesn't get rows. I could modify the 'ON' condition in the join to MV.FECDOC >= SC.FECDOC and then get at least the inventory row, but I shouldn't do that because on inventory day there might be other previous movements that I shouldn't get for stock calculation.
Moreover, I will have the same problem if I want to get stock in a date for a product without inventory movements, because subquery 'SC' won't get rows.
Any help, please?
If you know that the inventory records exist, but the other transaction records might or might not exist afterwards, you will need to select your inventory records first and then LEFT JOIN the other transaction records.
If we only had to select one inventory record, we could start with a SELECT TOP 1 * ... ORDER BY FECDOC DESC with appropriate additional conditions. However, to get the latest inventory record for each of multiple warehouses, it get a bit more complicated. Two approaches that come to mind.
One would be to first select distinct warehouse codes (or perhaps distinct warehouse and product codes) and then CROSS APPLY a subselect to retrieve the latest inventory record for each.
The other approach would be to use the ROW_NUMBER() window function to number the inventory records by descending date (partitioned by warehouse codes and product codes) and then exclude all but row number = 1.
In either case, the next step would be to LEFT JOIN the transaction records, apply a GROUP BY, and SUM() up the results. Since SUM() returns NULL if there are no elements to sum, we need to use the ISNULL() function to assign a default value of zero.
The following two similar queries demonstrate the above approaches.
DECLARE #CODART VARCHAR(10) = 'C5'
DECLARE #AsOfDate DATETIME2 = '2022-06-01'
-- Method 1: SELECT DISTINCT followed by a cross apply
SELECT
SC.CODART, SC.DESCART, SC.CODALM,
SC.UNIDADESSTOCK + ISNULL(SUM(MV2.ENTRANSTOCK), 0) - ISNULL(SUM(MV2.SALENSTOCK), 0) as STOCK
FROM (
SELECT DISTINCT MV.CODART, MV.CODALM
FROM #MOVSTOCKS MV
WHERE MV.CODART = #CODART
) A
CROSS APPLY (
-- Most recent inventory for each selected CODART, CODALM
SELECT TOP 1 MV1.*
FROM #MOVSTOCKS MV1
WHERE MV1.CODART = A.CODART
AND MV1.CODALM = A.CODALM
AND MV1.TIPDOC = 'IN'
AND MV1.FECDOC <= #AsOfDate
ORDER BY MV1.FECDOC DESC
) SC
LEFT JOIN #MOVSTOCKS MV2
ON MV2.CODART = SC.CODART
AND MV2.CODALM = SC.CODALM
AND MV2.TIPDOC IN ('EN', 'SL')
AND MV2.FECDOC > SC.FECDOC
AND MV2.FECDOC <= #AsOfDate
GROUP BY SC.CODART, SC.DESCART, SC.CODALM, SC.UNIDADESSTOCK
ORDER BY SC.CODART, SC.CODALM
-- Method 2: Using the ROW_NUMBER() window function
SELECT
SC.CODART, SC.DESCART, SC.CODALM,
SC.UNIDADESSTOCK + ISNULL(SUM(MV2.ENTRANSTOCK), 0) - ISNULL(SUM(MV2.SALENSTOCK), 0) as STOCK
FROM (
-- Most recent inventory at or before #AsOfDate will have Recency = 1
SELECT MV1.*,
ROW_NUMBER() OVER(PARTITION BY CODART, CODALM ORDER BY FECDOC DESC) AS Recency
FROM #MOVSTOCKS MV1
WHERE MV1.FECDOC <= #AsOfDate
AND MV1.TIPDOC = 'IN'
) SC
LEFT JOIN #MOVSTOCKS MV2
ON MV2.CODART = SC.CODART
AND MV2.CODALM = SC.CODALM
AND MV2.FECDOC > SC.FECDOC
AND MV2.FECDOC <= #AsOfDate
AND MV2.TIPDOC IN ('EN', 'SL')
WHERE SC.Recency = 1
AND SC.CODART = #CODART
GROUP BY SC.CODART, SC.DESCART, SC.CODALM, SC.UNIDADESSTOCK
ORDER BY SC.CODART, SC.CODALM
Both queries above produce the desired result. See this db<>fiddle for a working demo.
*** UPDATE *** To handle cases where we might have transactions (added stock and/or sales) but no prior reference inventory, we will need to assume an initial inventory of zero and include all transactions since the beginning-of-time (start or data). This also requires making the initial SELECT DISTINCT the primary source for product and warehouse information. The CROSS APPLY becomes an OUTER APPLY (similar to a LEFT JOIN) and we need to make other adjustments to handle a possible null inventory record. That includes adjusting the date range for the transaction join.
The updated query would be something like:
SELECT
A.CODART, A.DESCART, A.CODALM,
ISNULL(SC.UNIDADESSTOCK, 0) + ISNULL(SUM(MV2.ENTRANSTOCK), 0) - ISNULL(SUM(MV2.SALENSTOCK), 0) as STOCK
, SC.UNIDADESSTOCK AS PriorInventory
, SUM(MV2.ENTRANSTOCK) AS NewStock
, SUM(MV2.SALENSTOCK) as Sales
FROM (
SELECT DISTINCT MV.CODART, MV.DESCART, MV.CODALM
FROM #MOVSTOCKS MV
WHERE MV.CODART = #CODART
) A
OUTER APPLY (
-- Most recent inventory for each selected CODART, CODALM combination
SELECT TOP 1 MV1.*
FROM #MOVSTOCKS MV1
WHERE MV1.CODART = A.CODART
AND MV1.CODALM = A.CODALM
AND MV1.TIPDOC = 'IN'
AND MV1.FECDOC <= #AsOfDate
ORDER BY MV1.FECDOC DESC
) SC
LEFT JOIN #MOVSTOCKS MV2
-- Transactions since inventory was last recorded
ON MV2.CODART = A.CODART
AND MV2.CODALM = A.CODALM
AND MV2.TIPDOC IN ('EN', 'SL') -- Alternately <> 'IN' or omitted entirely.
AND MV2.FECDOC > ISNULL(SC.FECDOC, '1900-01-01')
AND MV2.FECDOC <= #AsOfDate
GROUP BY A.CODART, A.DESCART, A.CODALM, SC.UNIDADESSTOCK
ORDER BY A.CODART, A.CODALM
See this db<>fiddle for an updated demo. For this demo, I added a new stock record for warehouse 3 and set the #AsOfDate to '2022-12-31'. I also added details to the result showing the separate initial inventory, new stock, and sales that feed the final STOCK calculation.
CODART
DESCART
CODALM
STOCK
PriorInventory
NewStock
Sales
C5
Palet
1
7
9
0
2
C5
Palet
2
1
1
null
null
C5
Palet
3
6
null
6
0
The above includes cases having inventory with transactions, inventory but no transactions, and transactions with no initial inventory.
I abandoned the ROW_NUMBER() alternate approach.

SQL query to show good records as well as null records

My query works perfectly well to find records with real values, however, I also need my query to show records with null values. So far my attempts at recreating this query to also show null values has resulted in losing at least 1 of my columns of results so now I'm looking for help.
This is my query so far:
SELECT sq.*, sq.TransactionCountTotal - sq.CompleteTotal as InProcTotal from
(
select
c.CustName,
t.[City],
sum (t.TransactionCount) as TransactionCountTotal
sum (
case
when (
[format] in (23,25,38)
or [format] between 400 and 499
or format between 800 and 899
)
then t.TransactionCount
else 0
end
) as CompleteTotal
FROM [log].[dbo].[TransactionSummary] t
INNER JOIN [log].[dbo].[Customer] c
on t.CustNo = c.CustNo
and t.City = c.City
and t.subno = c.subno
where t.transactiondate between '7/1/16' and '7/11/16'
group by c.CustName,t.City
) sq
This is currently what my query results show:
CustName City InProcTotal TransactionCountTotal Complete Total
Cust 1 City(a) 23 7 30
Cust 2 City(b) 74 2 76
Cust 3 City(c) 54 4 58
This is what I want my query results to show:
CustName City InProcTotal TransactionCountTotal Complete Total
Cust 1 City(a) 23 7 30
Cust 2 City(b) 74 2 76
Cust 3 City(c) 54 4 58
Cust 4 City(d) 0 0 0
Cust 5 City(e) 0 0 0
I suggest you use RIGHT JOIN in the place of INNER JOIN. You should then retain the rows from Customer that don't have matching rows in TransactionSummary.
You may also want to refactor the query like this so you use LEFT JOIN. The next person to work on the query will thank you; LEFT JOIN operations are more common.
FROM [log].[dbo].[Customer] c
LEFT JOIN [log].[dbo].[TransactionSummary] t
on t.CustNo = c.CustNo
and t.City = c.City
jwabsolution, your issue stems from grabbing all transactions instead of all customers. My mind works in this way: you want to select all of the customers & find all transaction states. Therefore, you should be selecting from the customer table. Also, you shouldn't use the INNER JOIN or you will ignore any customers that don't have transactions. Instead, use left join the transactions table. In this manner, you will retrieve all customers (even those with no transactions). Here is a good visual for SQL joins: http://www.codeproject.com/KB/database/Visual_SQL_Joins/Visual_SQL_JOINS_orig.jpg
So your query should look like this:
SELECT sq.*, sq.TransactionCountTotal - sq.CompleteTotal as InProcTotal from
(
select
c.CustName,
t.[City],
sum (t.TransactionCount) as TransactionCountTotal
sum (
case
when (
[format] in (23,25,38)
or [format] between 400 and 499
or format between 800 and 899
)
then t.TransactionCount
else 0
end
) as CompleteTotal
FROM [log].[dbo].[Customer] c
LEFT JOIN [log].[dbo].[TransactionSummary] t
on c.CustNo = t.CustNo
and c.City = t.City
and c.subno = t.subno
where t.transactiondate between '7/1/16' and '7/11/16'
group by c.CustName,t.City
) sq
Fixed it. Needed to use coalesce to get the values to show up properly.
Also added a "where" option if I want to query individual customers
SELECT sq.* ,sq.TransactionCountTotal - sq.CompleteTotal as [InProcTotal]
from
(
select
c.custname
,c.port
,sum(coalesce(t.transactioncount,0)) as TransactionCountTotal
,sum(
case when (
[format]in(23,25,38)
or[format]between 400 and 499
or[format]between 800 and 899)
then t.TransactionCount
else 0
end) as CompleteTotal
from log.dbo.customer c
left join log.dbo.TransactionSummary t
on c.custNo=t.custno
and c.subno=t.subno
and c.city=t.city
and t.transactiondate between '7/1/16' and '7/12/16'
/*where c.custname=''*/
group by c.custname,c.city
) sq

How to select distinct values after left outer join operation

I want to select some values from three tables with aggregate function but without duplicates in one of the columns, for example:
select t3.ValueDesc as FeatureType,
count(t2.Strategic) as TotalCount
,t2.RequestID,t1.StoryID --these are not needed, but put for better vision
from tblRequests t2
left outer join (select * from tblAgileMultiDD where Type=18) t3
on t3.FormulaValue = t2.Strategic
left outer join tblAgileStory t1
on t1.Feature = t2.RequestID
where t2.RequestID > 0
and t1.DemoStatus = 1
group by t3.ValueDesc
,t2.RequestID, t1.StoryID --these are not needed but put for better vision
order by t3.ValueDesc
And then it returns me something like this:
FeatureType TotalCount RequestID StoryID
Protect Base 1 311 1629
Protect Base 1 311 1630
Protect Base 1 312 1631
Protect Base 1 312 1637
New Market 1 313 1640
New Market 1 313 1645
And if I comment out lines with ",t2.RequestID, t1.StoryID", it gives me the result:
FeatureType TotalCount
Protect Base 4
New Market 2
So, for each unique combination of RequestID and StoryID it returns new row. How to make it return new row only for each unique RequestID regardless to StoryID?
So I want this query to result like this:
FeatureType TotalCount
Protect Base 2 (for RequestID = 311, 312)
New Market 1 (for RequestID = 313)
Putting word "distinct" at the beginning doesn't take effect on it.
Can you help me with this?
select distinct FeatureType,TotalCount from (
select t3.ValueDesc as FeatureType,
count(t2.Strategic) as TotalCount
,t2.RequestID
-- ,t1.StoryID --these are not needed, but put for better vision
from tblRequests t2
left outer join (select * from tblAgileMultiDD where Type=18) t3
on t3.FormulaValue = t2.Strategic
left outer join tblAgileStory t1
on t1.Feature = t2.RequestID
where t2.RequestID > 0
and t1.DemoStatus = 1
group by t3.ValueDesc
,t2.RequestID
-- , t1.StoryID --these are not needed but put for better vision
) as T
order by t3.ValueDesc
could you try this.

Select Count Top Inner Join and Where Clause in SQL

This is my Query:
SELECT TOP 3 tablestudentanswer.examid,
tablestudentanswer.studentid,
tablestudentanswer.itemno,
tablestudentanswer.studentanswer,
tablescore.score
FROM tablestudentanswer
INNER JOIN tablescore
ON tablestudentanswer.studentid = tablescore.studentid
AND tablestudentanswer.examid = tablescore.examid
WHERE tablestudentanswer.examid = 1
AND tablestudentanswer.itemno = 1
ORDER BY tablescore.score ASC
It returns this table:
ExamID StudentID ItemNo StudentAnswer Score
1006 1 1 A 25
1005 1 2 B 30
1004 1 3 A 35
What i want to do is it will return 2 if StudentAnswer='A' and 1 if StudentAnswer='B'
Guys there is nothing wrong with my query on top. What i am asking is what should I add in that query.
I have this which in my mind should return 2 but its an error.
Select COUNT(*) From (
Select Top 3 TableStudentAnswer.ExamID, TableStudentAnswer.StudentID, TableStudentAnswer.ItemNo, TableStudentAnswer.StudentAnswer, TableScore.Score
from TableStudentAnswer
Inner join TableScore on TableStudentAnswer.StudentID=TableScore.StudentID and TableStudentAnswer.ExamID=TableScore.ExamID
where TableStudentAnswer.ExamID=1 and TableStudentAnswer.ItemNo=1
Order By TableScore.Score Asc) where TableStudentAnswer.StudentAnswer = 'A'
It should return:
2
Please help me!
Will this do?
SELECT TOP 3 tablestudentanswer.examid,
tablestudentanswer.studentid,
tablestudentanswer.itemno,
tablestudentanswer.studentanswer,
tablescore.score,
case
when tablestudentanswer.studentanswer = 'A' then 2
when tablestudentanswer.studentanswer = 'B' then 1
else NULL
end as [MyColumn]
FROM tablestudentanswer
INNER JOIN tablescore
ON tablestudentanswer.studentid = tablescore.studentid
AND tablestudentanswer.examid = tablescore.examid
WHERE tablestudentanswer.examid = 1
AND tablestudentanswer.itemno = 1
ORDER BY tablescore.score ASC
Your question is a bit unclear. Perhaps you want the amount of answers for each?
count(1) over (partition by tablestudentanswer.studentanswer)
This will give you a column with the amount of all the answers with the given studentanswer to each of the rows in the result set. However, note that this could be quite slow. If you can, you're better off using a normal group by.
Do you mean you would like the query to return the number of answers? If so, using COUNT may help.
SELECT tablestudentanswer.studentid,
tablestudentanswer.studentanswer
COUNT(1) AS NumberOfAnswers
FROM tablestudentanswer
INNER JOIN tablescore
ON tablestudentanswer.studentid = tablescore.studentid
AND tablestudentanswer.examid = tablescore.examid
GROUP BY tablestudentanswer.studentid, tablestudentanswer.studentanswer
Please correct me if I am wrong.
By the way, why does your result table doesn't consist of itemno even though you have it in your SELECT statement?

SQL Server partition unnormalized data

I think there might be a way to partition this to find what I want, but I can't see it. I have some slightly unnormalized data about product sets:
SetItemID ComponentItemID quantity IsPrimary
123 234 1 1
123 345 2 0
456 567 3 1
456 678 2 0
I need to find sets where the quantity's described are, for example, IsPrimary component's quantity > 'IsPrimary=0component'squantity. I also need to find sets where quantities are equal between components, etc. It's ok to use multiple statements for this, if necessary
All I have so far is a partial PARTITION statement that may or may not be the right way to do this, but I can't figure out how to compare quantities within sets:
WITH setdata as
(select *, row_number() OVER(
PARTITION by s.setitemid order by s.setitemid) position from set_table s )
// descending just to get newer sets
SELECT* from setdata order by setitemid desc
If I'm following your requirements right, this should do it:
SELECT p1.SetItemId
from (-- IsPrimary = 1
select SetItemID, ComponentItemId, Quantity
from SetData
where IsPrimary = 1) p1
inner join (-- IsPrimary = 0
select SetItemID, ComponentItemId, Quantity
from SetData
where IsPrimary = 0) p0
on p0.SetItemID = p1.SetItemID
and p1.Quantity > p0.Quantity
--and p1.Quantity = p0.Quantity
Use that last line for sets with equal quantities.
SELECT *
FROM set_table a
WHERE isPrimary = 1
AND quantity > (SELECT quantity
FROM set_table b
WHERE a.setItemId = b.setItemId
AND isPrimary = 0)
ORDER BY setItemId DESC
Or
SELECT a.*
FROM set_table a
INNER JOIN set_table b
ON a.setItemId = b.setItemId
AND a.isPrimary = 1
AND b.isPrimary = 0
WHERE a.quantity > b.quantity

Resources