TSQL calculating from combined queries - sql
I currently have six queries in which I take the results and use a spreadsheet to calculate two different final percentages. I believe that it can be done in a single query, and without a spreadsheet, but I am not knowledgeable enough in SQL to figure it out. I am hoping for some direction from the amazing SQL Gods here on SO.
We have several locations, and calculate a Past Due % and a Past Due Fallout %, per location, based on the average of two other percentages:
Past Due Dollars ÷ Projected Dollars = Past Due Float %
Past Due Units ÷ Total Active Units = Past Due Unit %
( Past Due Unit % + Past Due Dollar % ) / 2 = Past Due %
Fallout uses the same calculations, but looks at what the amounts will be tomorrow.
SOLVED: I spent time learning about sub-queries, and joined them by the STID. Thanks for all who assisted and helped guide me in the
correct direction.
Here is my final code:
SET DATEFIRST 1;
DECLARE #Today date = dbo.getdateparam(92,999);
DECLARE #TodayNum int = DATEPART(dw, #Today);
DECLARE #Saturday date = DATEADD(DAY, (6-#TodayNum)%7, #Today);
DECLARE #PrevSat date = DATEADD(DAY, -7, #Saturday);
Select store.STID As Store,
Proj.ProjRent As Projected,
PDRent.PastDueDollars As PDRent,
UOR.Units As UOR,
PDUnits.UnitsPD As PDUnits,
(PDRent.PastDueDollars / Proj.ProjRent) * 100 As FloatPerc,
(Cast(PDUnits.UnitsPD As Decimal) / Cast(UOR.Units As Decimal)) *
100 As UnitPerc,
Cast(Round((((PDRent.PastDueDollars / Proj.ProjRent) * 100) +
((Cast(PDUnits.UnitsPD As Decimal(18,4)) / Cast(UOR.Units As Decimal(18,4))) *
100)) / 2, 2) As Decimal(18,2)) As PDPerc,
Reds.RedsPD As PDReds,
Round(Cast(Reds.RedsPD As Float) / Cast(UOR.Units As Float) * 100,
2) As RedsPerc
From
-- Stores
(Select Distinct Stores.STID,
Stores.StoreName,
Stores.EMail,
Stores.ManagersName
From Stores
Where Stores.STID Not In (7, 999)) As store
-- Projected Rent
Left Join (Select CashProj.STID,
Sum(CashProj.ProjectedRental) As ProjRent
From CashProj
Where CashProj.ProjectionDate Between DateAdd(mm, DateDiff(mm, 0, #Today),
0) And DateAdd(mm, DateDiff(mm, 0, #Today) + 1, 0)
Group By CashProj.STID) As Proj On store.STID = Proj.STID
-- Past Due Float
Left Join (Select Agreemnt.STID As STID,
Sum(DateDiff(d, Agreemnt.DueDate, (Case DatePart(dw, #Today)
When '1' Then DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw,
#Today)) % 7, #Today)) When '6' Then #Today
Else DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today)
End)) * Round(Agreemnt.WeeklyRate / 7, 2)) As PastDueDollars,
DatePart(dw, #Today) As TodayNum,
DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7,
#Today)) As PrevSat,
DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today) As Saturday
From Agreemnt
Where Agreemnt.AStatID = 1 And Agreemnt.DueDate < (Case DatePart(dw,
#Today)
When '1' Then DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw,
#Today)) % 7, #Today)) When '6' Then #Today
Else DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today)
End) And Agreemnt.RentToRent = 0
Group By Agreemnt.STID) As PDRent On store.STID = PDRent.STID
-- Units On Rent
Left Join (Select Invntry.STID,
Cast(Count(Invntry.StockNumber) As Int) As Units
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And
Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And
Agreemnt.AgreeID = AgreHist.AgreeID And Agreemnt.AStatID =
AgreHist.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And
Agreemnt.AStatID = 1
Group By Invntry.STID) As UOR On store.STID = UOR.STID
-- Past Due Units
Left Join (Select Invntry.STID,
Cast(Count(Invntry.StockNumber) As Int) As UnitsPD
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And
Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And
Agreemnt.AgreeID = AgreHist.AgreeID And AgreHist.AStatID =
Agreemnt.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And
Agreemnt.AStatID = 1 And Agreemnt.DueDate < (Case #TodayNum When '1' Then #PrevSat When '6' Then #Today Else #Saturday End) And Agreemnt.RentToRent = 0
Group By Invntry.STID) As PDUnits On store.STID = PDUnits.STID
-- Reds
Left Join (Select Invntry.STID,
Count(Invntry.StockNumber) As RedsPD
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And
Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And
Agreemnt.AgreeID = AgreHist.AgreeID And Agreemnt.AStatID =
AgreHist.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And
Agreemnt.AStatID = 1 And Agreemnt.DueDate < DateAdd(day, -15, Case
Cast(DatePart(dw, #Today) As Int)
When '1' Then Cast(DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw,
#Today)) % 7, #Today)) As Date)
When '6' Then Cast(#Today As Date)
Else Cast(DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today) As
Date) End) And Agreemnt.RentToRent = 0
Group By Invntry.STID) As Reds On store.STID = Reds.STID
Order By Store
You can't use variables like that and you can't do this in one aggregate query because it's going to create a massive Cartesian product and give you incorrect results.
You could use CTE's or subqueries for each query you have listed and then join them together on the STID and apply your formulas.
example...
/* declare all variables here */
DECLARE #dayNum INT;
SET #dayNum = datepart(dw, getdate());
with PastDueDollars as (
select ... from ... group by STID
), ProjectedDollers as (
select ... from ... group by STID
), PastDueUnits as (
select ... from ... group by STID
), preFinal as (
select
PastDueDollarRatio = pdd.PastDueDollars / pd.ProjRent,
PastDueUnitRatio = pdu.UnitsPD / tau.TotalActiveUnits
/* add the other calculations */
from
PastDueDollars pdd
inner join ProjectedDollers pd on pdd.STID = pd.STID
inner join PastDueUnits pdu on pdu.STID = pd.STID
/* join more CTEs */
)
select
f.*,
PastDueRatio = (f.PastDueDollarRatio + f.PastDueUnitRatio) / 2
/* and so on for the other calculations of calculations... */
from
preFinal f
You never set the variables to any real value, the you proceed to select the useless variable in a irrelevant SELECT statement.
in the following line
Set #pdD = Sum( Case When a.DueDate < GetDate() Then DateDiff(d,a.DueDate,#runDate) * (a.WeeklyRate/7) )
You are not stating where s.DueDate comes from. It wont even compile.
In this SELECT the table is completely irrelevant
Select a.STID as STID,
#pdU As PastDueUnits,
#activeU As ActiveUnits,
#pdD As PastDueDollars,
#projRent As ProjRent,
#pdP As PastDuePerc,
#foU As FalloutUnits,
#foD As FalloutDollars,
#foP As FalloutPerc
FROM Agreemnt a INNER JOIN CashProj on a.STID = CashProj.STID JOIN Invntry i ON a.STID = i.STID JOIN AgreHist h On i.InvID = h.InvID And i.STID = h.STID INNER JOIN Agreemnt a On a.STID = h.STID AND a.AgreeID = h.AgreeID AND a.AStatID = h.AStatID
WHERE a.RentToRent = 0 AND i.InvStatID = 11 AND i.DisposalDate IS NULL AND a.AStatID = 1 AND a.DueDate < DateAdd(d, #runDate, GetDate())
GROUP BY a.STID
ORDER BY a.STID
This is an example to how you should set the values before you try to do calculations with the variables.
DECLARE #dayNum INT;
SET #dayNum = datepart(dw, getdate());
Select Invntry.STID,
#foU = COUNT(Invntry.StockNumber)
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And
Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And Agreemnt.AgreeID =
AgreHist.AgreeID And Agreemnt.AStatID = AgreHist.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And
Agreemnt.AStatID = 1 And Agreemnt.DueDate < DateAdd(d, Case #dayNum When '1' Then -2 When '2' Then 1 When '3' Then 1 When '4' Then 1
When '5' Then 1 When '6' Then 0 When '7' Then -1 End, GetDate()) And Agreemnt.RentToRent = 0
Group By Invntry.STID
My problem was a lack of knowledge and understanding of SQL, which I'm improving on. Here is what I ended up with that gave me the result I desired, whether the most efficient or not.
SET DATEFIRST 1;
DECLARE #Today date = dbo.getdateparam(92,999);
DECLARE #TodayNum int = DATEPART(dw, #Today);
DECLARE #Saturday date = DATEADD(DAY, (6-#TodayNum)%7, #Today);
DECLARE #PrevSat date = DATEADD(DAY, -7, #Saturday);
Select store.STID As Store,
Proj.ProjRent As Projected,
PDRent.PastDueDollars As PDRent,
UOR.Units As UOR,
PDUnits.UnitsPD As PDUnits,
(PDRent.PastDueDollars / Proj.ProjRent) * 100 As FloatPerc,
(Cast(PDUnits.UnitsPD As Decimal) / Cast(UOR.Units As Decimal)) * 100 As UnitPerc,
Cast(Round((((PDRent.PastDueDollars / Proj.ProjRent) * 100) + ((Cast(PDUnits.UnitsPD As Decimal(18,4)) / Cast(UOR.Units As Decimal(18,4))) * 100)) / 2, 2) As Decimal(18,2)) As PDPerc,
Reds.RedsPD As PDReds,
Round(Cast(Reds.RedsPD As Float) / Cast(UOR.Units As Float) * 100,2) As RedsPerc
From
-- Stores
(Select Distinct Stores.STID,
Stores.StoreName,
Stores.EMail,
Stores.ManagersName
From Stores
Where Stores.STID Not In (7, 999)) As store
-- Projected Rent
Left Join (Select CashProj.STID,
Sum(CashProj.ProjectedRental) As ProjRent
From CashProj
Where CashProj.ProjectionDate Between DateAdd(mm, DateDiff(mm, 0, #Today),0) And DateAdd(mm, DateDiff(mm, 0, #Today) + 1, 0)
Group By CashProj.STID) As Proj On store.STID = Proj.STID
-- Past Due Float
Left Join (Select Agreemnt.STID As STID,
Sum(DateDiff(d, Agreemnt.DueDate, (Case DatePart(dw, #Today) When '1' Then DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today)) When '6' Then #Today
Else DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today) End)) * Round(Agreemnt.WeeklyRate / 7, 2)) As PastDueDollars,
DatePart(dw, #Today) As TodayNum,
DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7,
#Today)) As PrevSat,
DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today) As Saturday
From Agreemnt
Where Agreemnt.AStatID = 1 And Agreemnt.DueDate < (Case DatePart(dw, #Today) When '1' Then DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today)) When '6' Then #Today Else DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today) End) And Agreemnt.RentToRent = 0
Group By Agreemnt.STID) As PDRent On store.STID = PDRent.STID
-- Units On Rent
Left Join (Select Invntry.STID,
Cast(Count(Invntry.StockNumber) As Int) As Units
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And
Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And
Agreemnt.AgreeID = AgreHist.AgreeID And Agreemnt.AStatID =
AgreHist.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And Agreemnt.AStatID = 1
Group By Invntry.STID) As UOR On store.STID = UOR.STID
-- Past Due Units
Left Join (Select Invntry.STID,
Cast(Count(Invntry.StockNumber) As Int) As UnitsPD
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And
Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And
Agreemnt.AgreeID = AgreHist.AgreeID And AgreHist.AStatID =
Agreemnt.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And
Agreemnt.AStatID = 1 And Agreemnt.DueDate < (Case #TodayNum When '1' Then #PrevSat When '6' Then #Today Else #Saturday End) And Agreemnt.RentToRent = 0
Group By Invntry.STID) As PDUnits On store.STID = PDUnits.STID
-- Reds
Left Join (Select Invntry.STID,
Count(Invntry.StockNumber) As RedsPD
From Invntry
Inner Join AgreHist On Invntry.InvID = AgreHist.InvID And Invntry.STID = AgreHist.STID
Inner Join Agreemnt On Agreemnt.STID = AgreHist.STID And Agreemnt.AgreeID = AgreHist.AgreeID And Agreemnt.AStatID = AgreHist.AStatID
Where Invntry.InvStatID = 11 And Invntry.DisposalDate Is Null And Agreemnt.AStatID = 1 And Agreemnt.DueDate < DateAdd(day, -15, Case Cast(DatePart(dw, #Today) As Int) When '1' Then Cast(DateAdd(DAY, -7, DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today)) As Date) When '6' Then Cast(#Today As Date) Else Cast(DateAdd(DAY, (6 - DatePart(dw, #Today)) % 7, #Today) As Date) End) And Agreemnt.RentToRent = 0
Group By Invntry.STID) As Reds On store.STID = Reds.STID
Order By Store
Related
Function to exclude Saturdays and Sundays
I created a "Daily Sales Query" that captures all the Total Sales entered from the previous work day which runs Mon-Fri at 8AM. Question is, if it is Monday today, how can I capture the records from Friday. so that I can exclude weekends. Because if it is Monday, the total sales displays 0 which actually makes sense because Sunday is a not a work day. Please assist. See my current code: SELECT CONVERT(VARCHAR, DATEADD(dd, - 1, GETDATE()), 103) AS Date, 'Sales Orders' AS Type, COUNT(o.SalesOrderID) AS Orders, SUM(d.QtyOrdered) AS Chairs, ISNULL(ROUND(SUM(d.ExtendedPrice), 2), 0) AS [Total Ex GST] FROM dbo.SalesOrder o LEFT OUTER JOIN dbo.SalesOrderDetails d ON o.SalesOrderID = d.SalesOrderID WHERE (o.EntryDate >= CONVERT(CHAR(8), DATEADD(dd, - 1, GETDATE()), 112)) AND (o.EntryDate < CONVERT(CHAR(8), GETDATE(), 112)) AND (o.CustomerID <> 187);
You can use datediff(dd,0,getdate()) % 7 = 0 to determine if the current date is a Monday regardless of any other server settings (this is because the zero date in SQL Server is 1900-01-01 which happens to be a Monday). declare #start date; declare #finish date; set #start = dateadd(dd, case when datediff(dd,0,getdate()) % 7 = 0 then -3 else -1 end, getdate()); set #finish = dateadd(dd,1,#start); select #start, datename(weekday,#start) , #finish, datename(weekday,#finish) , datename(weekday,getdate()) ; So in your query I would use: declare #start date; declare #finish date; set #start = dateadd(dd, case when datediff(dd,0,getdate()) % 7 = 0 then -3 else -1 end, getdate()); set #finish = dateadd(dd,1,#start); SELECT CONVERT(VARCHAR, #start, 103) AS Date, 'Sales Orders' AS Type, COUNT(o.SalesOrderID) AS Orders, SUM(d.QtyOrdered) AS Chairs, ISNULL(ROUND(SUM(d.ExtendedPrice), 2), 0) AS [Total Ex GST] FROM dbo.SalesOrder o LEFT OUTER JOIN dbo.SalesOrderDetails d ON o.SalesOrderID = d.SalesOrderID WHERE o.EntryDate >= #start AND o.EntryDate < #finish AND o.CustomerID <> 187 ;
If you use a case statement to determine how many days in the past you need to go e.g. dateadd(dd, case when datepart(weekday,getdate()) = 1 then -3 else -1 end, getdate()) -- StartDate dateadd(dd, case when datepart(weekday,getdate()) = 1 then -2 else 0 end, getdate()) -- EndDate so your code would look like SELECT CONVERT(VARCHAR, dateadd(dd, case when datepart(weekday,getdate()) = 1 then -3 else -1 end, getdate()), 103) AS Date, 'Sales Orders' AS Type, COUNT(o.SalesOrderID) AS Orders, SUM(d.QtyOrdered) AS Chairs, ISNULL(ROUND(SUM(d.ExtendedPrice), 2), 0) AS [Total Ex GST] FROM dbo.SalesOrder o LEFT OUTER JOIN dbo.SalesOrderDetails d ON o.SalesOrderID = d.SalesOrderID WHERE (o.EntryDate >= CONVERT(CHAR(8), dateadd(dd, case when datepart(weekday,getdate()) = 1 then -3 else -1 end, getdate()), 112)) AND (o.EntryDate < CONVERT(CHAR(8), dateadd(dd, case when datepart(weekday,getdate()) = 1 then -2 else 0 end, getdate()), 112)) AND (o.CustomerID <> 187); PS: Do confirm that weekday 1 is Monday on your server.
SQL Server 2014 Select total for each day
Hello I am working on a dataset for a report in SSRS and I have a query which gives the total requests in the backlog : SELECT COUNT(*) as NB FROM p_rqt WITH (NOLOCK) INNER JOIN p_cpy WITH (NOLOCK) ON p_cpy.CpyInCde = p_rqt.OrigCpyInCde WHERE CpyTypInCde IN (27, 31) AND p_rqt.RqtNatInCde IN (74, 75, 76) AND HeadRqtInCde = 0 AND p_rqt.OrigCpyInCde LIKE CASE WHEN #Client = 0 THEN '%' ELSE #Client END AND ((RcvDte < DATEADD(day, 1, #DateDeb) AND RqtEndDte IS NULL) OR (RcvDte < DATEADD(day, 1, #DateDeb) AND RqtEndDte > DATEADD(day, 1, #DateDeb))) and I want to retrieve the total amount left per day. I tried lot of things like this : SELECT CONVERT(date,rcvdte,103), count(*) as nb FROM p_rqt p WITH (NOLOCK) INNER JOIN p_cpy WITH (NOLOCK) ON p_cpy.CpyInCde = p.OrigCpyInCde WHERE CpyTypInCde IN (27, 31) AND p.RqtNatInCde IN (74, 75, 76) AND HeadRqtInCde = 0 AND ((RcvDte < DATEADD(day, 1, '20170901') AND RqtEndDte IS NULL) OR (RcvDte < DATEADD(day, 1, '20170901') AND RqtEndDte > DATEADD(day, 1, '20170901'))) group by CONVERT(date,rcvdte,103) order by CONVERT(date,rcvdte,103) I tried inner join subqueries, Sum and other stuff but all I can manage to do is to have the number of records added per day and I want something like this : date: NB: 01/01/2017 1950 02/01/2017 1954 (+4 items) 03/01/2017 1945 (-9 items) Thank you
Use LAG: WITH cte AS ( SELECT CONVERT(date, rcvdte, 103) AS date, COUNT(*) AS nb FROM p_rqt p WITH (NOLOCK) INNER JOIN p_cpy WITH (NOLOCK) ON p_cpy.CpyInCde = p.OrigCpyInCde WHERE CpyTypInCde IN (27, 31) AND p.RqtNatInCde IN (74, 75, 76) AND HeadRqtInCde = 0 AND ((RcvDte < DATEADD(day, 1, '20170901') AND RqtEndDte IS NULL) OR (RcvDte < DATEADD(day, 1, '20170901') AND RqtEndDte > DATEADD(day, 1, '20170901'))) GROUP BY CONVERT(date, rcvdte, 103) ORDER BY CONVERT(date, rcvdte, 103) ) SELECT t1.date, (SELECT SUM(t2.nb) FROM cte t2 WHERE t2.date <= t1.date) AS nb, CASE WHEN t1.nb - LAG(t1.nb, 1, t1.nb) OVER (ORDER BY t1.date) > 0 THEN '(+' + (t1.nb - LAG(t1.nb, 1, t1.nb) OVER (ORDER BY t1.date)) + ' items)' ELSE '(' + (t1.nb - LAG(t1.nb, 1, t1.nb) OVER (ORDER BY t1.date)) + ' items)' END AS difference FROM cte t1 ORDER BY t1.date;
So i found a solution but it is really slow, i still post the answer anyway DECLARE #Tb TABLE ( Colonne1 Datetime, Colonne2 INT ) DECLARE #Debut Datetime = '01/09/2017' WHILE #Debut < '13/09/2017' BEGIN DECLARE #Compteur int = ( SELECT COUNT(1) NB FROM p_rqt WITH (NOLOCK) INNER JOIN p_cpy WITH (NOLOCK) ON p_cpy.CpyInCde = p_rqt.OrigCpyInCde WHERE CpyTypInCde IN (27, 31) AND p_rqt.RqtNatInCde IN (74, 75, 76) AND HeadRqtInCde = 0 AND p_rqt.OrigCpyInCde LIKE '%' AND ( (RcvDte < #Debut AND RqtEndDte IS NULL) OR (RcvDte < #Debut AND RqtEndDte > #Debut) ) ) INSERT INTO #Tb (Colonne1, Colonne2) VALUES (#Debut, #Compteur) SET #Debut = DATEADD(day, 1, #Debut) IF #Debut > '13/09/2017' BREAK ELSE CONTINUE END SELECT * FROM #Tb
Calculating monthly sales based on a quarter
I need to calculate the monthly sales for a given quarter. Here is my code to calculate the previous quarter. set #quarter = datepart(QQ, getdate()) - 1 if #quarter = 0 begin set #quarter = 4 set #year = datepart(year, getdate()) -1 end else set #year = datepart(year, getdate()) Here is my code to calculate the average monthly sales for the entire quarter. SELECT TOP 5 d.sdealer_name, COUNT(c.icontract_id) / 3 as 'AverageMonthlySales' FROM dealers d INNER JOIN contracts c ON c.sdealer_number = d.sdealer_number WHERE (d.sdealer_number NOT LIKE '%demo%' AND d.sdealer_status in ('A', 'R') AND c.sagent_number = #sagent_number AND c.sstatus in ('P', 'A', 'C', 'E') AND c.iproduct_type_id in (4) AND DATEPART(QQ, c.dtcontract_sale_date) = #quarter AND DATEPART(year, c.dtcontract_sale_date) = #year) GROUP BY d.sdealer_name ORDER BY COUNT(distinct c.icontract_id) desc How would I calculate the total sales for each month dynamically for the given quarter?
ctrl+h ... just replace quarter datepart with month. is there some reason you haven't tried that? set #month = datepart(month, getdate()) - 1 if #month = 0 begin set #month = 12 set #year = datepart(year, getdate()) -1 end else set #year = datepart(year, getdate()) SELECT TOP 5 d.sdealer_name, COUNT(c.icontract_id) as 'AverageMonthlySales' FROM dealers d INNER JOIN contracts c ON c.sdealer_number = d.sdealer_number WHERE (d.sdealer_number NOT LIKE '%demo%' AND d.sdealer_status in ('A', 'R') AND c.sagent_number = #sagent_number AND c.sstatus in ('P', 'A', 'C', 'E') AND c.iproduct_type_id in (4) AND DATEPART(month, c.dtcontract_sale_date) = #month AND DATEPART(year, c.dtcontract_sale_date) = #year) GROUP BY d.sdealer_name ORDER BY COUNT(distinct c.icontract_id) desc
Applying different time period Groupings to a set of data
The following was a pattern I started to use two years ago and it is repeated over and over in my legacy code. It effectively groups the same data using different time periods. Is there a standard way I should be approaching this or is this long winded method as good as I'll get? Another way of putting this question is how can the following be made more concise? All 4 queries come out of the same data source and all four go into the same output table can these 4 queries be amalgamated into 1 shorter script? DECLARE #myDate DATETIME = CONVERT(DATETIME,CONVERT(VARCHAR(11),GETDATE(),106)); DECLARE #myFirstDateLastMth CHAR(8) =CONVERT(CHAR(6),DATEADD(mm,-1,#myDate-1),112) + '01'; DECLARE #myFirstDateCurrentMth CHAR(8) =CONVERT(CHAR(6),DATEADD(mm,0,#myDate-1),112) + '01'; DELETE FROM WH.dbo.tb_myTable --day on day========== INSERT INTO WH.dbo.tb_myTable SELECT TimePeriod = CASE WHEN x.DateKey = CONVERT(VARCHAR(11),#myDate - 1,112) THEN 'Day' WHEN x.DateKey = CONVERT(VARCHAR(11),#myDate - 2,112) THEN 'Day-1' END, Game = x.Name, Score = SUM(x.Score), Ticks = SUM(x.Ticks), ScorePerTick = SUM(x.Score)/SUM(x.Ticks) FROM #LimitedBetinfo x WHEREx.DateKey >= CONVERT(VARCHAR(11),#myDate - 2,112) GROUP BY CASE WHEN x.DateKey = CONVERT(VARCHAR(11),#myDate - 1,112) THEN 'Day' WHEN x.DateKey = CONVERT(VARCHAR(11),#myDate - 2,112) THEN 'Day-1' END, x.Name; --wk on wk========== INSERT INTO WH.dbo.tb_myTable SELECT TimePeriod = CASE WHEN x.DateKey >= CONVERT(VARCHAR(11),#myDate - 7,112) THEN 'Week' WHEN x.DateKey < CONVERT(VARCHAR(11),#myDate - 7,112) AND x.DateKey >= CONVERT(VARCHAR(11),#myDate - 14,112) THEN 'Week-1' END, Game = x.Name, Score = SUM(x.Score), Ticks = SUM(x.Ticks), ScorePerTick = SUM(x.Score)/SUM(x.Ticks) FROM #LimitedBetinfo x WHERE x.DateKey >= CONVERT(VARCHAR(11),#myDate - 14,112) GROUP BY CASE WHEN x.DateKey >= CONVERT(VARCHAR(11),#myDate - 7,112) THEN 'Week' WHEN x.DateKey < CONVERT(VARCHAR(11),#myDate - 7,112) AND x.DateKey >= CONVERT(VARCHAR(11),#myDate - 14,112) THEN 'Week-1' END, g.Name; --mth on mth========== INSERT INTO WH.dbo.tb_myTable SELECT TimePeriod = CASE WHEN x.DateKey >= CONVERT(VARCHAR(11),#myDate - 28,112) THEN 'Month' WHEN x.DateKey < CONVERT(VARCHAR(11),#myDate - 28,112) AND x.DateKey >= CONVERT(VARCHAR(11),#myDate - 56,112) THEN 'Month-1' END, Game = x.Name, Score = SUM(x.Score), Ticks = SUM(x.Ticks), ScorePerTick = SUM(x.Score)/SUM(x.Ticks) FROM #LimitedBetinfo x WHERE x.DateKey >= CONVERT(VARCHAR(11),#myDate - 56,112) GROUP BY CASE WHEN x.DateKey >= CONVERT(VARCHAR(11),#myDate - 28,112) THEN 'Month' WHEN x.DateKey < CONVERT(VARCHAR(11),#myDate - 28,112) AND x.DateKey >= CONVERT(VARCHAR(11),#myDate - 56,112) THEN 'Month-1' END, g.Name; --MTD and PrevCalMonth========== INSERT INTO WH.dbo.tb_myTable SELECT TimePeriod = CASE WHEN x.DateKey >= #myFirstDateCurrentMth THEN 'MTD' WHEN x.DateKey < #myFirstDateCurrentMth AND x.DateKey >=#myFirstDateLastMth THEN 'PrevCalMonth' END, Game = x.Name, Score = SUM(x.Score), Ticks = SUM(x.Ticks), ScorePerTick = SUM(x.Score)/SUM(x.Ticks) FROM #LimitedBetinfo x WHERE x.DateKey >= CONVERT(CHAR(6),DATEADD(mm,-1,#myDate-1),112) + '01' GROUP BY CASE WHEN x.DateKey >= #myFirstDateCurrentMth THEN 'MTD' WHEN x.DateKey < #myFirstDateCurrentMth AND x.DateKey >=#myFirstDateLastMth THEN 'PrevCalMonth' END, g.Name;
I would make it a single insert statement. Would prefer for now not to use the group by grouping sets, cube, or rollup as that I don't see how I could limit the rows calculated over individual day groups from being less than those calculated over larger time period groups. So, to keep that from happening you could create a common-table-expression (;WITH mycte AS (...subquery...)), temp table, table variable, or XML formatted text object that would contain the time periods, one row/element for each. This script can also be run with more or less time periods defined to get all results with only one trip from the app to the server. Here's an example with temp table, that could also be easily made into a table variable: --Define time periods CREATE TABLE #TempTimePeriods ( TimePeriod VARCHAR(20) PRIMARY KEY, TPBegin VARCHAR(11) NOT NULL, TPEnd VARCHAR(11) NULL ); DECLARE #myDate DATETIME = '2012-10-10'; DECLARE #myDateMinusOne DATETIME = DATEADD(dd, -1, #myDate); INSERT INTO #TempTimePeriods ( TimePeriod, TPBegin, TPEnd ) SELECT [TimePeriod], CONVERT(VARCHAR(11), TPBegin, 112) TPBegin, CONVERT(VARCHAR(11), TPEnd, 112) TPEnd FROM ( SELECT 'Day' [TimePeriod], #myDate - 1 TPBegin, #myDate - 1 TPEnd UNION ALL SELECT 'Day-1' [TimePeriod], #myDate - 2 TPBegin, #myDate - 2 TPEnd UNION ALL SELECT 'Week' [TimePeriod], #myDate - 7 TPBegin, NULL TPEnd UNION ALL SELECT 'Week-1' [TimePeriod], #myDate - 14 TPBegin, #myDate - 8 TPEnd UNION ALL SELECT 'Month' [TimePeriod], #myDate - 28 TPBegin, NULL TPEnd UNION ALL SELECT 'Month-1' [TimePeriod], #myDate - 56 TPBegin, #myDate - 29 TPEnd UNION ALL SELECT 'MTD' [TimePeriod], DATEADD(dd, -1 * DAY(#myDateMinusOne) + 1, #myDateMinusOne) TPBegin, NULL TPEnd UNION ALL SELECT 'PrevCalMonth' [TimePeriod], DATEADD(mm,-1,DATEADD(dd, -1 * DAY(#myDateMinusOne) + 1, #myDateMinusOne)) TPBegin, DATEADD(dd, -1 * DAY(#myDateMinusOne), #myDateMinusOne) TPEnd ) TT; And here is the main query... --compute/insert results INSERT INTO WH.dbo.tb_myTable SELECT TimePeriods.TimePeriod, x.Name Game, SUM(x.Score) Score, SUM(x.Ticks) Ticks, CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick FROM #TempTimePeriods TimePeriods --for periods with no data use left outer join to return 0-value results, otherwise inner join LEFT OUTER JOIN #LimitedBetInfo x ON x.DateKey >= [TimePeriods].TPBegin AND ( [TimePeriods].TPEnd IS NULL OR x.DateKey <= [TimePeriods].TPEnd ) GROUP BY TimePeriods.TimePeriod, x.Name You could also eliminate the the #TempTimePeriods table using a Common-Table-Expression below: DECLARE #myDate DATETIME = '2012-10-10'; DECLARE #myDateMinusOne DATETIME = DATEADD(dd, -1, #myDate); ;WITH TimePeriods AS ( SELECT [TimePeriod], CONVERT(VARCHAR(11), TPBegin, 112) TPBegin, CONVERT(VARCHAR(11), TPEnd, 112) TPEnd FROM ( SELECT 'Day' [TimePeriod], #myDate - 1 TPBegin, #myDate - 1 TPEnd UNION ALL SELECT 'Day-1' [TimePeriod], #myDate - 2 TPBegin, #myDate - 2 TPEnd UNION ALL SELECT 'Week' [TimePeriod], #myDate - 7 TPBegin, NULL TPEnd UNION ALL SELECT 'Week-1' [TimePeriod], #myDate - 14 TPBegin, #myDate - 8 TPEnd UNION ALL SELECT 'Month' [TimePeriod], #myDate - 28 TPBegin, NULL TPEnd UNION ALL SELECT 'Month-1' [TimePeriod], #myDate - 56 TPBegin, #myDate - 29 TPEnd UNION ALL SELECT 'MTD' [TimePeriod], DATEADD(dd, -1 * DAY(#myDateMinusOne) + 1, #myDateMinusOne) TPBegin, NULL TPEnd UNION ALL SELECT 'PrevCalMonth' [TimePeriod], DATEADD(mm,-1,DATEADD(dd, -1 * DAY(#myDateMinusOne) + 1, #myDateMinusOne)) TPBegin, DATEADD(dd, -1 * DAY(#myDateMinusOne), #myDateMinusOne) TPEnd ) TT ) INSERT INTO WH.dbo.tb_myTable SELECT TimePeriods.TimePeriod, x.Name Game, SUM(x.Score) Score, SUM(x.Ticks) Ticks, CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick FROM [TimePeriods] --for periods with no data use left outer join to return 0-value results, otherwise inner join LEFT OUTER JOIN #LimitedBetInfo x ON x.DateKey >= [TimePeriods].TPBegin AND ( [TimePeriods].TPEnd IS NULL OR x.DateKey <= [TimePeriods].TPEnd ) GROUP BY [TimePeriods].TimePeriod, x.Name And lastly you could define the time periods in an XML string-handy for passing to a stored procedure if that's your preference and proceed as follows: --example XML string with time period definitions DECLARE #TimePeriodsXml NVARCHAR(MAX) = ' <TimePeriod name="Day" tpbegin="20121010" tpend="20121010" /> <TimePeriod name="Day-1" tpbegin="20121009" tpend="20121009" /> <TimePeriod name="Week" tpbegin="20121004"/> <TimePeriod name="Week-1" tpbegin="20120927" tpend="20121004" /> <TimePeriod name="Month" tpbegin="20120913" /> <TimePeriod name="Month-1" tpbegin="20120815" tpend="20120912" /> <TimePeriod name="MTD" tpbegin="20121001" /> <TimePeriod name="PrevCalMonth" tpbegin="20120901" tpend="20120930" /> '; and the main query modified to read the XML: SELECT TimePeriods.TimePeriod, x.Name Game, SUM(x.Score) Score, SUM(x.Ticks) Ticks, CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick FROM ( SELECT E.TimePeriod.value('./#name', 'VARCHAR(20)') TimePeriod, E.TimePeriod.value('./#tpbegin', 'VARCHAR(20)') TPBegin, E.TimePeriod.value('./#tpend', 'VARCHAR(20)') TPEnd FROM ( SELECT CAST(#TimePeriodsXml AS XML) tpxml ) TT CROSS APPLY tpxml.nodes('/TimePeriod') AS E(TimePeriod) ) TimePeriods --for periods with no data use left outer join to return 0-value results, otherwise inner join LEFT OUTER JOIN #LimitedBetInfo x ON x.DateKey >= [TimePeriods].TPBegin AND ( [TimePeriods].TPEnd IS NULL OR x.DateKey <= [TimePeriods].TPEnd ) GROUP BY TimePeriods.TimePeriod, x.Name For an example of how the XML stringed query could be turned into a procedure, to support a single parameter of 1 or more time periods: CREATE PROCEDURE dbo.GetTimePeriodAggregates #TimePeriodsXmlString NVARCHAR(MAX) AS BEGIN SET NOCOUNT ON; SELECT TimePeriods.TimePeriod, x.Name Game, SUM(x.Score) Score, SUM(x.Ticks) Ticks, CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick FROM ( SELECT E.TimePeriod.value('./#name', 'VARCHAR(20)') TimePeriod, E.TimePeriod.value('./#tpbegin', 'VARCHAR(20)') TPBegin, E.TimePeriod.value('./#tpend', 'VARCHAR(20)') TPEnd FROM ( SELECT CAST(#TimePeriodsXml AS XML) tpxml ) TT CROSS APPLY tpxml.nodes('/TimePeriod') AS E(TimePeriod) ) TimePeriods LEFT OUTER JOIN #LimitedBetInfo x ON x.DateKey BETWEEN TimePeriods.TPBegin AND TimePeriods.TPEnd GROUP BY TimePeriods.TimePeriod, x.Name END Which could be run as: --This declare is just an example, it could be instead a parameter passed from an application DECLARE #ThisExecutionsXmlString NVARCHAR(MAX) = N' <TimePeriod name="Day" tpbegin="20121010" tpend="20121010" /> <TimePeriod name="Day-1" tpbegin="20121009" tpend="20121009" /> <TimePeriod name="Week" tpbegin="20121004"/> <TimePeriod name="Week-1" tpbegin="20120927" tpend="20121004" /> <TimePeriod name="Month" tpbegin="20120913" /> <TimePeriod name="Month-1" tpbegin="20120815" tpend="20120912" /> <TimePeriod name="MTD" tpbegin="20121001" /> <TimePeriod name="PrevCalMonth" tpbegin="20120901" tpend="20120930" /> '; INSERT INTO WH.dbo.tb_myTable EXEC dbo.GetTimePeriodAggregates #TimePeriodsXmlString=#ThisExecutionsXmlString
You can create this stored procedure CREATE PROCEDURE InsertData #minLimit date, #maxLimit date, #minTerm nvarchar(50), #maxTerm nvarchar(50) AS BEGIN SET NOCOUNT ON; INSERT INTO tb_myTable SELECT [TimePeriod] = CASE WHEN x.DateKey >= #maxLimit THEN #maxTerm ELSE #minTerm END, [Game] = x.Name, [Score] = SUM(x.[Score]), [Ticks] = SUM(x.[Ticks]), [ScorePerTick] = SUM(x.[Score])/SUM(x.[Ticks]) FROM #LimitedBetinfo x WHERE x.DateKey >= #minLimit GROUP BY CASE WHEN x.DateKey >= #maxLimit THEN #maxTerm ELSE #minTerm END, x.Name END GO And use like this TRUNCATE TABLE tb_myTable DECLARE #today date = cast(getdate() as date) DECLARE #yesterday date = dateadd(day, -1, #today) EXECUTE dbo.InsertData #yesterday, #today, N'Day-1', N'Day' DECLARE #thisweek date = DATEADD(ww, DATEDIFF(ww,0,GETDATE()), 0) DECLARE #lastweek date = DATEADD(ww, -1, #thisweek) EXECUTE dbo.InsertData #lastweek, #thisweek, N'Week-1', N'Week' DECLARE #prev28 date = dateadd(day, -28, #today) DECLARE #prev56 date = dateadd(day, -56, #today) EXECUTE dbo.InsertData #prev56, #prev28, N'Month-1', N'Month' DECLARE #thismonth date = DATEADD(mm, DATEDIFF(mm,0,GETDATE()), 0) DECLARE #lastmonth date = DATEADD(mm, -1, #thismonth) EXECUTE dbo.InsertData #lastmonth, #thismonth, N'PrevCalMonth', N'MTD'
Use parameters - VALUES As a Table Source and apply them as parameters in CROSS APPLY with derived table DECLARE #myDate datetime = CAST(GETDATE() AS date); IF OBJECT_ID('WH.dbo.tb_myTable') IS NOT NULL DROP TABLE WH.dbo.tb_myTable SELECT TimePeriod, Game, Score, Ticks, ScorePerTicks INTO WH.dbo.tb_myTable FROM (VALUES('Day', DATEADD(day, -1, #myDate), #myDate), ('Day-1', DATEADD(day, -2, #myDate), DATEADD(day, -2, #myDate)), ('Week', DATEADD(day, -7, #myDate), #myDate), ('Week-1', DATEADD(day, -14, #myDate), DATEADD(day, -8, #myDate)), ('Month', DATEADD(day, -28, #myDate), #myDate), ('Month-1', DATEADD(day, -56, #myDate), DATEADD(day, -29, #myDate)), ('MTD', DATEADD(DAY, 1 - DAY(#myDate), #myDate), #myDate), ('PrevCalMonth', DATEADD(DAY, 1 - DAY(#myDate), DATEADD(MONTH, -1, #myDate)), DATEADD(DAY, - DAY(#myDate), #myDate))) RParameters(TimePeriod, BDate, EDate) CROSS APPLY (SELECT x.Name AS Game, SUM(x.Score) AS Score, SUM(x.Ticks) AS Ticks, SUM(x.Score) / SUM(x.Ticks) AS ScorePerTicks FROM #LimitedBetinfo x WHERE DateKey BETWEEN RParameters.BDate AND RParameters.EDate GROUP BY Name) AS o Demo on SQLFiddle
A possible improvement on fred's answer. Not in terms of speed, just readability / modifiability by removing the extra CASE. As a suggestion, I also replaced the passing of both strings (e.g. DAY and DAY-1) with a single string and to have the other just be a concat; this would however cause PrevCalMonth to be displayed as MTD-1 instead (though there are some work-arounds for this). CREATE PROCEDURE InsertData #minLimit date, #maxLimit date, #string nvarchar(50) AS INSERT INTO tb_myTable SELECT TimePeriod, Name, SUM(Score) Score, SUM(Ticks) Ticks, SUM(Score)/SUM(Ticks) ScorePerTick FROM ( SELECT *, /* or 'Name, Score, Ticks,' */ TimePeriod = CASE WHEN x.DateKey >= #maxLimit THEN #string ELSE #string+'-1' END FROM #LimitedBetinfo x WHERE x.DateKey >= #minLimit ) A GROUP BY TimePeriod, Name GO And use like this: TRUNCATE TABLE tb_myTable DECLARE #today date = cast(getdate() as date) DECLARE #yesterday date = dateadd(day, -1, #today) EXECUTE dbo.InsertData #yesterday, #today, N'Day' DECLARE #thisweek date = DATEADD(ww, DATEDIFF(ww,0,GETDATE()), 0) DECLARE #lastweek date = DATEADD(ww, -1, #thisweek) EXECUTE dbo.InsertData #lastweek, #thisweek, N'Week' DECLARE #prev28 date = dateadd(day, -28, #today) DECLARE #prev56 date = dateadd(day, -56, #today) EXECUTE dbo.InsertData #prev56, #prev28, N'Month' DECLARE #thismonth date = DATEADD(mm, DATEDIFF(mm,0,GETDATE()), 0) DECLARE #lastmonth date = DATEADD(mm, -1, #thismonth) EXECUTE dbo.InsertData #lastmonth, #thismonth, N'MTD'
It seems that this may be the job for CUBE groupings. Sorry, I will not give you exact solution to your problem, but the MOCKUP form of select should be like: select * from ( select *,count(*) amount from ( select datepart(HOUR, login_time) as hour, datepart(MINUTE, login_time) as minute, cmd as name from sys.sysprocesses ) tmp group by cube(tmp.hour, tmp.minute, tmp.name) ) tmp2 where tmp2.name is not null and ( (tmp2.hour is not null and tmp2.minute is null) or (tmp2.hour is null and tmp2.minute is not null) ) One minus - that cube generates too much data for your problem here. So it needs to be filtered out. A big plus would be that you will only need just ONE select into temporary table.
SQL Server + Getting sales query for current and previous month
I have a query below which draws the quantity of sales committed by men for the current as well as previous month, I like to find out if it is efficient to do it this way because it looks repetitive and if I were to get the report for a year then the query will be extremely long. Please advice if I can in any ways improve on this query, I am looking at performance improvements or even code reduction. Thanks. Declare #CurrentMonth varchar(20) Declare #PreviousMonth varchar(20) Set #CurrentMonth = ( select count(*) from transact t join card c on (t.cardno = c.cardno) join member m on (c.Memberid = m.id) where mode ='1' and voidby is null and gender='M' and month(transactdate) = month(getdate()) ) Set #PreviousMonth = ( select count(*) from transact t join card c on (t.cardno = c.cardno) join member m on (c.Memberid = m.id) where mode='1' and voidby is null and gender='M' and month(transactdate) = month(dateadd(month, -1, (getdate()))) ) select #currentMonth, #PreviousMonth
Please check result with your previous version. Quite important it can use index on transactdate if such exists. declare #CurMonth int declare #PrevMonth int select #PrevMonth = sum( case when transactdate < select dateadd(mm, datediff(mm, 0, getdate()), 0) then 1 else 0 end ), #CurMonth = sum( case when transactdate > select dateadd(mm, datediff(mm, 0, getdate()), 0) then 1 else 0 end ) from transact t join card c on t.cardno = c.cardno join member m on c.Memberid = m.id where mode ='1' and voidby is null and gender='M' and transactdate >= dateadd(mm, datediff(mm, 0, getdate()) - 1, 0) and transactdate < dateadd(mm, datediff(mm, 0, getdate()) + 1, 0)