SQL Run query multiple times changing variable - sql

I have a query which checks the status of a specific tank, however I have 50 tanks which I'd like to run this for (FV101, FV102, FV103 etc.)
I could union together 50 queries with the WHERE changed, but I suspect that there must be a better way.
The query is very simple:
DECLARE #tank varchar(5)
SET #tank = 'FV101'
SELECT
ROW_NUMBER() OVER (PARTITION BY [Item] ORDER BY [TankName]) [Row],
*
FROM
(
SELECT TOP 1
#tank [TankName],
T1.[Item],
CASE WHEN AVG(CAST(T0.[pHValue] AS dec (10,5))) NOT BETWEEN MAX(T2. [pH_lower]) AND MAX(T2.[pH_upper]) THEN 'Red' ELSE 'Black' END [Spec]
FROM
t005_pci_data T0 INNER JOIN t001_fvbatch T1 ON T1.[FVBatch] = T0. [FVBatch] LEFT JOIN t024_specifications T2 ON T2.[BeerBrand] = T1.[BeerBrand] AND [Type] = 'Finished Product'
WHERE
T0.[FVBatch] = (SELECT TOP 1 T0.[FVBatch] FROM t005_pci_data T0 INNER JOIN t001_fvbatch T1 ON T1.[FVBatch] = T0.[FVBatch] WHERE T1.[TankName] = 'FV101' ORDER BY T0.[DateTime] DESC) AND
EXISTS (SELECT [FVBatch] FROM t005_pci_data WHERE [TankName] = #tank AND [DateTime] >= DATEADD(day,-2,GETDATE()))
GROUP BY
T1.[Item],T0.[DateTime]
ORDER BY
T0.[DateTime] DESC
) a
Is there a way to pass a list of values to this query for it to use instead of repeating the query manually multiple times?
EDIT as per iamdave's suggestion
Schema (simplified) is as below:
The goal is to list everything which is currently in each tank and check if the value of the pH is within the acceptable limits.
Desired output (up to FV105) would be:
In this example there is nothing in tanks FV101 or FV104, as decided by the following code in the WHERE
EXISTS (SELECT [FVBatch] FROM t005_pci_data WHERE [TankName] = #tank AND [DateTime] >= DATEADD(day,-2,GETDATE()))
The end result is I would like to create a table in MSSRS which shows what item is in each tank and whether it is within specifications or not.
FURTHER EDIT with sample data as requested
(Not very imaginative I'm afraid)
declare #t1 table(FVBatch int, TankName nvarchar(5), Item nvarchar(20));
declare #t2 table(Item nvarchar(20), ph_lower decimal(10,2), ph_upper decimal(10,2));
declare #t3 table(FVBatch int, pHValue decimal(10,2), DateValue datetime);
insert into #t1 values
(3160001,'FV101','Stout')
,(3160002,'FV102','Stout')
,(3160003,'FV103','Stout')
,(3160004,'FV104','Pale Ale')
,(3160005,'FV105','Pale Ale')
,(3160070,'FST04','IPA');
insert into #t2 values
('Pale Ale',3.5,5.5)
,('Stout',2,3.5);
insert into #t3 values
(3160001,4 ,'20161209')
,(3160001,4 ,'20161210')
,(3160001,4 ,'20161212')
,(3160002,4 ,'20161218')
,(3160002,4 ,'20161220')
,(3160002,4 ,'20161222')
,(3160003,4 ,'20161218')
,(3160003,4 ,'20161220')
,(3160003,4 ,'20161222')
,(3160004,4 ,'20161209')
,(3160004,4 ,'20161210')
,(3160004,4 ,'20161212')
,(3160005,4 ,'20161218')
,(3160005,4 ,'20161220')
,(3160005,4 ,'20161222')
,(3160070,4.26,'20161218')
,(3160070,4.26,'20161216')
,(3160070,4.24,'20161215')
,(3160070,4.24,'20161214')
,(3160070,4.26,'20161213')
,(3160070,4.2 ,'20161212')
,(3160070,4.21,'20161211')
,(3160070,4.12,'20161209')
,(3160070,4.09,'20161208')
,(3160070,4.1 ,'20161207');

How does this do?
select row_number() over (partition by t1.Item order by t1.TankName) as RowNum
,t1.TankName
,t1.Item
,case when avg(t3.pHValue) between t2.ph_lower and t2.ph_upper
then 'Black'
else 'Red'
end as Spec
from #t1 t1
left join #t2 t2
on(t1.Item = t2.Item)
inner join(select FVBatch
,pHValue
,max(DateValue) as DateValue
from #t3
where DateValue >= cast(dateadd(d,-2,getdate()) as date)
group by FVBatch
,pHValue
) t3
on(t1.FVBatch = t3.FVBatch)
group by t1.TankName
,t1.Item
,t2.ph_lower
,t2.ph_upper
order by t1.TankName
,RowNum
Using your test data, the above query returns:
RowNum TankName Item Spec
1 FV102 Stout Red
2 FV103 Stout Red
1 FV105 Pale Ale Black
Edit based on conversation
/*
The full requirement is this:
there could be multiple of each measurement taken on any one date,
so I want to check that the average of these measurements per day is withing the range
each batch goes through several stages
if the stage isn't "bottle" or "can" then we look back 2 days
if it is one of those then we look back 5 days instead
*/
declare #t001_fvbatch table(FVBatch int
,TankName nvarchar(5)
,BeerBrand nvarchar(20)
);
declare #t024_specifications table(BeerBrand nvarchar(20)
,pH_lower decimal(10,2)
,pH_upper decimal(10,2)
,OG_lower decimal(10,2)
,OG_upper decimal(10,2)
,PG_lower decimal(10,2)
,PG_upper decimal(10,2)
,EBCHaze decimal(10,2)
,ABV_lower decimal(10,2)
,ABV_upper decimal(10,2)
,[Type] nvarchar(50)
);
declare #t005_pci_data table(FVBatch int
,pHValue decimal(10,2)
,Alcohol decimal(10,2)
,OG decimal(10,2)
,PG decimal(10,2)
,EBCHaze decimal(10,2)
,[DateTime] datetime
,Stage nvarchar(20)
,TankName nvarchar(20)
);
select b.FVBatch
,b.TankName
,b.BeerBrand
,case when((d.Stage in('CAN','BOTTLE','BBT')
and avg(cast(d.EBCHaze as dec(10,5))) > 5
)
or (avg(cast(d.Alcohol as dec(10,5))) not between max(s.ABV_lower) and max(s.ABV_upper)
or avg(cast(d.OG as dec(10,5))) not between max(s.OG_lower) and max(s.OG_upper)
or avg(cast(d.PG as dec(10,5))) not between max(s.PG_lower) and max(s.PG_upper)
or avg(cast(d.pHValue as dec(10,5))) not between max(s.pH_lower) and max(s.pH_upper)
)
)
then 'Red'
else 'Black'
end as Spec
from #t001_fvbatch b -- Always start at the table with the most central piece of data. In this case, the specifications and measurements all relate to a single batch.
left join #t024_specifications s
on(b.BeerBrand = s.BeerBrand
and s.[Type] = 'Finished Product'
)
inner join (select d2.FVBatch -- This sub select returns the most recent DateTime value per each FVBatch and TankName combination.
,d2.TankName
,cast(max(d2.[DateTime]) as date) as MostRecentMeasurement -- Cast/convert to DATE type to remove the TIME element. This means 2016-12-22 12:00:00 and 2016-12-22 13:59:43 both become 2016-12-22.
from #t005_pci_data d2
where d2.[DateTime] >= cast(dateadd(d -- This case statement filters the DateTime values by a number of days based on the value in the Stage column.
,case when d2.Stage in('can','bottle')
then -5
else -2
end
,getdate()
)
as date)
group by d2.FVBatch
,d2.TankName
) dm
on(b.FVBatch = dm.FVBatch
and b.TankName = dm.TankName
)
inner join #t005_pci_data d -- We then join into the data table again to get all the measurements taken on the same day as the most recent.
on(b.FVBatch = d.FVBatch
and b.TankName = d.TankName
and d.[DateTime] >= dm.MostRecentMeasurement -- Filtering like this allows any indexes on your DateTime column to be used to speed execution time. (This is called sargability).
and d.[DateTime] < dateadd(d,1,dm.MostRecentMeasurement) -- We could use functions to convert the DateTime to a DATE data type to match the MostRecentMeasurement value, but this would then need to be calculated for every single measurement in the table, which is wasteful.
)
-- Using INNER JOIN for the measurements means we are only return batches that have measurements that meet our filtering criteria.
-- If we wanted to return a batch even if there were no measurements matching, we would use LEFT JOIN instead.
group by b.FVBatch
,b.TankName
,b.BeerBrand
,d.Stage

