I have the following table
Order_ID Loc_ID OrderDate ShippingDate DeliveryDate
10 2 10/12/2018 null null
10 2 null null 18/12/2018
10 2 null 12/13/2019 null
Basically, every time a date is recorded, it is added as a row. I want the table to look like this:
Order_ID Loc_ID Order_Date ShippingDate DeliveryDate
10 2 10/12/2018 13/12/2018 18/12/2018
Can someone tell me how I should do this?
Use MAX:
SELECT Order_ID,
Loc_ID,
MAX(OrderDate) AS OrderDate,
MAX(ShippingDate) AS ShippingDate,
MAX(DeliveryDate) AS DeliveryDate
FROM dbo.YourTable
GROUP BY Order_ID,
Loc_ID;
When ordering data NULL has the lowest value, so any non-NULL value will have a "greater" value. As a result MAX will return the non-NULL value.
A simple aggregation should do the trick
Example
Select Order_ID
,Loc_ID
,OrderDate = max(OrderDate)
,ShippingDate = max(ShippingDate)
,DeliveryDate = max(DeliveryDate)
From YourTable
Group By Order_ID,Loc_ID
I'm new to T-SQL and need help converting an excel report to a run on SQL. I have a SQL table that records all the daily inventory transactions (in/out) from each stockroom. I need to create a report that list the current inventory levels for each product in each location and the qty in each place as follows. In other words, the current inventory levels of each place.
I also need help on how to insert the Preferred Out Report (below) into SQL Server as a view so I can run this each month over and over again.
Thanks in Advance!
Inventory Log table:
PubID QTY LocationID Transaction
1 10 1 Add
1 20 2 Add
1 30 3 Add
1 5 1 Sold
1 10 2 Sold
1 5 3 Sold
2 10 1 Add
2 10 2 Add
2 5 2 Sold
2 8 2 Sold
1 20 1 Add
1 20 2 Add
2 2 2 Sold
Preferred Output Table:
PubID Local_1 Local_2 Local_3 Total
1 25 30 25 80
2 5 0 0 5
Total 30 30 25 85
I see a lot of close examples here but most just add the value while I need to subtract the Sold inventory from the Added stock to get my totals in each column.
The row totals and column totals on the right and bottom are pluses but not needed if it's easier without.
THANKS!
If this was about aggregation without pivoting, you could use a CASE expression, like this:
SELECT
...
Local_1 = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END),
...
FROM ...
GROUP BY ...
However, in the PIVOT clause, the argument of the aggregate function must be just a column reference, not an expression. You can work around that by transforming the original dataset so that QTY is either positive or negative, depending on Transaction:
SELECT
PubID,
QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
LocationID
FROM dbo.InventoryLog
The above query will give you a result set like this:
PubID QTY LocationID
----- --- ----------
1 10 1
1 20 2
1 30 3
1 -5 1
1 -10 2
1 -5 3
2 10 1
2 10 2
2 -5 2
2 -8 2
1 20 1
1 20 2
2 -2 2
which is now easy to pivot:
WITH prepared AS (
SELECT
PubID,
QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
LocationID
FROM dbo.InventoryLog
)
SELECT
PubID,
Local_1 = [1],
Local_2 = [2],
Local_3 = [3]
FROM prepared
PIVOT
(
SUM(QTY)
FOR LocationID IN ([1], [2], [3])
) AS p
;
Note that you could actually prepare the names Local_1, Local_2, Local_3 beforehand and avoid renaming them in the main SELECT. Assuming they are formed by appending the LocationID value to the string Local_, here's an example of what I mean:
WITH prepared AS (
SELECT
PubID,
QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
Name = 'Local_' + CAST(LocationID AS varchar(10))
FROM dbo.InventoryLog
)
SELECT
PubID,
Local_1,
Local_2,
Local_3
FROM prepared
PIVOT
(
SUM(QTY)
FOR Name IN (Local_1, Local_2, Local_3)
) AS p
;
You will see, however, that in this solution renaming will be needed at some point anyway, so I'll use the previous version in my further explanation.
Now, adding the totals to the pivot results as in your desired output may seem a little tricky. Obviously, the column could be calculated simply as the sum of all the Local_* columns, which might actually not be too bad with a small number of locations:
WITH prepared AS (
SELECT
PubID,
QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
LocationID
FROM dbo.InventoryLog
)
SELECT
PubID,
Local_1 = [1],
Local_2 = [2],
Local_3 = [3]
Total = COALESCE([1], 0)
+ COALESCE([2], 0)
+ COALESCE([3], 0)
FROM prepared
PIVOT
(
SUM(QTY)
FOR LocationID IN ([1], [2], [3])
) AS p
;
(COALESCE is needed because some results may be NULL.)
But there's an alternative to that, where you don't have to list all the locations explicitly one extra time. You could return the totals per PubID alongside the details in the prepared dataset using SUM() OVER (...), like this:
WITH prepared AS (
SELECT
PubID,
QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
LocationID,
Total = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END)
OVER (PARTITION BY PubID)
FROM dbo.InventoryLog
)
…
or like this, if you wish to avoid repetition of the CASE expression:
WITH prepared AS (
SELECT
t.PubID,
QTY = x.AdjustedQTY,
t.LocationID,
Total = SUM(x.AdjustedQTY) OVER (PARTITION BY t.PubID)
FROM dbo.InventoryLog AS t
CROSS APPLY (
SELECT CASE t.[Transaction] WHEN 'Add' THEN t.QTY ELSE -t.QTY END
) AS x (AdjustedQTY)
)
…
Then you would just include the Total column into the main SELECT clause along with the pivoted results and PubID:
…
SELECT
PubID,
Local_1,
Local_2,
Local_3,
Total
FROM prepared
PIVOT
(
SUM(QTY)
FOR LocationID IN ([1], [2], [3])
) AS p
;
That would be the total column for you. As for the row, it is actually easy to add it when you are acquainted with the ROLLUP() grouping function:
…
SELECT
PubID,
Local_1 = SUM([1]),
Local_2 = SUM([2]),
Local_3 = SUM([3]),
Total = SUM(Total)
FROM prepared
PIVOT
(
SUM(QTY)
FOR LocationID IN ([1], [2], [3])
) AS p
GROUP BY ROLLUP(PubID)
;
The total row will have NULL in the PubID column, so you'll again need COALESCE to put the word Total instead (only if you want to return it in SQL; alternatively you could substitute it in the calling application):
…
PubID = COALESCE(CAST(PubID AS varchar(10)), 'Total'),
…
And that would be all. To sum it up, here is a complete query:
WITH prepared AS (
SELECT
PubID,
QTY = x.AdjustedQTY,
t.LocationID,
Total = SUM(x.AdjustedQTY) OVER (PARTITION BY t.PubID)
FROM dbo.InventoryLog AS t
CROSS APPLY (
SELECT CASE t.[Transaction] WHEN 'Add' THEN t.QTY ELSE -t.QTY END
) AS x (AdjustedQTY)
)
SELECT
PubID = COALESCE(CAST(PubID AS varchar(10)), 'Total'),
Local_1 = SUM([1]),
Local_2 = SUM([2]),
Local_3 = SUM([3]),
Total = SUM(Total)
FROM prepared
PIVOT
(
SUM(QTY)
FOR LocationID IN ([1], [2], [3])
) AS p
GROUP BY ROLLUP(PubID)
;
As a final touch to it, you may want to apply COALESCE to the SUMs as well, to avoid returning NULLs in your data (if that is necessary).
The query below does what you need. I might have had one extra group by that could be combined into 1 but you get the idea.
DECLARE #InventoryLog TABLE
(
PubId INT,
Qty INT,
LocationId INT,
[Transaction] Varchar(4)
)
DECLARE #LocationTable TABLE
(
Id INT,
Name VarChar(10)
)
INSERT INTO #LocationTable
VALUES
(1, 'LOC_1'),
(2, 'LOC_2'),
(3, 'LOC_3')
INSERT INTO #InventoryLog
VALUES
(1 , 10, 1 , 'Add'),
(1 , 20, 2 , 'Add'),
(1 , 30, 3 , 'Add'),
(1 , 5 , 1 , 'Sold'),
(1 , 10, 2 , 'Sold'),
(1 , 5 , 3 , 'Sold'),
(2 , 10, 1 , 'Add'),
(2 , 10, 2 , 'Add'),
(2 , 5 , 2 , 'Sold'),
(2 , 8 , 2 , 'Sold'),
(1 , 20, 1 , 'Add'),
(1 , 20, 2 , 'Add'),
(2 , 2 , 2 , 'Sold')
SELECT PubId,
lT.Name LocationName,
CASE
WHEN [Transaction] ='Add' Then Qty
WHEN [Transaction] ='Sold' Then -Qty
END as Quantity
INTO #TempInventoryTable
FROM #InventoryLog iL
INNER JOIN #LocationTable lT on iL.LocationId = lT.Id
SELECT * INTO #AlmostThere
FROM
(
SELECT PubId,
ISNULL(LOC_1,0) LOC_1,
ISNULL(LOC_2,0) LOC_2,
ISNULL(LOC_3,0) LOC_3,
SUM(ISNULL(LOC_1,0) + ISNULL(LOC_2,0) + ISNULL(LOC_3,0)) AS TOTAL
FROM #TempInventoryTable s
PIVOT
(
SUM(Quantity)
FOR LocationName in (LOC_1,LOC_2,LOC_3)
) as b
GROUP BY PubId, LOC_1, LOC_2, LOC_3
) b
SELECT CAST(PubId as VARCHAR(10))PubId,
LOC_1,
LOC_2,
LOC_3,
TOTAL
FROM #AlmostThere
UNION
SELECT ISNULL(CAST(PubId AS VARCHAR(10)),'TOTAL') PubId,
[LOC_1]= SUM(LOC_1),
[LOC_2]= SUM(LOC_2),
[LOC_3]= SUM(LOC_3),
[TOTAL]= SUM(TOTAL)
FROM #AlmostThere
GROUP BY ROLLUP(PubId)
DROP TABLE #TempInventoryTable
DROP TABLE #AlmostThere
PubId LOC_1 LOC_2 LOC_3 TOTAL
1 25 30 25 80
2 10 -5 0 5
TOTAL 35 25 25 85
Sql Fiddle
Here is another approach: aggregate the data before pivoting, then pivot the aggregated results.
Compared to my other suggestion, this method is much simpler syntactically, which may also make it easier to understand and maintain.
All the aggregation is done with the help of the CUBE() grouping function. The basic query would be this:
SELECT
PubID,
LocationID,
QTY = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END)
FROM dbo.InventoryLog
GROUP BY CUBE(PubID, LocationID)
You can see the same CASE expression as in my other answer, only this time it can be directly used as the argument of SUM.
Using aggregation by CUBE gives us not only the totals by (PubID, LocationID), but also by PubID and LocationID separately, as well as the grand total. This is the result of the query for the example in your question:
PubID LocationID QTY
----- ---------- ---
1 1 35
2 1 10
NULL 1 45
1 2 50
2 2 25
NULL 2 75
1 3 35
NULL 3 35
NULL NULL 155
1 NULL 120
2 NULL 35
Rows with NULLs in LocationID are row totals in the final result set, and those with NULLs in PubID are column totals. The row with NULLs in both columns is the grand total.
Before we can proceed with the pivoting, we need to prepare column names for the pivoted results. If the names are supposed to be derived from the values of LocationID, the following declaration will replace LocationID in the original query's SELECT clause:
Location = COALESCE('Local_' + CAST(LocationID AS varchar(10)), 'Total')
We can also substitute 'Total' for the NULLs in PubID at this same stage, so this will replace PubID in the SELECT clause:
PubID = COALESCE(CAST(PubID AS varchar(10)), 'Total')
Now the results will look like this:
PubID LocationID QTY
----- ---------- ---
1 Local_1 35
2 Local_1 10
Total Local_1 45
1 Local_2 50
2 Local_2 25
Total Local_2 75
1 Local_3 35
Total Local_3 35
Total Total 155
1 Total 120
2 Total 35
and at this point everything is ready to apply PIVOT. This query transforms the above result set according to the desired format:
WITH aggregated AS (
SELECT
PubID = COALESCE(CAST(PubID AS varchar(10)), 'Total'),
Location = COALESCE('Local_' + CAST(LocationID AS varchar(10)), 'Total'),
QTY = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END)
FROM dbo.InventoryLog
GROUP BY CUBE(PubID, LocationID)
)
SELECT
PubID,
Local_1,
Local_2,
Local_3,
Total
FROM aggregated
PIVOT (
MAX(QTY)
FOR Location IN (Local_1, Local_2, Local_3, Total)
) AS p
;
This query will return NULLs for missing combinations of (PubID, LocationID). If you want to return 0 instead, apply COALESCE to the result of SUM in the definition of aggregated.
I'm a bit stumped about how to solve this particular piece of a problem I'm working on. I started with a much bigger problem, but I managed to simplify it into this while keeping good performance intact.
Say I have the following result set. AggregateMe is something I'm deriving from SQL conditionals.
MinutesElapsed AggregateMe ID Type RowNumber
1480 1 1 A 1
1200 0 1 A 2
1300 0 1 B 3
1550 0 1 C 4
725 1 1 A 5
700 0 1 A 6
1900 1 2 A 7
3300 1 2 A 8
4900 0 2 A 9
If AggregateMe is 1 (true) or, if you prefer, if is true, I want the counts to be aggregated into the next row where AggregateMe (or conditions) do not evaluate to true.
Aggregate functions or Subqueries are fair game as is PARTITION BY.
For example, the above result set would become:
MinutesElapsed ID Type
2680 1 A
1300 1 B
1550 1 C
1425 1 A
10100 2 A
Is there a clean way to do this? If you want, I can share more about the original problem, but it is a bit more complicated.
Edited to add: SUM and GROUP BY alone won't work, because some sums would be rolled into the wrong row. My sample data did not reflect this case, so I added rows where this case can occur. In the updated sample data, using an aggregate function in the simplest way would cause the 2680 count and the 1425 count to be rolled together, which I do not want.
EDIT: And if you're wondering how I got here in the first place, here you go. I'm going to aggregate statistics about how long our program left something in a certain ActionType, and my first step was by creating this subquery. Please feel free to criticize:
select
ROW_NUMBER() over(order by claimid, insertdate asc) as RowNbr,
DateDiff(mi, ahCurrent.InsertDate, CASE WHEN ahNext.NextInsertDate is null THEN GetDate() ELSE ahNext.NextInsertDate END) as MinutesInActionType,
ahCurrent.InsertDate, ahNext.NextInsertDate,
ahCurrent.ClaimID, ahCurrent.ActionTypeID,
case when ahCurrent.ActionTypeID = ahNext.NextActionTypeID and ahCurrent.ClaimID = ahNext.NextClaimID then 1 else 0 end as aggregateme
FROM
(
select ROW_NUMBER () over(order by claimid, insertdate asc) as RowNum, ClaimID, InsertDate, ActionTypeID
From autostatushistory
--Where AHCurrent is not AHPast
) ahCurrent
LEFT JOIN
(
select ROW_NUMBER() over(order by claimid, insertdate asc) as RowNum, ClaimID as NextClaimID, InsertDate as NextInsertDate, ActionTypeID as NextActionTypeID
FROM autostatushistory
) ahNext
ON (ahCurrent.ClaimID = ahNext.NextClaimID AND ahCurrent.RowNum = ahNext.RowNum - 1 and ahCurrent.ActionTypeID = ahNext.NextActionTypeID)
here the query the you need to execute,
it's not clean, maybe you'll optimize it:
WITH cte AS( /* Create a table containing row number */
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ROW,
MinutesElapsed,
AggregateMe,
ID,
TYPE
FROM rolling
)
SELECT MinutesElapsed + (CASE /* adding minutes from next valid records*/
WHEN cte.AggregateMe <> 1 /*if current record is 0 then */
THEN 0 /*skip it*/
ELSE
(SELECT SUM(MinutesElapsed) /* calculating sum of all -> */
FROM cte localTbl
WHERE
cte.ROW < localTbl.ROW /* next records -> */
AND
localTbl.ROW <= ( /* until we find aggregate = 0 */
SELECT MIN(ROW)
FROM cte sTbl
WHERE sTbl.AggregateMe = 0
AND
sTbl.ROW > cte.ROW
)
AND
(localTbl.AggregateMe = 0 OR /* just to be sure :) */
localTbl.AggregateMe = 1))
END) as MinutesElapsed,
AggregateMe,
ID,
TYPE
FROM cte
WHERE cte.ROW = 1 OR NOT( /* not showing records used that are used in sum, skipping 1 record*/
( /* records with agregate 0 after record with aggregate 1 */
cte.AggregateMe = 0
AND
(
SELECT AggregateMe
FROM cte tblLocal
WHERE cte.ROW = (tblLocal.ROW + 1)
)>0
)
OR
( /* record with aggregate 1 after record with aggregate 1 */
cte.AggregateMe = 1
AND
(
SELECT AggregateMe
FROM cte tblLocal
WHERE cte.ROW = (tblLocal.ROW + 1)
)= 1
)
);
test here
hope it helps to your problem.
feel free to ask questions.
By looking at your result set seems like following would work,
SELECT ID,Type,SUM(MinutesElapsed)
FROM mytable
GROUP BY ID,Type
But cannot tell for sure without looking into original dataset.