Find the most recent row of a group - sql

I know there is no "last" row so I hope I'm clear that isn't what I'm really looking for. I want to select the rows in a table if the value of one particular field is the last alphabetically. I'll try my best to draw it out below. I'm a bit of a novice so please bear with me...
TABLE
[Order Number], [Delivery Date], [Order Qty], [Shipped Qty], [Bill To], [Ship To], [Invoice Number]
There are many times when we will reissue invoices and that invoice number will increment by a letter. This will also update additional field values as well. Below is a typical set with multiples invoices...
'987654', '2014-05-01 00:00:00', '100', '90', 'BillToXYZ', 'ShipToXYZ', '987654A' - NEW RECORD -
'987654', '2014-05-01 00:00:00', '-100', '-90', 'BillToXYZ', 'ShipToXYZ', '987654B' - NEW RECORD -
'987654', '2014-05-01 00:00:00', '100', '100', 'BillToXYZ', 'ShipToNEWSHIPTOLOCATION', '987654C' - NEW RECORD -
'987654', '2014-05-01 00:00:00', '10', '10', 'BillToXYZ', '2ndNEWSHIPTOLOCATION', '987654D' - NEW RECORD -
What I need is to query all the above fields and only bring back those where the [Invoice Number] is the last(alphabetically) (in this case 987654D) but also have it SUM the values of the [Order Qty] and [Shipped Qty] for all of the records regardless of [Invoice Number].
If I can provide any additional information please let me know. Thank you in advance.

It possible to use the ROW_NUMBER function to get the last row in a group setting the ORDER BY descending and then using a filter to get the row with the value 1.
The SUM and MAX with windowing help to get the other aggregate values.
WITH D AS (
SELECT [Order Number]
, [Delivery Date]
, SUM([Order Qty]) OVER (PARTITION BY [Order Number]) [Total Order Qty]
, [Total Shipped Qty]
= SUM([Shipped Qty]) OVER (PARTITION BY [Order Number])
, [Bill To]
, [Ship To]
, [Last Invoice Number]
= MAX([Invoice Number]) OVER (PARTITION BY [Order Number])
, ID = ROW_NUMBER() OVER (PARTITION BY [Order Number]
ORDER BY [Invoice Number] DESC)
FROM Table1
)
SELECT [Order Number]
, [Delivery Date]
, [Total Order Qty]
, [Total Shipped Qty]
, [Bill To]
, [Ship To]
, [Last Invoice Number]
FROM D
WHERE ID = 1
SQLFiddle demo

Looks to me like you can use a GROUP BY and aggregates as suggested by other answers, but put that in a subquery so that you can link your 'main' table to it and get the detail values for the latest invoice. Does the following code do what you're looking for?
CREATE TABLE #Table
(
OrderNumber int,
DeliveryDate datetime,
OrderQty int,
ShippedQty int ,
BillTo varchar(10),
ShipTo varchar(100),
InvoiceNumber varchar(20)
)
INSERT INTO #Table
(
OrderNumber,
DeliveryDate,
OrderQty,
ShippedQty,
BillTo,
ShipTo,
InvoiceNumber
)
SELECT '987654', '2014-05-01 00:00:00', '100', '90', 'BillToXYZ', 'ShipToXYZ', '987654A' UNION
SELECT '987654', '2014-05-01 00:00:00', '-100', '-90', 'BillToXYZ', 'ShipToXYZ', '987654B' UNION
SELECT '987654', '2014-05-01 00:00:00', '100', '100', 'BillToXYZ', 'ShipToNEWSHIPTOLOCATION', '987654C' UNION
SELECT '987654', '2014-05-01 00:00:00', '10', '10', 'BillToXYZ', '2ndNEWSHIPTOLOCATION', '987654D'
SELECT t.*, s.TotalOrderQty, s.TotalShippedQty
FROM
#Table t
INNER JOIN
(
SELECT
OrderNumber,
MAX(InvoiceNumber) LastInvoice,
SUM(OrderQty) TotalOrderQty,
SUM(ShippedQty) TotalShippedQty
FROM #Table
GROUP BY OrderNumber
) s ON
t.OrderNumber = s.OrderNumber AND
t.InvoiceNumber = s.LastInvoice
DROP TABLE #Table