Related

Sql how to convert nest while loops to a CTE using two temp tables

I have one temp table that pulls from the Abstracting table for dx codes and Registration table for the account number. It joins by the VisitID and the dx codes are in order by DiagnosisUrn - #Visits
I need to join it to the DiagTable. The dx codesin this table are the primary ones for billing, in
order by Transaction Number ID with the lowest number being the most primary.
In the end, for each visit id , account id, I need to for the Dx codes to print out
like this example:
Account ICD10 SequenceNumber
AT000118 R06.00 1
AT000118 I25.10 2
AT000118 I65.23 3
AT000118 E11.29 4
AT000118 Z95.1 5
AT000118 E11.65 6
AT000118 E78.2 7
AT000118 E78.1 8
AT000118 E11.42 9
For this particular account (there are many others similar), all of the dx are in the Visit table
however only the first four are in Diag table with the proper TransactionDiagnosisURN/DiagnosisURN/Sequence Number
The While loops reorder the rest of DX codes that were in the visit temp table and adding them to the end of the ones in the Diag table with the result populating the PBDxCodes temp table.
It needs to run by date range - I've changed it to pull a few account numbers instead to try to work on
a CTE. Using loops causes it to run an extremely long time and sometimes it just times out.
I'm getting a recursion error when trying to convert the query to CTE. I have only used basic CTE in the past and it has been a while.
Below is the query using the while loop and then my attempt at trying to convert one loop to a CTE:
While loops:
DECLARE #NumofVisits AS INT
DECLARE #NumberofTxs AS INT
DECLARE #Looper AS INT
DECLARE #ThisVisitID AS VARCHAR(35)
--Get the VisitIDs to find Dx Codes
DROP TABLE IF EXISTS #Visits
SELECT DISTINCT
RAM.VisitID
,RAM.AccountNumber
,ADX.DiagnosisCode_MisDxID
,CAST(ADX.DiagnosisUrnID AS INT) AS ABSDxCode
INTO #Visits
FROM livefdb.dbo.AbsAcct_Diagnoses ADX
JOIN livefdb.dbo.RegAcct_Main AS RAM
ON RAM.VisitID = ADX.VisitID
AND RAM.SourceID = ADX.SourceID
AND RAM.Facility_MisFacID NOT IN ('KK', 'JFJ','MPM')
AND RAM.RegistrationType_MisRegTypeID IN ('AMB','AMBR','BNV')
LEFT JOIN livefdb.dbo.HimRec_Main AS HRM
ON HRM.SourceID = RAM.SourceID
AND HRM.PatientID = RAM.PatientID
WHERE ADX.VisitID IN ('LE1-B20210114114749175', 'AT0-B20191211155403251'
,'ID0-B20201201104450465','OM1-B20210107093907435'
,'AT0-B20191205154143298','LE0-B20200716135146384'
,'PC1-B20210111125154209','SV1-B20210112122435108'
,'UB0-B20200915140417079','ID1-B20201222150226545'
)
SET #NumofVisits = (SELECT COUNT(DISTINCT VisitID) FROM #Visits )
-- Create a table to hold Dx data for each visit
DROP TABLE IF EXISTS #PBDxCodes
CREATE TABLE #PBDxCodes (
EncounterRecordNumber VARCHAR(18),
ICD10DiagnosisCode VARCHAR(15),
SequenceNumber INT,
)
-- Get the Diagnosis from BAR Transactions. The least transaction number is the Primary Charge
-- and the Diagnosis Codes are the Primary Diagnosis codes
WHILE #NumofVisits > 0
BEGIN
DROP TABLE IF EXISTS #UpdateDiag
CREATE TABLE #UpdateDiag (
DiagnosisUrnID INT IDENTITY(1,1) PRIMARY KEY,
VisitID VARCHAR(35),
Diagnosis_MisDxID VARCHAR(50)
)
SET #ThisVisitID = (SELECT TOP 1 VisitID FROM #Visits)
SELECT DISTINCT
BATD.VisitID,
BATD.TransactionNumberID,
CAST(BATD.TransactionDiagnosisUrnID AS INT) AS TransactionDiagnosisUrnID,
BATD.TransactionDiagnosis_MisDxID
INTO #Diag
FROM livefdb.dbo.BarAcct_TxnDiagnoses AS BATD
WHERE BATD.VisitID = #ThisVisitID
SET #NumberofTxs = (SELECT COUNT(DISTINCT TransactionNumberID) FROM #Diag)
SET #Looper = 0
WHILE ( #NumberofTxs > #Looper)
BEGIN
INSERT INTO #UpdateDiag(VisitID, Diagnosis_MisDxID)
SELECT
VisitID,
TransactionDiagnosis_MisDxID
FROM #Diag
WHERE TransactionNumberID =
(CASE WHEN 1 = (SELECT COUNT(DISTINCT TransactionNumberID) FROM #Diag)
THEN (SELECT TOP 1 TransactionNumberID FROM #Diag )
ELSE (SELECT MIN(TransactionNumberID) FROM #Diag)
END)
AND TransactionDiagnosis_MisDxID NOT IN (SELECT Diagnosis_MisDxID FROM #UpdateDiag)
ORDER BY TransactionDiagnosisUrnID
DELETE FROM #Diag WHERE TransactionNumberID = (SELECT MIN(TransactionNumberID) FROM #Diag)
SET #Looper = #Looper + 1
END
-- Are there Diagnosis in Abstracting that are not in BAR
INSERT INTO #UpdateDiag(VisitID, Diagnosis_MisDxID)
SELECT
VisitID,
DiagnosisCode_MisDxID
FROM #Visits
WHERE #Visits.VisitID = #ThisVisitID
AND #Visits.DiagnosisCode_MisDxID NOT IN (SELECT Diagnosis_MisDxID FROM #UpdateDiag)
ORDER BY #Visits.ABSDxCode
-- Add this visit to the temp table holding final results
INSERT INTO #PBDxCodes (EncounterRecordNumber,SequenceNumber,ICD10DiagnosisCode,SourceSystem)
SELECT DISTINCT
#Visits.AccountNumber AS EncounterRecordNumber,
#UpdateDiag.DiagnosisUrnID AS SequenceNumber,
#UpdateDiag.Diagnosis_MisDxID AS ICD10DiagnosisCode
FROM #UpdateDiag
INNER JOIN #Visits ON #UpdateDiag.VisitID = #Visits.VisitID
DELETE FROM #Visits WHERE VisitID = #ThisVisitID
SET #NumofVisits = #NumofVisits - 1
DROP TABLE #Diag, #UpdateDiag
END
SELECT * FROM #PBDxCodes
WHERE EncounterRecordNumber='AT0001184503'
ORDER BY EncounterRecordNumber, SequenceNumber
DROP TABLE #Visits, #PBDxCodes
CTE Attempt:
DECLARE #NumofVisits AS INT
DECLARE #NumberofTxs AS INT
DECLARE #Looper AS INT
DECLARE #ThisVisitID AS VARCHAR(35)
--Get VisitID,Account Nos and Dx in Abstracting.
DROP TABLE IF EXISTS #Visits
SELECT DISTINCT
RAM.VisitID
,RAM.AccountNumber
,ADX.DiagnosisCode_MisDxID
,CAST(ADX.DiagnosisUrnID AS INT) AS ABSDxCode
INTO #Visits
FROM livefdb.dbo.AbsAcct_Diagnoses ADX
JOIN livefdb.dbo.RegAcct_Main RAM
ON ADX.SourceID=RAM.SourceID
AND ADX.VisitID=RAM.VisitID
AND RAM.Facility_MisFacID NOT IN ('KK', 'JFJ','MPM')
AND RAM.RegistrationType_MisRegTypeID IN ('AMB','AMBR','BNV')
WHERE ADX.VisitID IN ('AT0-B20191211155403251','LE1-B20210114114749175')
SET #NumofVisits = (SELECT COUNT(DISTINCT VisitID) FROM #Visits);
WITH CTE AS (
SELECT
V.ABSDxCode
,#NumofVisits AS n
,V.VisitID
,V.DiagnosisCode_MisDxID
FROM #Visits V
UNION ALL
SELECT
C.ABSDxCode
,C.n+1
,V.VisitID AS VisitID
,V.DiagnosisCode_MisDxID
FROM CTE C
INNER JOIN #Visits AS V ON V.VisitID=C.VisitID
)
SELECT C.ABSDxCode, C.DiagnosisCode_MisDxID
FROM CTE C
WHERE C.n=(SELECT MAX(n) FROM CTE WHERE ABSDxCode=C.ABSDxCode)

Why is SQL server inserting 0 values into my table instead of the correct values using function

Hope somebody can help me with this as I'm completely out of ideas as to why it's happening.
I am currently conducting some analysis on Premier League Match Results and as part of this, I have created a Multi-Statement Table UDF.
This function accepts a HomeTeam, AwayTeam and a MatchDate parameter, and then performs a count of each match result that was won, drawn or lost historically between the home and away team prior to the up match date specified.
This function works fine by manually calling it, and returns values such as
Home Away Draw
0 8 4
I wanted to add this information to every match result in my match table, so created a query to move the matches from a staging table, using OUTER APPLY with the function to insert these values.(I also OUTER APPLY another function prior to this which works fine.) and then insert into my MatchData table.
The query works if I just select the values, but if I INSERT INTO my MatchData table, the values are all populating as 0s.
I have tried numerous tests, which confirm that this also happens if I use a SELECT into, unless the table is temporary.
To add, there is no conversion of the values in question at any point, they remain as integers all the way through and the destination column is of type int also.
Hope somebody can give me some ideas on what to try next. Code is below. apologies for anything that isn't well written, as I have muddled with the code a lot up to now trying to get it to insert the right value
Thanks in advance!
Here's the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [Load].[MoveToMatchData]
AS
BEGIN
INSERT INTO Football.MatchData.PremierLeague
SELECT *
FROM
(SELECT
[League], [MatchID], [Season],
[MatchDate], [HomeTeam], [AwayTeam],
[FTHomeGoals], [FTAwayGoals], [FTResult],
[HTHomeGoals], [HTAwayGoals], [HTResult],
[Referee], [HomeShots], [AwayShots],
[HomeShotsOnTarget], [AwayShotsOnTarget],
[HomeFouls], [AwayFouls], [HomeCorners], [AwayCorners],
[HomeYellows], [AwayYellows], [HomeReds], [AwayReds]
FROM
[Football].[Load].[Staging_MatchData] AS a
WHERE
League = 'E0') AS a
OUTER APPLY
(
SELECT * FROM Football.Load.CreateRelativeTable_Prem
(a.MatchDate, a.HomeTeam, a.AwayTeam, a.Season, A.League)
) as b
OUTER APPLY
Here's the UDF
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [Load].[GetH2HRecords]
(#HomeTeam varchar(50), #AwayTeam varchar(50), #MatchDate date)
RETURNS #H2H TABLE
(
Home int,
Away int,
Draw int
)
AS
BEGIN
DECLARE #FromDate date
SET #FromDate = DATEADD(yyyy,-10,#MatchDate)
INSERT INTO #H2H
SELECT
a.[Number of Matches] as HomeHTH, b.[Number of Matches] as AwayHTH, c.[Number of Matches] as DrawHTH
FROM
(
SELECT
COUNT(MatchID) as [Number of Matchesh]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'H'
) as a
OUTER APPLY
(
SELECT
COUNT(MatchID) as [Number of Matchesa]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'A'
) as b
OUTER APPLY
(
SELECT
COUNT(MatchID) as [Number of Matchesd]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'D'
) as c
RETURN
END
(
SELECT * FROM Football.Load.GetH2HRecords
(a.HomeTeam, a.AwayTeam, a.MatchDate)
) as c
END
This is only potentially an answer, but... why is your Function query so complex? This does the same thing in only one SELECT statement:
CREATE FUNCTION [Load].[GetH2HRecords]
(#HomeTeam varchar(50), #AwayTeam varchar(50), #MatchDate date)
RETURNS #H2H TABLE
(
Home int,
Away int,
Draw int
)
AS
BEGIN
DECLARE #FromDate date
SET #FromDate = DATEADD(yyyy,-10,#MatchDate)
INSERT INTO #H2H
SELECT
SUM(
CASE
WHEN FTResult = 'H'
Then 1
ELSE 0
END
) as HomeHTH,
SUM(
CASE
WHEN FTResult = 'A'
Then 1
ELSE 0
END
) as AwayHTH,
SUM(
CASE
WHEN FTResult = 'D'
Then 1
ELSE 0
END
) as DrawHTH
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
RETURN
END

Splitting multiple delimited values into multiple rows [duplicate]

This question already has answers here:
SQL Server: Split operation
(5 answers)
Closed 6 years ago.
I have been looking for a solution in StackOverflow but didn't find anything useful. I am facing a issue and I hope anyone would like to help me out.
I have value like this:
Create table DemoRecords
(
CustID int identity (1,1),
CustomerName varchar(50),
CurrencyCode varchar(50),
CurrentBalance varchar(50),
DateValue varchar(50)
)
GO
INSERT INTO DemoRecords VALUES ('Mr. X', 'BDTýUSDýGBP','10500ý2500ý1050','20150101ý20150201ý20150301')
..and I need output like this: (Please take a look at the picture attached below)
Picture
Please don't suggest me to use CTE because there are more than 100 columns in that table.
Here is a function to split a string into rows. Below that is a query against your demorecords table that uses the function to get the requested result.
create function dbo.split
(
#delimited nvarchar(max),
#delimiter nvarchar(5)
)
returns #rows table
(
rownumber int not null identity(1,1),
value nvarchar(max) not null
)
as
begin
if #delimited is null return
declare #delr_len int = len(#delimiter)
declare #start_at int = 1
declare #end_at int
declare #deld_len int
while 1=1
begin
set #end_at = charindex(#delimiter,#delimited,#start_at)
set #deld_len = case #end_at when 0 then len(#delimited) else #end_at-#start_at end
insert into #rows (value) values( substring(#delimited,#start_at,#deld_len) );
if #end_at = 0 break;
set #start_at = #end_at + #delr_len
end
return
end
go
select custid, customername, currencycode=currencycode.value, currentbalance=currentbalance.value, datevalue=datevalue.value
from demorecords r
cross apply (select rownumber, value from dbo.split(r.currencycode,'ý') ) currencycode
cross apply (select rownumber, value from dbo.split(r.currentbalance,'ý') where rownumber = currencycode.rownumber ) currentbalance
cross apply (select rownumber, value from dbo.split(r.datevalue,'ý') where rownumber = currencycode.rownumber ) datevalue
If you have a column that may contain missing values, use an outer apply instead of an inner apply to join the result of the function for that column. In the following example, the DateValue column is missing value 3 and value 4.
INSERT INTO DemoRecords VALUES ('Mr. X', 'BDTýUSDýGBPýEUR','10500ý2500ý1050ý','ý')
select custid, customername, currencycode=currencycode.value, currentbalance=currentbalance.value, datevalue=datevalue.value
from demorecords r
cross apply (select rownumber, value from dbo.split(r.currencycode,'ý') ) currencycode
cross apply (select rownumber, value from dbo.split(r.currentbalance,'ý') where rownumber = currencycode.rownumber ) currentbalance
outer apply (select rownumber, value from dbo.split(r.datevalue,'ý') where rownumber = currencycode.rownumber ) datevalue
Alternatively, you could clean up your input to not be missing values. In the above example, I would expect DateValue to be 'ýýý' not 'ý'. If your situation allows it, you might prefer finding and fixing these and not using an outer join.

Subtotaling with Ranges and Limited Compatibility

On SQL Server 2008 R2, I am trying to create a stored procedure that will give the user subtotals of commodities based on their inputted range. I would appreciate any assistance.
However, I ran into a problem in which the query displays All commodities that are being calculated so the user ends with values that will eventually exceed their inputted range. I'll include some sample data on the bottom as well as what I have tried.
Here's an example (that I took out some columns in for clarity in this example):
table
As you can tell, for a range between 2000 and 10000, commodity 998-32 is going to exceed 10000 in subtotals but still displays since each PO NO is individually less than 10000.
Here is some sample data:
DROP TABLE ##mytable;
CREATE TABLE ##mytable(
Commodity VARCHAR(15) NOT NULL
,PO_NO INTEGER NOT NULL
,LINE_NO INTEGER NOT NULL
,PO_Line_Description VARCHAR(82)
,Commodity_Description VARCHAR(60) NOT NULL
,Fiscal_Year INTEGER NOT NULL
,Vendor_ID INTEGER NOT NULL
,Vendor_Name VARCHAR(20) NOT NULL
,QUANTITY INTEGER NOT NULL
,UNIT_COST NUMERIC(7,2) NOT NULL
,Line_Amount NUMERIC(7,2) NOT NULL
);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-18',1448923,1,'Face Scholastic book order attached.','Sale of Surplus and Obsolete Books',2015,47650,'SCHOLASTIC INC',1,9999.8,9999.8);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1416311,2,'First 12 months maintenance agreement to be billed quarterly at .0039 per b/w copy','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,5148,5148);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1424377,1,NULL,'Sale of Surplus and Obsolete Copy Machines',2015,300590,'KONICA MINOLTA/ CIT',1,2894.58,2894.58);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1404031,1,'1st (12) months of (36) month Lease payment for (1) MPC4503 copier.','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,2050.68,2050.68);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-75',1401552,1,'Blanket order for 50 teachers - each teacher not to exceed $100.00.','Sale of Surplus and Obsolete Paper and Paper Products',2015,27536,'KNOWLEDGE TREE',1,5000,5000);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-78',1521390,1,'PL02286>PRESSURE PLUMBER INSTANT DRAIN OPENER 24 SHOT CARTRIDGE','Sale of Surplus and Obsolete Plumbing Equipment and Supplies',2015,402985,'TECH MECH SUPPLY LLC',480,8,3840);
I have tried using a SUM() OVER (PARTITION BY [ ] ORDER BY [ ] ROWS UNBOUNDED PRECEDING) but apparently that the OVER clause with a ROW was introduced in SQL Server 2012 and does not work with 2008 R2.
I have also tried using GROUP BY ROLLUP but I get a message that says "The CUBE() and ROLLUP() grouping constructs are not allowed in the current compatibility mode. They are only allowed in 100 mode or higher." When I asked our DBA, he said we can't move to 100 mode from 90 mode because a lot of things will break.
So now I'm stuck with the below query, which has the problem stated earlier, the issue of giving me the data I want but also the subtotals of data that will eventually exceed my specified range.
P.S. I have also noticed that if the commodity has PO NOs that are within my selected range but also PO NOs with costs outside of it, this query will give me those within the selected range which is very misleading since it is still a commodity that will be outside the selected range if it were all calculated. They shouldn't be listed into the results at all with the commodities that are truly within my selected range.
ALTER PROCEDURE [dbo].[POreport] (
#Param1 INT
,#Param2 INT
,#Param3 INT
)
AS
BEGIN
SET NOCOUNT ON;
SELECT DISTINCT ROW_NUMBER() OVER (
ORDER BY T.Product_ID
,T.PO_NO
,T.LINE_NO
) AS [RowID]
,ISNULL(T.PRODUCT_ID, 'NULL') AS [Commodity]
,ISNULL(T.PO_NO, 'NULL') AS [PO NO]
,ISNULL(T.LINE_NO, 'NULL') AS [LINE NO]
,QUOTENAME(T.DESCRIPTION, '"') AS [PO Line Description]
,QUOTENAME(C.DESCRIPTION, '"') AS [Commodity Description]
,ISNULL(T.FY, 'NULL') AS [Fiscal Year]
,PH.Vendor_ID AS [Vendor ID]
,QUOTENAME(V.Vendor_Name, '"') AS [Vendor Name]
,T.QUANTITY
,T.UNIT_COST
,T.QUANTITY * T.UNIT_COST AS [Line Amount]
,(
SELECT CAST(0.00 AS NUMERIC(10, 2))
) AS Sub_Total_Cost
INTO ##TmpPOReport
FROM dbo.DBVW_FI_REQ_PO_ITEMS T
INNER JOIN dbo.FI_VENDOR FV ON T.INST_ID = FV.INST_ID
INNER JOIN dbo.FI_REQ_PO_HEADER PH ON T.PO_NO = PH.PO_NO
INNER JOIN dbo.FI_VENDOR V ON PH.VENDOR_ID = V.VENDOR_ID
INNER JOIN dbo.FI_COMMODITY C ON T.PRODUCT_ID = C.FI_COMMODITY_CODE
WHERE T.INST_ID = 'SC00'
AND T.FY = #Param1
AND V.VENDOR_TYPE = 'V'
AND T.PO_NO IS NOT NULL
AND (
T.PRODUCT_ID <> ''
AND T.PRODUCT_ID IS NOT NULL
)
AND T.QUANTITY * T.UNIT_COST BETWEEN #Param2
AND #Param3
GROUP BY T.PRODUCT_ID
,T.PO_NO
,T.LINE_NO
,T.DESCRIPTION
,C.DESCRIPTION
,T.FY
,PH.Vendor_ID
,V.Vendor_Name
,T.QUANTITY
,T.UNIT_COST
,PH.Created_Date
ORDER BY Commodity
DECLARE #PID VARCHAR(15) = 00
,#QUANTITY INT
,#UNIT_COST NUMERIC(10, 2)
,#PrevID VARCHAR(15)
,#RowID BIGINT
,#PrevRowID BIGINT
,#RowAmount NUMERIC(10, 2)
,#SubTotal NUMERIC(10, 2) = 0.00
SET NUMERIC_ROUNDABORT OFF;
WHILE EXISTS (
SELECT TOP 1 *
FROM ##TmpPOReport
WHERE Sub_Total_Cost = 0.00
)
BEGIN
SET #RowAmount = (
SELECT TOP 1 (QUANTITY * UNIT_COST)
FROM ##TmpPOReport
WHERE Sub_Total_Cost = 0.00
)
SELECT TOP 1 #PID = Commodity
,#RowID = RowID
FROM ##TmpPOReport
WHERE Sub_Total_Cost = 0.00
IF (#PID = #PrevID)
AND (#RowID <> #PrevRowID)
BEGIN
SET #SubTotal += #RowAmount;
UPDATE T
SET Sub_Total_Cost = #SubTotal
FROM ##TmpPOReport T
WHERE T.Commodity = #PID
AND RowID = #RowID
SET #PrevID = #PID;
SET #PrevRowID = #RowID
END
ELSE
BEGIN
SET #SubTotal = #RowAmount;
UPDATE T
SET Sub_Total_Cost = #SubTotal
FROM ##TmpPOReport T
WHERE T.Commodity = #PID
AND RowID = #RowID
SET #PrevID = #PID;
SET #PrevRowID = #RowID
END
END
SET NUMERIC_ROUNDABORT ON;
SELECT *
FROM ##TmpPOReport
WHERE [Line Amount] BETWEEN #Param2
AND #Param3
DROP TABLE ##TmpPOReport
END
Thanks!
Sorry if this is not clear. If you want to duplicate the SUM OVER ROWS then you will need to use a CTE or similar sub query below. This is not an optimized query but it will give you the running sum over commodity. It uses RANK() OVER PARTITION to get an instance or row number per PO within a commodity.
DECLARE #mytable TABLE(
Commodity VARCHAR(15) NOT NULL
,PO_NO INTEGER NOT NULL
,LINE_NO INTEGER NOT NULL
,PO_Line_Description VARCHAR(82)
,Commodity_Description VARCHAR(60) NOT NULL
,Fiscal_Year INTEGER NOT NULL
,Vendor_ID INTEGER NOT NULL
,Vendor_Name VARCHAR(20) NOT NULL
,QUANTITY INTEGER NOT NULL
,UNIT_COST NUMERIC(7,2) NOT NULL
,Line_Amount NUMERIC(7,2) NOT NULL
);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-18',1448923,1,'Face Scholastic book order attached.','Sale of Surplus and Obsolete Books',2015,47650,'SCHOLASTIC INC',1,9999.8,9999.8);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1416311,2,'First 12 months maintenance agreement to be billed quarterly at .0039 per b/w copy','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,5148,5148);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1424377,1,NULL,'Sale of Surplus and Obsolete Copy Machines',2015,300590,'KONICA MINOLTA/ CIT',1,2894.58,2894.58);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1404031,1,'1st (12) months of (36) month Lease payment for (1) MPC4503 copier.','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,2050.68,2050.68);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-75',1401552,1,'Blanket order for 50 teachers - each teacher not to exceed $100.00.','Sale of Surplus and Obsolete Paper and Paper Products',2015,27536,'KNOWLEDGE TREE',1,5000,5000);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-78',1521390,1,'PL02286>PRESSURE PLUMBER INSTANT DRAIN OPENER 24 SHOT CARTRIDGE','Sale of Surplus and Obsolete Plumbing Equipment and Supplies',2015,402985,'TECH MECH SUPPLY LLC',480,8,3840);
DECLARE #FiscalYear INT
DECLARE #LowTotalCost INT
DECLARE #HighTotalCost INT
SET #FiscalYear=2015
SET #LowTotalCost=1000
SET #HighTotalCost=10000
SELECT
*
FROM
(
SELECT Commodity,PO_NO,QUANTITY,UNIT_COST,Line_Amount,
RunningTotal=
(
SELECT SUM(TotalCost) FROM
(
SELECT Commodity,TotalCost =(MT.QUANTITY * MT.UNIT_COST),
Instance=RANK()OVER(PARTITION BY Commodity ORDER BY Commodity,PO_NO DESC)
FROM
#mytable MT
WHERE
(MT.QUANTITY * MT.UNIT_COST) BETWEEN #LowTotalCost AND #HighTotalCost
AND
(MT.Fiscal_Year=#FiscalYear)
)AS Y
WHERE
(Y.Commodity=X.Commodity) AND(Y.Instance<=X.Instance)
)
FROM
(
SELECT Commodity,PO_NO,QUANTITY,UNIT_COST,Line_Amount,
TotalCost = (MT.QUANTITY * MT.UNIT_COST),
Instance=RANK()OVER(PARTITION BY Commodity ORDER BY Commodity,PO_NO DESC)
FROM
#mytable MT
WHERE
--IS FILTER HERE -->(MT.QUANTITY * MT.UNIT_COST) BETWEEN #LowTotalCost AND #HighTotalCost
--AND
(MT.Fiscal_Year=#FiscalYear)
)AS X
)AS Z
WHERE
(Commodity IN
(
SELECT Commodity FROM
(
SELECT
Commodity,
Total=SUM(QUANTITY * UNIT_COST)
FROM
#mytable mt2
WHERE
mt2.Fiscal_Year=#FiscalYear
GROUP BY
Commodity
)AS A
WHERE
A.Total BETWEEN #LowTotalCost AND #HighTotalCost
)
)
SELECT
*
FROM
#mytable MT

suddenly query executing slowly

I have very typical situation where I'm doing wrong. I have query which have executed fast initially but later on it taking loads of time to execute ..
My query :
Declare
#fromdate varchar(20) = '01/01/2014',
#todate varchar(20)= '27/05/2015',
#SERVICE_ID CHAR(5) = '123'
DECLARE #FDATE DATETIME ,
#TDATE DATETIME
SET #FDATE = (CONVERT(DATETIME,#fromdate,103))
SET #TDATE = (CONVERT(DATETIME,#todate,103))
IF OBJECT_ID('tempdb..#RUID') IS NOT NULL
DROP TABLE #RUID
CREATE TABLE #RUID(
OFFICEID INT,
OFFICE_TITTLE INT,
MAIN_OFFICE_TITTLE VARCHAR(50),
RLB_NAME VARCHAR(20),
DIST_NAME INT,
district_description VARCHAR(30))
CREATE CLUSTERED INDEX IDX_C_RUID_ID ON #RUID(OFFICEID)
CREATE NONCLUSTERED INDEX IDX_RUID_Name ON #RUID(OFFICE_TITTLE,DIST_NAME)INCLUDE(district_description)
INSERT INTO #RUID
SELECT OFFICEID,
OFFICE_TITTLE,
MAIN_OFFICE_TITTLE,
RLB_NAME,
DIST_NAME,
D.district_description
FROM APSDC..DISTRICT D
INNER JOIN cdma..Unified_RUID_WARD_MSTR I WITH(NOLOCK)
ON D.CDMA_DistrictID = I.DIST_NAME
WHERE RLB_NAME in(3) AND I.STATEID ='01'
select C.MAIN_OFFICE_TITTLE AS 'OFFICE_TITTLE',C.officeid, C.DIST_NAME AS DistrictName, C.district_description,
ISNULL(count(I.ApplicationNumber),0) 'Total_Trans',
isnull(sum(case when Data_Available='Y' AND DataTampered = 'N' then 1 else 0 end),0) 'CategoryA'
from #RUID c with(nolock)
LEFT JOIN Unified_BirthDeathAppDetails I WITH(NOLOCK) ON
(C.OFFICE_TITTLE=I.RUID AND C.DIST_NAME=I.DistrictName)
AND I.Service_Type= '01' AND
(DATEADD(DD,0,DATEDIFF(DD,0,I.Created_Date))) BETWEEN #FDATE AND #TDATE
AND NOT EXISTS(select application_number from reversal_details WITH(NOLOCK) WHERE ApplicationNumber <> i.ApplicationNumber AND service_id='123' )
group by C.MAIN_OFFICE_TITTLE,C.officeid, C.DIST_NAME,C.district_description
order by C.district_description ,C.MAIN_OFFICE_TITTLE
I have tried with #temp table and table variable but it is not even showing any result set. But the same query executed in 2 secs now it is taking lot of time. I have tried UPDATE Statstics on this tables and I have checked with locking also. What I need to do I have followed every other peformance optimized techinique.
Try:
Declare
#FDate Date = '01/01/2014',
#TDate Date= '27/05/2015',
#SERVICE_ID CHAR(5) = '123'
;WITH RUID
AS
(
SELECT OFFICEID,
OFFICE_TITTLE,
MAIN_OFFICE_TITTLE,
RLB_NAME,
DIST_NAME,
D.district_description
FROM APSDC..DISTRICT D
INNER JOIN cdma..Unified_RUID_WARD_MSTR I
ON D.CDMA_DistrictID = I.DIST_NAME
WHERE RLB_NAME in(3) AND I.STATEID ='01'
),
AppDetails
AS
(
SELECT ApplicationNumber,
CASE WHEN Data_Available='Y' AND DataTampered = 'N'
THEN 1
ELSE 0
END CategoryA
FROM Unified_BirthDeathAppDetails I
WHERE I.CreateDate >= #FDate AND I.CreatedDate < #TDate AND
NOT EXISTS
( select application_number
FROM reversal_details
WHERE I.Service_Type= '01' AND
ApplicationNumber <> i.ApplicationNumber AND
service_id= #Service_iD
)
)
SELECT C.MAIN_OFFICE_TITTLE AS OFFICE_TITTLE
,C.officeid, C.DIST_NAME AS DistrictName
, C.district_description
,ISNULL(count(I.ApplicationNumber),0) Total_Trans
,isnull(sum(CategoryA),0) CategoryA
FROM RUID c
LEFT JOIN AppDetails I
ON C.OFFICE_TITTLE=I.RUID AND C.DIST_NAME=I.DistrictName
GROUP BY C.MAIN_OFFICE_TITTLE
,C.officeid
,C.DIST_NAME
,C.district_description
ORDER BU C.district_description
,C.MAIN_OFFICE_TITTLE
Make sure you have decent indexes on RLB_name, StateId, CDMA_DistrictId, Dist_Name, CreatedDate etc.