Is this what you're going after?
EDIT 2: Got clarification from OP. Have edited my solution to bring up aggregation for complete order but only the line item information of the latest invoice. Fiddle here: http://sqlfiddle.com/#!3/08bb0/6
SELECT t1.[Order Number],
t2.[Max Invoice Number],
t1.[Ship To],
t2.[Sum Order Qty],
t2.[Sum Shipped Qty]
FROM Table1 t1 INNER JOIN
(SELECT [Order Number],
MAX([Invoice Number] ) AS [Max Invoice Number],
SUM([Order Qty]) AS [Sum Order Qty],
SUM([Shipped Qty]) AS [Sum Shipped Qty]
FROM Table1
GROUP BY [Order Number]) t2
ON t1.[Invoice Number] = t2.[Max Invoice Number]
ORDER BY t1.[Order Number];
You can add more columns from the D invoice by placing them in the SELECT list from t1, as I did with [Ship To].
EDIT 1: Added grouping so whole table is considered. To be fair, Martin K. posted this answer first.

Related

Performance issue with SQL Query

I am pulling data from two tables-Forecast and Orders to compute sales forecast accuracy.
Steps I am taking:
Identifying all exhaustive combinations of product-region-demand month b/w both data sets...let's call this (1)
Identifying different forecast snapshots in forecast data... let's call this (2)
Performing a cross-join of (1) and (2)...let's call this (3)
Performing the "SUMIF()" equivalent on the lines from (3) for both orders and forecast. For example, if I am comparing forecast to actual orders for February,
Jan "INDPOR" Forecast---> For some Product/Region-Feb Delivery Combination: February Forecast (generated in January) vs. Orders booked after Jan 1st with a delivery schedule in Feb
Feb "INDPOR" Forecast---> For the same Product/Region-Feb Delivery Date Combination: February Forecast (generated in February) vs. Orders booked after Jan 27th* with a delivery schedule in Feb
Note 1: Generating multiple forecasts for the same month
Note 2: Fiscal calendar definitions used; that is why Feb starts on Jan 27th
Output is generating correctly. But, it is painfully slow (1 hour +). Please help me fine-tune this and make it faster as I will need to use this for larger data sets too.
Other Details:
I am running this on SQL Server 2014 locally from my desktop.Uploading this using the SQL data import wizard into SQL from an Excel file currently
Input Forecast data: ForecastAggTable
Input Orders data: OrderAggTable
Input and Output Files
Code:
Select *
from
(
Select *,
(Select isnull(sum([Forecast Qty]),0) from ForecastAggTable t2 where t2.LOB=D.LOB and
t2.[Demand Month]=D.[Demand Month] and t2.Class=D.Class
and t2.[Item Type]=D.[Item Type] and t2.[LoB Region]=D.[LoB Region] and
t2.[Key Account]=D.[Key Account] and t2.Country=D.Country
and t2.[Master Customer]=D.[Master Customer] and t2.[INDPOR Version]=D.[INDPOR Version])[Forecast Qty],
(
Select isnull(sum([Order Qty]),0) from OrderAggTable t1 where t1.LOB=D.LOB and
t1.[SAD Month]=D.[Demand Month] and t1.Class=D.Class
and t1.[Item Type]=D.[Item Type] and t1.[LoB Region]=D.[LoB Region] and
t1.[Key Account]=D.[Key Account] and t1.Country=D.Country
and t1.[Master Customer]=D.[Master Customer] and t1.[Book Date]>=D.[INDPOR Timestamp]
)[SAD-OrderQty],
(
Select isnull(sum([Order Revenue]),0) from OrderAggTable t1 where t1.LOB=D.LOB and
t1.[SAD Month]=D.[Demand Month] and t1.Class=D.Class
and t1.[Item Type]=D.[Item Type] and t1.[LoB Region]=D.[LoB Region] and
t1.[Key Account]=D.[Key Account] and t1.Country=D.Country
and t1.[Master Customer]=D.[Master Customer] and t1.[Book Date]>=D.[INDPOR Timestamp]
)[SAD-OrderRevenue],
(
Select isnull(sum([Order Qty]),0) from OrderAggTable t1 where t1.LOB=D.LOB and
t1.[RDD Month]=D.[Demand Month] and t1.Class=D.Class
and t1.[Item Type]=D.[Item Type] and t1.[LoB Region]=D.[LoB Region] and
t1.[Key Account]=D.[Key Account] and t1.Country=D.Country
and t1.[Master Customer]=D.[Master Customer] and t1.[Book Date]>=D.[INDPOR Timestamp]
)[RDD-OrderQty],
(
Select isnull(sum([Order Revenue]),0) from OrderAggTable t1 where t1.LOB=D.LOB and
t1.[RDD Month]=D.[Demand Month] and t1.Class=D.Class
and t1.[Item Type]=D.[Item Type] and t1.[LoB Region]=D.[LoB Region] and
t1.[Key Account]=D.[Key Account] and t1.Country=D.Country
and t1.[Master Customer]=D.[Master Customer] and t1.[Book Date]>=D.[INDPOR Timestamp]
)[RDD-OrderRevenue]
from
(
Select distinct LOB,[INDPOR Version],[INDPOR Timestamp],[Demand Month],
[Demand Quarter],[Min Date],Class,[Item Type],[Offer PF],
[LoB Region],[Key Account],Country,[Master Customer]
from
(
Select V.LOB,V.[SAD Month][Demand Month],V.[SAD Quarter][Demand Quarter],V.[SAD Min Date][Min Date],V.Class,
[Item Type],[Offer PF],[LoB Region],[Key Account],Country,[Master Customer]
from OrderAggTable V
union
(
Select Z.LOB,Z.[RDD Month][Demand Month],Z.[RDD Quarter][Demand Quarter],Z.[RDD Min Date][Min Date],Z.Class,
[Item Type],[Offer PF],[LoB Region],[Key Account],Country,[Master Customer]
from OrderAggTable Z
)
union
(
Select LOB,[Demand Month],[Demand Quarter],[Min Date],Class[Class],[Item Type],[Offer PF],[LoB Region],
[Key Account],Country,[Master Customer] from ForecastAggTable
)
)A
cross join
(
select distinct [INDPOR Version],[INDPOR Timestamp]
from ForecastAggTable
)B
)D
where [Min Date]>=[INDPOR Timestamp]
)E
where ([SAD-OrderQty] + [RDD-OrderQty] + [Forecast Qty]<>0)
How about simplifying, and doing less passes of your tables.
In this example I do two scans of the forecast table, one for the distinct, and one for the union, and one scan of the orders table.
with cte as
(
select distinct [INDPOR Version],[INDPOR Timestamp]
from ForecastAggTable
)
,cte2 as
(
Select
V.LOB
,iif(DUP=0,V.[SAD Month] ,V.[RDD Month] ) [Demand Month]
,iif(DUP=0,V.[SAD Quarter] ,V.[RDD Quarter] ) [Demand Quarter]
,iif(DUP=0,V.[SAD Min Date] ,V.[RDD Min Date] ) [Min Date]
,V.[Book Date]
,V.Class
,V.[Item Type]
,V.[Offer PF]
,V.[LoB Region]
,V.[Key Account]
,V.Country
,V.[Master Customer]
,null [INDPOR Version]
,null [Forecast Qty]
,iif(DUP=0,v.[Order Qty] ,null ) [SAD-OrderQty]
,iif(DUP=0,V.[Order Revenue] ,null ) [SAD-OrderRevenue]
,iif(DUP=1,V.[Order Qty] ,null ) [RDD-OrderQty]
,iif(DUP=1,V.[Order Revenue] ,null ) [RDD-OrderRevenue]
from OrderAggTable V
cross join (select dup from (values (0),(1))a(dup)) a
union all
Select
LOB
,[Demand Month]
,[Demand Quarter]
,[Min Date]
,[Min Date]
,Class
,[Item Type]
,[Offer PF]
,[LoB Region]
,[Key Account]
,Country
,[Master Customer]
,[INDPOR Version]
,[Forecast Qty]
,null[SAD-OrderQty]
,null[SAD-OrderRevenue]
,null[RDD-OrderQty]
,null[RDD-OrderRevenue]
from ForecastAggTable
)
select
cte2.LOB
,cte.[INDPOR Version]
,cte.[INDPOR Timestamp]
,cte2.[Demand Month]
,cte2.[Demand Quarter]
,cte2.[Min Date]
,cte2.Class
,cte2.[Item Type]
,cte2.[Offer PF]
,cte2.[LoB Region]
,cte2.[Key Account]
,cte2.Country
,cte2.[Master Customer]
,isnull(sum(cte2.[Forecast Qty] ),0) [Forecast Qty]
,isnull(sum(cte2.[SAD-OrderQty] ),0) [SAD-OrderQty]
,isnull(sum(cte2.[SAD-OrderRevenue]) ,0) [SAD-OrderRevenue]
,isnull(sum(cte2.[RDD-OrderQty] ),0) [RDD-OrderQty]
,isnull(sum(cte2.[RDD-OrderRevenue]) ,0) [RDD-OrderRevenue]
from cte2
inner join cte
on cte2.[Book Date]>=cte.[INDPOR Timestamp]
where isnull(cte2.[INDPOR Version],cte.[INDPOR Version])=cte.[INDPOR Version]
group by
cte2.LOB
,cte2.[Demand Month]
,cte2.[Demand Quarter]
,cte2.[Min Date]
,cte2.Class
,cte2.[Item Type]
,cte2.[Offer PF]
,cte2.[LoB Region]
,cte2.[Key Account]
,cte2.Country
,cte2.[Master Customer]
,cte.[INDPOR Version]
,cte.[INDPOR Timestamp]
having
isnull(sum(cte2.[Forecast Qty] ),0) +
isnull(sum(cte2.[SAD-OrderQty] ),0) +
isnull(sum(cte2.[RDD-OrderQty] ),0)
!=0

Meeting 2 conditions in 2 different columns

I'm trying to run a query where I need very specific conditions to be met:
Sales code is All
Item has Original Price flag set
Item has a price with no Original Price flag set that is the same as the Price with Original Price flag set
Price without Original price flag set must be created after the price with Original price flag
Currently I am using the following query to get the information I need;
select [item no_], [variant code],[unit price including vat],
[original price], [Starting Date], [Ending Date] from [Sales Price]
where [Sales Code] = 'all'
and [Ending Date] = '1753-01-01 00:00:00.000'
This is the example result:
1 means Original Price flag is set and 0 means it is not
The result I need from this query would be to only show these two:
I am assuming you are working with SQL Server as your current query syntax suggests.
If, so you can use lag() :
select sp.*
from (select sp.*,
lag([original price]) over (partition by [item no_] order by [Starting Date]) as prev_price
from [Sales Price] sp
where [Sales Code] = 'all'
) sp
where ([original price] = 1 or prev_price = 1);
let me know if you need me to explain; else its pretty straight forward.
select a.*
from (
select [item no_]
, [variant code]
,[unit price including vat]
, [original price]
, [Starting Date]
, [Ending Date]
,Column_Test = case when ( [original price] = 1 and [original price] = 0 ) and ([Starting Date]<[Ending Date]) then 1 else 0 end
from [Sales Price]
where [Sales Code] = 'all'
and [Ending Date] = '1753-01-01 00:00:00.000'
) a
where Column_Test = 1

SELECT DISTINCT CASE COUNT

I'm new to SQL, and trying to return a column in a data set that Counts and/or Sums the total of 1 column only when another column meets the criteria
I've got the below in my Select clause, but as I Group the data later in query and don't output the G.[Constituent ID] I cant count that row (and don't want to as it wont group the data the way i want).
Is there a way around this?
(I'm using Microsoft SQL)
SELECT
(Case WHEN G.[Constituent ID] IN ('2014', '2015', '2016', '2017', '2018') THEN COUNT(DISTINCT G.[Gift Import ID])) AS [COUNT of Gifts Anonymous]
This is my whole statement
SELECT
a.Apl_Appeal_ID AS [Appeal ID],
A.Apl_Description AS [Appeal Description],
A.Apl_Start_date AS [Appeal Start Date],
A.Apl_Campaign_ID AS [Campaign ID],
SUM(A.Apl_No_solicited) AS [Number Solicited],
SUM(A.Apl_ApE_1_01_Amount) AS [Appeal Expenses],
RIGHT(A.Apl_Appeal_ID,1) AS [PackCode],
A.Apl_AtrCat_1_01_Description AS [Gift Classification],
A.Apl_AtrCat_2_01_Description AS [Channel],
A.Apl_AtrCat_3_01_Description AS [MD/Corp],
A.Apl_AtrCat_4_01_Description AS [Purpose],
A.Apl_AtrCat_5_01_Description AS [Campaign],
A.Apl_AtrCat_6_01_Description AS [Wave],
A.Apl_AtrCat_7_01_Description AS [Supplier],
A.Apl_AtrCat_8_01_Description AS [Timing],
A.Apl_AtrCat_9_01_Description AS [Year],
COUNT(DISTINCT G.[Constituent ID]) AS [COUNT of Donors],
COUNT(DISTINCT G.[Gift Import ID]) AS [COUNT of Gifts],
SUM(G.[Gift Amount]) AS [Income],
(Case WHEN G.[Constituent ID] IN ('2014', '2015', '2016', '2017', '2018') THEN COUNT(DISTINCT G.[Gift Import ID])) AS [COUNT of Gifts Anonymous]
FROM dbo.[FHF_Appeals] A
LEFT OUTER JOIN dbo.FHF_Gifts_ExcludingRG G
ON A.Apl_Appeal_ID = G.[Appeal List]
WHERE (a.Apl_Campaign_ID = '1002100-Acquisition' AND A.Apl_AtrCat_2_01_Description LIKE 'Direct_Mail')
AND A.Apl_Start_date >= '1/1/2014'
GROUP BY
A.Apl_Appeal_ID,
A.Apl_Description,
A.Apl_Start_date,
A.Apl_Campaign_ID,
A.Apl_AtrCat_1_01_Description,
A.Apl_AtrCat_2_01_Description,
A.Apl_AtrCat_3_01_Description,
A.Apl_AtrCat_4_01_Description,
A.Apl_AtrCat_5_01_Description,
A.Apl_AtrCat_6_01_Description,
A.Apl_AtrCat_7_01_Description,
A.Apl_AtrCat_8_01_Description,
A.Apl_AtrCat_9_01_Description
I think you want the CASE expression as an argument to the COUNT(DISTINCT):
COUNT(DISTINCT CASE WHEN G.[Constituent ID] IN ('2014', '2015', '2016', '2017', '2018')
THEN G.[Gift Import ID]
END) AS [COUNT of Gifts Anonymous]

Workaround for PIVOT statement

I have this query, is taking like 2 minutes to resolve, I need to find a workaround, I know that UNPIVOT has a better solution using CROSS APPLY, is there anything similar for PIVOT?
SELECT [RowId], [invoice date], [GL], [Entité], [001], [Loc], [Centre Cout], [Compte_1], [Interco_1], [Futur_1], [Department], [Division], [Compagnie], [Localisation], [Centre/Cout], [Compte], [Interco], [Futur], [Account], [Mobile], [Last Name], [First Name], [license fee], [GST], [HST], [PST], [Foreign Tax], [Sales Tax License], [Net Total], [Total], [ServiceType], [Oracle Cost Center], [CTRL], [EXPENSE], [Province]
FROM
(SELECT fd.[RowId], fc.[ColumnName], fd.[Value]
FROM dbo.FileData fd
INNER JOIN dbo.[FileColumn] fc
ON fc.[FileColumnId] = fd.[FileColumnId]
WHERE FileId = 1
AND TenantId = 1) x
PIVOT
(
MAX(Value)
FOR [ColumnName] IN ( [invoice date], [GL], [Entité], [001], [Loc], [Centre Cout], [Compte_1], [Interco_1], [Futur_1], [Department], [Division], [Compagnie], [Localisation], [Centre/Cout], [Compte], [Interco], [Futur], [Account], [Mobile], [Last Name], [First Name], [license fee], [GST], [HST], [PST], [Foreign Tax], [Sales Tax License], [Net Total], [Total], [ServiceType], [Oracle Cost Center], [CTRL], [EXPENSE], [Province])
) AS p
Pivots are great, but so are Conditional Aggregations. Also, there would be no datatype conficts or conversions necessary
SELECT [RowId]
,[invoice date] = max(case when [FileColumnId] = ??? then Value end)
,[GL] = max(case when [FileColumnId] = ??? then Value end)
,... more fields
FROM dbo.FileData fd
WHERE FileId = 1
AND TenantId = 1
Group By [RowId]
EDIT
You could add back the join to make it more readable.

SQL Result Set Merge

I have a limitation where I can only send one result set to a reporting application at any one time, to produce an end report for a customer.
So a query like this
select
[AGENT],
[TRANSDATE],
[RECIPT NO],
[CUSTOMER NAME],
[ORDER NO] ,
[TRANS NO] ,
QUANTITY,
[AMOUNT COST],
From [Customer] C
However I need lots of totals at the bottom such as this query for some of the columns. I cannot make any changes to front end due to it being a legacy reporting application.
select
Sum ( QUANTITY ) as [SUM OF QUANTITY] ,
Sum ( AMOUNT COST ) AS [SUM OF AMOUNT COST]
From [Customer] C
Obviously I simplified the queries I am using. So the question is how to make 2 results sets one result set in SQL?
Union and union all failed due to date columns being defaulted if you use blank for a column in end application.
Rollup or Pivoting or CTE I kinda thought of but cannot see a solution yet.
what about windowed functions?
like...
select
[AGENT],
[TRANSDATE],
[RECIPT NO],
[CUSTOMER NAME],
[ORDER NO] ,
[TRANS NO] ,
QUANTITY,
[AMOUNT COST],
Sum ( QUANTITY ) over () as [SUM OF QUANTITY] ,
Sum ( [AMOUNT COST] ) over () AS [SUM OF AMOUNT COST]
From [Customer] C