Creating a derived table in SQL Server - sql-server

I need some help summing two (or more) alias columns.
I know I need a derived table to do it, but so far I get lost with online tutorials and documentation as their examples are far too simple. they only have one table, two columns, etc.
What could be my best option here:
I need to calculate the sum of two alias columns: 'InFxO' and 'OnTxO' and my code is as follows:
ALTER PROC [dbo].[DIFOTIS]
#Mode as Varchar (5)
AS
Begin
Declare
#StartDate date,
#EndDate date
SET #StartDate=
CASE #Mode
WHEN 'MTD' THEN DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)
WHEN 'YTD' THEN DATEADD(yy,DATEDIFF(yy,0,GETDATE()),0)
WHEN 'QTD' THEN DATEADD(qq,DATEDIFF(qq,0,GETDATE()),0)
WHEN 'WTD' THEN DATEADD(wk,DATEDIFF(wk,0,GETDATE()),0)
END
Set #EndDate=
CASE #Mode
WHEN 'MTD' THEN DATEADD(mm,DATEDIFF(mm,0,GETDATE())+1,0)
WHEN 'YTD' THEN DATEADD(yy,DATEDIFF(yy,0,GETDATE())+1,0)
WHEN 'QTD' THEN DATEADD(qq,DATEDIFF(qq,0,GETDATE())+1,0)
WHEN 'WTD' THEN DATEADD(wk,DATEDIFF(wk,0,GETDATE())+1,0)
END
Select DATEPART(ISO_WEEK,d.DateOpn) AS 'Week#'
,Clients.CustCateg, Clients.ClntGroup
,d.DocumentCode as 'CORD_DocumentCode'
,CDSPDocs.DocumentCode AS 'DESP_DocumentCode'
,Count(CORDLines.Qnty) AS 'Cord_Lines'
,SUM(CORDLines.Qnty) AS 'CORD_Qty'
,Count(CDSPLines.Qnty) AS 'DESP_Lines'
,Sum(CDSPLines.Qnty) AS 'DESP_Qty'
,CDSPLines.Status, d.Status as d_status
,d.OpenDate, d.DateDue
,CDSPDocs.PostDate AS 'DESP_PostedDate'
,d.DocType, DATEDIFF(day, d.OpenDate, d.DateDue) AS 'Lead times'
--in-full
,CASE WHEN SUM(CORDLines.Qnty) = Sum(CDSPLines.Qnty) THEN '1' ELSE '0' END as InFxO
--On-Time by order according to Despatch SLAs
,CASE WHEN (Clients.ClntGroup IN ('Local Market','Local Market - Pharm','Web Sales - Local','Web Sales - Export', 'Mail Order','Mail Order - Export')) AND (Datediff(day, d.OpenDate, CDSPDocs.PostDate) - (Datediff(Week, d.OpenDate, CDSPDocs.PostDate)*2) <= 2) then '1'
WHEN (Clients.ClntGroup = 'Export Market') AND (Datediff(day, d.OpenDate, CDSPDocs.PostDate) - (Datediff(Week, d.OpenDate, CDSPDocs.PostDate)*2) <= 14) then '1'
WHEN (Clients.ClntGroup = 'Export Market') or Clients.CustCateg = 'UK Transfer' AND (d.DateDue >= CDSPDocs.PostDate) then '1'
ELSE '0'
END as OnTxO
From dbo.Documents AS d INNER JOIN
dbo.Clients ON d.ObjectID = dbo.Clients.ClntID AND Clients.ClientName <> 'Samples - Free / Give-aways' LEFT Outer JOIN
dbo.DocumentsLines AS CORDLines ON d.DocID = CORDLines.DocID AND CORDLines.TrnType = 'L'
LEFT OUTER JOIN
dbo.DocumentsLines AS CDSPLines ON CORDLines.TranID = CDSPLines.SourceID AND CDSPLines.TrnType = 'L' AND (CDSPLines.Status = 'Posted' OR CDSPLines.Status = 'Closed') LEFT OUTER JOIN
dbo.Documents AS CDSPDocs ON CDSPLines.DocID = CDSPDocs.DocID
WHERE (d.DocType IN ('CASW', 'CORD','MORD'))
AND (CORDLines.LneType NOT In ('Fght','MANF','Stor', 'PACK','EXPS'))
AND d.DateOpn >= #StartDate AND d.DateOpn < #EndDate
AND (CORDLines.LneType is not null)
AND (d.DateDue <= Convert(Date, GetDate(), 101))
Group by d.DateOpn
,d.DocumentCode
,Clients.CustCateg
,CDSPDocs.DocumentCode
,d.[Status]
,d.DocType
,d.OpenDate
,d.DateReq
,CDSPDocs.PostDate
,CDSPLines.[Status]
,Clients.ClntGroup
,d.DocumentName
,d.DateDue
,d.DateOpn
ORDER BY d.DateOpn, 'Week#'
END
GO
All help is appreciated.
Thanks
H

Below is an example that wraps the original query in a derived table so that you don't need to repeat the CASE expressions for the SUM. You could similarly wrap the query in a common table expression to achieve the same result.
I suggest you use single-quotes only to enclose string literals and enclose identifiers (column names, aliases, and object names) with either square brackets or double quotes as described in the SQL Server Books Online reference (http://msdn.microsoft.com/en-us/library/ms175874.aspx). Identifiers need be enclosed only when they don't conform to identifier naming rules or are a reserved keyword.
ALTER PROC dbo.DIFOTIS #Mode AS varchar(5)
AS
BEGIN
DECLARE #StartDate date
, #EndDate date;
SET #StartDate = CASE #Mode
WHEN 'MTD'
THEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0)
WHEN 'YTD'
THEN DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0)
WHEN 'QTD'
THEN DATEADD(qq, DATEDIFF(qq, 0, GETDATE()), 0)
WHEN 'WTD'
THEN DATEADD(wk, DATEDIFF(wk, 0, GETDATE()), 0)
END;
SET #EndDate = CASE #Mode
WHEN 'MTD'
THEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) + 1, 0)
WHEN 'YTD'
THEN DATEADD(yy, DATEDIFF(yy, 0, GETDATE()) + 1, 0)
WHEN 'QTD'
THEN DATEADD(qq, DATEDIFF(qq, 0, GETDATE()) + 1, 0)
WHEN 'WTD'
THEN DATEADD(wk, DATEDIFF(wk, 0, GETDATE()) + 1, 0)
END;
SELECT Week#
, CustCateg
, ClntGroup
, CORD_DocumentCode
, DESP_DocumentCode
, Cord_Lines
, CORD_Qty
, DESP_Lines
, DESP_Qty
, Status
, d_status
, OpenDate
, DateDue
, DESP_PostedDate
, DocType
, [Lead times]
, InFxO
, OnTxO
, InFxO + OnTxO AS InFullAndOneTime
FROM (
SELECT DATEPART(ISO_WEEK, d.DateOpn) AS Week#
, Clients.CustCateg
, Clients.ClntGroup
, d.DocumentCode AS CORD_DocumentCode
, CDSPDocs.DocumentCode AS DESP_DocumentCode
, COUNT(CORDLines.Qnty) AS Cord_Lines
, SUM(CORDLines.Qnty) AS CORD_Qty
, COUNT(CDSPLines.Qnty) AS DESP_Lines
, SUM(CDSPLines.Qnty) AS DESP_Qty
, CDSPLines.Status
, d.Status AS d_status
, d.OpenDate
, d.DateDue
, CDSPDocs.PostDate AS DESP_PostedDate
, d.DocType
, DATEDIFF(DAY, d.OpenDate, d.DateDue) AS [Lead times]
--in-full
, CASE WHEN SUM(CORDLines.Qnty) = SUM(CDSPLines.Qnty) THEN 1
ELSE 0
END AS InFxO
--On-Time by order according to Despatch SLAs
, CASE WHEN ( Clients.ClntGroup IN ( 'Local Market',
'Local Market - Pharm',
'Web Sales - Local',
'Web Sales - Export',
'Mail Order',
'Mail Order - Export' ) )
AND ( DATEDIFF(DAY, d.OpenDate, CDSPDocs.PostDate)
- ( DATEDIFF(WEEK, d.OpenDate,
CDSPDocs.PostDate) * 2 ) <= 2 )
THEN 1
WHEN ( Clients.ClntGroup = 'Export Market' )
AND ( DATEDIFF(DAY, d.OpenDate, CDSPDocs.PostDate)
- ( DATEDIFF(WEEK, d.OpenDate,
CDSPDocs.PostDate) * 2 ) <= 14 )
THEN 1
WHEN ( Clients.ClntGroup = 'Export Market' )
OR Clients.CustCateg = 'UK Transfer'
AND ( d.DateDue >= CDSPDocs.PostDate ) THEN '1'
ELSE 0
END AS OnTxO
FROM dbo.Documents AS d
INNER JOIN dbo.Clients ON d.ObjectID = dbo.Clients.ClntID
AND Clients.ClientName <> 'Samples - Free / Give-aways'
LEFT OUTER JOIN dbo.DocumentsLines AS CORDLines ON d.DocID = CORDLines.DocID
AND CORDLines.TrnType = 'L'
LEFT OUTER JOIN dbo.DocumentsLines AS CDSPLines ON CORDLines.TranID = CDSPLines.SourceID
AND CDSPLines.TrnType = 'L'
AND ( CDSPLines.Status = 'Posted'
OR CDSPLines.Status = 'Closed'
)
LEFT OUTER JOIN dbo.Documents AS CDSPDocs ON CDSPLines.DocID = CDSPDocs.DocID
WHERE ( d.DocType IN ( 'CASW', 'CORD', 'MORD' ) )
AND ( CORDLines.LneType NOT IN ( 'Fght', 'MANF', 'Stor',
'PACK', 'EXPS' ) )
AND d.DateOpn >= #StartDate
AND d.DateOpn < #EndDate
AND ( CORDLines.LneType IS NOT NULL )
AND ( d.DateDue <= CONVERT(date, GETDATE(), 101) )
GROUP BY d.DateOpn
, d.DocumentCode
, Clients.CustCateg
, CDSPDocs.DocumentCode
, d.Status
, d.DocType
, d.OpenDate
, d.DateReq
, CDSPDocs.PostDate
, CDSPLines.Status
, Clients.ClntGroup
, d.DocumentName
, d.DateDue
, d.DateOpn
) AS derived_table
ORDER BY d.DateOpn
, Week#;
END;
GO

Related

How do i escape aliases in Dynamic sql

I am getting an error near Alias Names i.e., Month_Name, Limit_Mins, Amount in this dynamic sql, how do i resolve that , i tried with adding one more quote, but when i do tyhat i am only getting #columnames into the sql. How do i resolve the error and make it part of the string
DECLARE #columns NVARCHAR(MAX) = '', #sql NVARCHAR(MAX) = '';
SELECT
#columns = coalesce(#columns + ',', '') + quotename(Limit_Mins)
from
(
select DISTINCT
(L.Limit_Mins ) as 'Limit_Mins'
from
Limit L
)
AS lIMITS
SELECT #columns
SET
#sql = 'select * from (
SELECT
month (Sa.[Date]) AS 'Month_Name',
convert(varchar(10),
case
when
S.Limit_Mins = L.Limit_Mins
and S.Childcare_Flag = 1
then
S.Limit_Mins
else
0
end
) as 'Limit_Mins', sum(
case
when
S.Childcare_Flag = 1
and S.Limit_Mins = L.Limit_Mins
then
case
when
D.PID = Sa.PID
and D.Discount_Date = Sa.[Date]
then
D.Discount_Price
else
P.Retail_price
end
*Sa.Quantity
else
0
end
) as 'Amount'
FROM
Limit L
join
Store S
on L.Limit_Mins = S.Limit_Mins
join
Sale Sa
on S.Store_Number = Sa.Store_number
join
[Date] Dt
on Dt.[Date] = Sa.[Date]
join
Product P
on Sa.PID = P.PID
left outer join
Discount D
on Sa.PID = D.PID
and Sa.[Date] = D.Discount_Date
WHERE
Sa.[Date] >= DATEADD(year, - 1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1))
AND Sa.[Date] < DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1) -- DATEDIFF(MM,Sale.[Date] ,GETDATE())<=12
--and Sale.[Date] < DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
GROUP BY
month(Sa.[Date]) ,
case
when
S.Limit_Mins = L.Limit_Mins
and S.Childcare_Flag = 1
then
S.Limit_Mins
else
0
end
union all
SELECT
month (Sa.[Date]) AS 'Month_Name',
''No Childcare'' as 'Limit_Mins',
isnull(sum(
case
when
S.Childcare_Flag = 0
then
case
when
D.PID = Sa.PID
and D.Discount_Date = Sa.[Date]
then
D.Discount_Price
else
P.Retail_price
end
*Sa.Quantity
else
0
end
), 0) as 'Sales_Amount'
FROM
Store S
join
Sale Sa
on S.Store_Number = Sa.Store_number
join
[Date] Dt
on Dt.[Date] = Sa.[Date]
join
Product P
on Sa.PID = P.PID
left outer join
Discount D
on Sa.PID = D.PID
and Sa.[Date] = D.Discount_Date
WHERE
Sa.[Date] >= DATEADD(year, - 1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1))
AND Sa.[Date] < DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1) -- DATEDIFF(MM,Sale.[Date] ,GETDATE())<=12
--and Sale.[Date] < DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
GROUP BY
month(Sa.[Date])
)
A PIVOT(sum(Amount) for Limit_Mins in
(
'+#columns+
',[No Childcare]
)
)as PIVOTTable
order by
[Month]'
print #sql
Use brackets instead of quotes: month (Sa.[Date]) AS [Month_Name],
I tried the quotes earlier, but it was throwing an error invalid column which i later realized was happening because i renamed the alias and forgot to rename the same column order by , Thanks for the input

Is there a faster way of adding up time ranges, taking overlaps into account?

I have a subquery that is taking multiple minutes to execute. If I pull out just the initial rows that are being added up, it only takes half a second with 2,400ish rows so I don't understand why the main query doing the sum is taking so long.
What I'm trying to do is for all the transactions in a date range, for all the workers assigned to those transactions, add up the scheduled hours for each worker.
The query is returning the correct data, it's just taking FOREVER to do it.
QUERY
SELECT scheduled_hours = COALESCE(sum(hours), 0), worker_sysid
FROM (
SELECT DISTINCT
B.DateR1,
B.DateR2,
hours = ABS((B.DAteR1 - B.DateR2) / 3600),
B.worker_sysid
FROM Trans A
OUTER APPLY (
SELECT
DateR1 = MIN(TRANS_START),
DateR2 = MAX(TRANS_END),
worker_sysid
FROM Trans
JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
LEFT JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
TRANS_START <= A.TRANS_END AND TRANS_END >= A.TRANS_START
AND TRANS_START IS NOT NULL AND TRANS_END IS NOT NULL
AND TRANS_START != '' AND TRANS_END != ''
AND Trans.CHARGEBY IN ('Hours', 'Hour')
AND (
COALESCE(Service.overnight, 0) != 1
OR
COALESCE(Service.active_overnight, 0) = 1
)
AND TRANSDATE BETWEEN 80387 AND 80400 ### These are Clarion dates
AND trans_workers.deleted_at IS NULL
GROUP BY worker_sysid
) B
) A
WHERE worker_sysid IS NOT NULL
GROUP BY worker_sysid
TABLES
Trans: SYSID (int, pk), TRANSDATE (int, clarion-formatted date), TRANS_START / TRANS_END (UNIX timestamp), SERVICESYSID (int, fk), CHARGEBY (varchar)
trans_workers: trans_sysid, worker_sysid, deleted_at
Service: SYSID (int, pk)
UPDATE
Moving the trans_workers join out of the OUTER APPLY has reduced the execution time from 1 minute down to 16 seconds, so that's an improvement.
SELECT scheduled_hours = COALESCE(sum(hours), 0), worker_sysid
FROM (
SELECT DISTINCT
B.DateR1,
B.DateR2,
hours = ABS((B.DateR1 - B.DateR2) / 3600),
worker_sysid
FROM Trans A
JOIN trans_workers ON A.SYSID = trans_workers.trans_sysid
OUTER APPLY (
SELECT
DateR1 = MIN(TRANS_START),
DateR2 = MAX(TRANS_END),
Trans.SYSID
FROM Trans
LEFT JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
TRANS_START <= A.TRANS_END AND TRANS_END >= A.TRANS_START
AND TRANS_START IS NOT NULL AND TRANS_END IS NOT NULL
AND TRANS_START != '' AND TRANS_END != ''
AND Trans.CHARGEBY IN ('Hours', 'Hour')
AND COALESCE(Service.overnight, 0) != 1
AND TRANSDATE BETWEEN 80387 AND 80400
GROUP BY Trans.SYSID
) B
) A
WHERE worker_sysid IS NOT NULL
GROUP BY worker_sysid
ORDER BY worker_sysid
Thanks to https://www.sqlservercentral.com/forums/topic/consolidate-overlapping-date-periods I have a query that executes in under a second and returns what appear to be the correct hours. Only problem being I don't understand what's happening.
DECLARE #start INTEGER, #end INTEGER;
SET #start = 80401; --06/02/2021
SET #end = 80414; --19/02/2021
WITH cteTemp
AS (
SELECT
worker_sysid,
BeginDate =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY theDate) - openCnt = 0 THEN theDate
END,
EndDate =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY theDate) - closeCnt = 0 THEN theDate
END
FROM (
SELECT
worker_sysid,
theDate = DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')),
closeCnt = NULL,
openCnt = (ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01'))) * 2) - 1
FROM
Trans
INNER JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
worker_sysid IS NOT NULL
AND Trans.deleted_at IS NULL
AND trans_workers.deleted_at IS NULL
AND Trans.CHARGEBY IN ('Hour', 'Hours')
AND (transCancelled IS NULL OR transCancelled != 1)
AND (
COALESCE(Service.overnight, 0) = 0
)
AND TRANSDATE BETWEEN #start AND #end
UNION ALL
SELECT
worker_sysid,
theDate = DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')),
closeCnt = ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01'))) * 2,
openCnt = NULL
FROM
Trans
JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
worker_sysid IS NOT NULL
AND Trans.deleted_at IS NULL
AND trans_workers.deleted_at IS NULL
AND Trans.CHARGEBY IN ('Hour', 'Hours')
AND (transCancelled IS NULL OR transCancelled != 1)
AND (
COALESCE(Service.overnight, 0) = 0
)
AND TRANSDATE BETWEEN #start AND #end
)
AS baseSelected
)
SELECT scheduled_hours = SUM(hours), worker_sysid
FROM (
SELECT
dt.worker_sysid,
hours = CAST(ABS(DATEDIFF(second, MIN(dt.BeginDate), MAX(dt.EndDate))) / 3600.0 AS DECIMAL(10,2))
FROM (
SELECT
worker_sysid,
BeginDate,
EndDate,
grpID =
IIF(BeginDate IS NOT NULL, ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, BeginDate), ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, EndDate))
FROM
cteTemp
WHERE
BeginDate IS NOT NULL OR EndDate IS NOT NULL
)
AS dt
GROUP BY dt.worker_sysid,grpID
) AS final_table
GROUP BY worker_sysid ORDER BY worker_sysid
Bonus points to myself for conversions because the DATE of each transaction is in Clarion and the TIME of each transaction is a Unix timestamp

Query Very Slow to Run, might need optimisation?

I have below code and it took around 10 minutes to run. I could not figure out why though... Any suggestion? Looking up for a month worth of sales which around 51000 lines of transaction.
WITH CUSTOMER_SALESTYPE AS (
SELECT CUSTOMER_ID
,CUSTOMER_NAME
,CASE Sales_type WHEN 'A' THEN 'TRADE'
WHEN 'B' THEN 'WHOLESALE'
WHEN 'C' THEN 'TRADE'
WHEN 'M' THEN 'WHOLESALE'
WHEN 'N' THEN 'WHOLESALE'
WHEN 'P' THEN 'TRADE'
WHEN 'S' THEN 'TRADE'
WHEN 'V' THEN 'WHOLESALE' END AS 'SALESTYPE'
,CASE B.STATE WHEN 'TAS' THEN 'VIC' ELSE B.STATE END AS 'STATE'
FROM BSIT_ERA_RAW_DATA.DBO.CUSTOMER A
LEFT JOIN BSIT_ERA_RAW_DATA.DBO.TERRITORY_REP B ON A.TERR_CODE = B.TERR_CODE
WHERE BSIT_STORE = 'STORE01'
AND B.STATE IN ('VIC','QLD','NSW','WA','SA','TAS')
)
SELECT B.STATE
,B.SALESTYPE
,C.BRAND_NAME
,SUM(TOTAL_SALE_AMT) AS 'Net Sales'
,SUM(TOTAL_COST) AS 'Net Cost'
,SUM(TOTAL_SALE_AMT) - SUM(TOTAL_COST) AS 'Net Margin'
,STR(100*((SUM(TOTAL_SALE_AMT) - SUM(TOTAL_COST)) / SUM(TOTAL_SALE_AMT)),5,2) + '%' AS 'Net Margin%'
FROM [BSIT_ERA_RAW_DATA].[dbo].[PROD_HIST] A
INNER JOIN CUSTOMER_SALESTYPE B ON A.CUST_NO = B.CUSTOMER_ID
INNER JOIN BSIT_ERA_RAW_DATA.dbo.DIM_PART C ON RIGHT(A.PART_NO,LEN(A.PART_NO)-2) = C.PART_NO
WHERE TRANS_DATE >= DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 1, 0)
AND TRANS_DATE < DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) , 0)
AND TRANS_TYPE IN ('SLS', 'CM')
AND INVOICE_NO IS NOT NULL
AND A.TRANS_CODE != 'L'
AND A.TRANS_CODE != 'PL'
OR
TRANS_DATE >= DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 1, 0)
AND TRANS_DATE < DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) , 0)
AND TRANS_TYPE IN ('SLS', 'CM')
AND INVOICE_NO IS NOT NULL
AND A.TRANS_CODE IS NULL
GROUP BY B.STATE
,B.SALESTYPE
,C.BRAND_NAME

Performance tuning for SQL Query

Hi here I am attaching my sample table structure which I want to use in my project
CREATE TABLE TESTSALESVOLUMETABLE
(
ID INT IDENTITY(1,1),
AMOUNT DECIMAL(18,2),
CREDITEDDATE DATETIME
)
and the queries I used like this
DECLARE #CURRENTDATE AS DATETIME = GETDATE()
DECLARE #PSV AS INT = 0
DECLARE #TOTAL AS INT = 0
IF (DATEPART(DAY, #CURRENTDATE) <= 15)
BEGIN
SELECT #PSV = (
SELECT Sum(AMOUNT)
FROM TESTSALESVOLUMETABLE
WHERE DATEPART(DAY, CREDITEDDATE) <= 15
AND MONTH(CREDITEDDATE) = MONTH(#CURRENTDATE)
AND YEAR(CREDITEDDATE) = YEAR(#CURRENTDATE)
)
END
ELSE
BEGIN
SELECT #PSV = (
SELECT Sum(AMOUNT)
FROM TESTSALESVOLUMETABLE
WHERE DATEPART(DAY, CREDITEDDATE) > 15
AND MONTH(CREDITEDDATE) = MONTH(#CURRENTDATE)
AND YEAR(CREDITEDDATE) = YEAR(#CURRENTDATE)
)
END
SELECT #total = (
SELECT Sum(Amount)
FROM TESTSALESVOLUMETABLE
)
SELECT #PSV 'PSV',
#total 'TOTAL'
Is there any way to increase the performance of this query
First, you don't need a subquery for setting the variable. Second, the use of functions on columns usually prevents the use of indexes. So, I would recommend something like this:
SELECT #PSV = Sum(AMOUNT)
FROM TESTSALESVOLUMETABLE
WHERE CREDITEDDATE >= DATEADD(DAY, 1 - DAY(GETDATE()), CAST(GETDATE() as DATE)) AND
CREDITEDDATE < DATEADD(DAY, 16 - DAY(GETDATE()), CAST(GETDATE() as DATE));
Then, you want an index on TESTSALESVOLUMETABLE(CREDTEDDATE, AMOUNT).
Following the guidelines from: Bad habits to kick : mis-handling date / range queries - Aaron Bertrand - 2009-10-16
First, we want to get rid of:
where datepart(day, crediteddate) <= 15
and month(crediteddate)=month(#currentdate)
and year(crediteddate)=year(#currentdate)
because:
[...] you've effectively eliminated the possibility of SQL Server taking advantage of an index. Since you've forced it to build a nonsargable condition, this means it will have to convert every single value in the table to compare it to the [value] you've presented on the right hand side [...]
Second, we want to make sure to avoid using between with datetimes because it can return unwanted rows or miss wanted rows, even when using something like between ... and dateadd(second, -1, #thrudate) or even between ... and 'yyyy-mm-ddT23:59:59.997'. (See Aaron Bertrand's article for more examples on this).
So the best way to do this would be to say:
If today is the 15th or earlier, get rows >= the 1st of this month and < the 16th of this month
If today is the 16th or later, get rows >= the 16th of this month and < the 1st of next month
Also, as Gordon Linoff mentioned, you will benefit from an index on testsalesvolumetable(crediteddate, amount). But Gordon's formulas always return the 1st and 16th of the current month.
Instead of breaking the procedure into two queries depending on the current day, we can calculate those from and thru dates and just use one query.
Here is example code both with and without using variables for the from and thru dates, along with a quick calendar test to check the resulting ranges.
rextester link for test setup: http://rextester.com/YVLI65217
create table testsalesvolumetable (crediteddate datetime not null, amount int not null)
insert into testsalesvolumetable values
('20161201',1) ,('20161202',1) ,('20161203',1) ,('20161204',1) ,('20161205',1)
,('20161206',1) ,('20161207',1) ,('20161208',1) ,('20161209',1) ,('20161210',1)
,('20161211',1) ,('20161212',1) ,('20161213',1) ,('20161214',1) ,('20161215',1)
,('20161216',1) ,('20161217',1) ,('20161218',1) ,('20161219',1) ,('20161220',1)
,('20161221',1) ,('20161222',1) ,('20161223',1) ,('20161224',1) ,('20161225',1)
,('20161226',1) ,('20161227',1) ,('20161228',1) ,('20161229',1) ,('20161230',1)
,('20161231',1) ,('20170101',1)
/* ----- without variables */
declare #psv int;
select #psv = Sum(amount)
from testsalesvolumetable
where crediteddate >= dateadd(day, (1- (day(convert(date,getdate()))/16)) - (day(convert(date,getdate()))%16), convert(date,getdate()))
and crediteddate < case
when day(convert(date,getdate()))>15
then dateadd(month, datediff(month, -1, convert(date,getdate())), 0)
else dateadd(day,15,dateadd(month, datediff(month, 0, convert(date,getdate())), 0))
end;
select psv=#psv;
--*/
/* ----- with variables */
--declare #psv int;
declare #currentdate date;
/* change to date datatype to get rid of time portion*/
set #currentdate = getdate();
--set #currentdate = '20161212'
declare #fromdatetime datetime;
declare #thrudatetime datetime;
set #fromdatetime = dateadd(day, (1- (day(#currentdate)/16)) - (day(#currentdate)%16), #currentdate);
set #thrudatetime = case
when day(#currentdate)>15
then dateadd(month, datediff(month, -1, #currentdate), 0)
else dateadd(day,15,dateadd(month, datediff(month, 0, #currentdate), 0))
end;
select #psv = sum(amount)
from testsalesvolumetable
where crediteddate >= #fromdatetime
and crediteddate < #thrudatetime;
--/*
select
psv=#psv
, CurrentDate =convert(varchar(10),#currentdate ,121)
, FromDateTime=convert(varchar(10),#fromdatetime,121)
, ThruDateTime=convert(varchar(10),#thrudatetime,121);
--*/
Rextester link for the calendar test: http://rextester.com/ESZRH30262
--/* ----- Calendar Test */
;with n as (
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
)
, cal as (
select DateValue=convert(datetime,dateadd(day, row_number() over (order by (select 1)) -1, '20160101'))
from n as a
cross join n as b
cross join n as c
cross join n as d
)
select
--DateValue=convert(varchar(10),DateValue,121)
minDate =convert(varchar(10),min(DateValue),121)
, maxDate =convert(varchar(10),max(DateValue),121)
, FromDatetime=convert(varchar(10),dateadd(day, (1- (day(DateValue)/16)) - (day(DateValue)%16), DateValue),121)
, ThruDatetime=convert(varchar(10),case
when day(DateValue)>15
then dateadd(m, datediff(m, -1, DateValue), 0)
else convert(varchar(10),dateadd(day, 16 - day(DateValue), DateValue),121)
end,121)
, GordonFrom = convert(varchar(10),dateadd(day, 1 - day(DateValue), cast(DateValue as date)),121)
, GordonThru = convert(varchar(10),dateadd(day, 16 - day(DateValue), cast(DateValue as date)),121)
from cal
where datevalue >= '20160101'
and datevalue < '20170101'
--/*
group by
convert(varchar(10),dateadd(day, (1- (day(DateValue)/16)) - (day(DateValue)%16), DateValue),121)
, convert(varchar(10),case
when day(DateValue)>15
then dateadd(m, datediff(m, -1, DateValue), 0)
else convert(varchar(10),dateadd(day, 16 - day(DateValue), DateValue),121)
end,121)
, convert(varchar(10),dateadd(day, 1 - day(DateValue), cast(DateValue as date)),121)
, convert(varchar(10),dateadd(day, 16 - day(DateValue), cast(DateValue as date)),121)
order by FromDateTime
I thing this will work fine
DECLARE #PSV AS INT = 0
DECLARE #TOTAL AS INT = 0
IF (DATEPART(DAY,GETDATE()) <= 15)
BEGIN
SELECT #PSV = Sum(AMOUNT)
FROM TESTSALESVOLUMETABLE
WHERE CREDITEDDATE >= DATEADD(DAY, 1 - DAY(GETDATE()), CAST(GETDATE() as DATE)) AND
CREDITEDDATE < DATEADD(DAY, 16 - DAY(GETDATE()), CAST(GETDATE() as DATE));
END
ELSE
BEGIN
SELECT #PSV = Sum(AMOUNT)
FROM TESTSALESVOLUMETABLE
WHERE CREDITEDDATE >= DATEADD(DAY, 16 - DAY(GETDATE()), CAST(GETDATE() as DATE)) AND
CREDITEDDATE < DATEADD(DAY, 31 - DAY(GETDATE()), CAST(GETDATE() as DATE));
END
SELECT #total = (
SELECT Sum(Amount)
FROM TESTSALESVOLUMETABLE
)
SELECT #PSV 'PSV',
#total 'TOTAL'

Select Query- exclude holidays count from a totaldays count obtain from date range

I have a project table having field as name,startdate,enddate,
using datediff I calculated totaldays and then filter sundays as colname TotalDay_sundayremoved.
I have another table as holidays , now have to filter these holidays over TotalDay_sundayremoved column
DDL:
CREATE TABLE holiday_hd (
date_hd DATETIME
, enent_hd VARCHAR(200)
);
INSERT INTO holiday_hd
VALUES
('2013-03-15 00:00:00.000', 'Aniversry Leave'),
('2013-08-15 00:00:00.000', 'Independence Day'),
('2014-08-15 00:00:00.000', 'Independence Day');
CREATE TABLE Project_pj (
Name VARCHAR(200)
, start_date_pj DATETIME
, ed_date_pj DATETIME
, expectedend_date_pj DATETIME
);
INSERT INTO Project_pj
VALUES
('aaa', '2012-12-01 00:00:00.000', '2023-07-22 00:00:00.000', NULL),
('bbb', '2012-12-01 00:00:00.000', NULL, '2023-07-22 00:00:00.000'),
('ccc', '2013-12-01 00:00:00.000', NULL, '2014-07-22 00:00:00.000'),
('ddd', '2013-07-01 00:00:00.000', NULL, '2016-07-22 00:00:00.000');
Query:
SELECT
Name
, start_date_pj
, ISNULL(ed_date_pj, expectedend_date_pj) AS enddate
, DATEDIFF(DAY, start_date_pj, ISNULL(ed_date_pj, expectedend_date_pj)) AS No_Days
, (DATEDIFF(dd, start_date_pj, ISNULL(ed_date_pj, expectedend_date_pj)) + 1)
- (DATEDIFF(wk, start_date_pj, ISNULL(ed_date_pj, expectedend_date_pj)) * 1)
- (CASE WHEN DATENAME(dw, start_date_pj) = 'Sunday' THEN 1 ELSE 0 END) AS TotalDay_sundayremoved
FROM Project_pj
SQL FIDDLE DEMO
Try this one -
SELECT
name
, start_date_pj
, enddate
, No_Days = DATEDIFF(DAY, start_date_pj, enddate)
, TotalDay_sundayremoved = (DATEDIFF(dd, start_date_pj, enddate) + 1)
- (DATEDIFF(wk, start_date_pj, enddate) * 1)
- (CASE WHEN DATENAME(dw, start_date_pj) = 'Sunday' THEN 1 ELSE 0 END)
-- - holidays
, holidays
FROM (
SELECT
name
, start_date_pj
, enddate = ISNULL(ed_date_pj, expectedend_date_pj)
FROM dbo.Project_pj
) p
OUTER APPLY (
SELECT holidays = COUNT(1)
FROM dbo.holiday_hd h
WHERE h.date_hd BETWEEN p.start_date_pj AND p.enddate
) t
Also you can write sub-query -
SELECT
name
, ...
, holidays = (
SELECT holidays = COUNT(1)
FROM dbo.holiday_hd h
WHERE h.date_hd BETWEEN p.start_date_pj AND p.enddate
)
FROM (
SELECT
name
, start_date_pj
, enddate = ISNULL(ed_date_pj, expectedend_date_pj)
FROM dbo.Project_pj
) p
Update:
SELECT
name
, start_date_pj
, enddate
, No_Days = DATEDIFF(DAY, start_date_pj, enddate)
, TotalDay_sundayremoved = (DATEDIFF(dd, start_date_pj, enddate) + 1)
- (DATEDIFF(wk, start_date_pj, enddate) * 1)
- isSunday
-- - holidays
, TotalDay_holidayRmoved = (DATEDIFF(dd, start_date_pj, enddate) + 1)
- (DATEDIFF(wk, start_date_pj, enddate) * 1)
- isSunday
- t.holidays
, t.holidays
, currentDays_removeSunday = (DATEDIFF(dd, start_date_pj, GETDATE()) + 1)
- (DATEDIFF(wk, start_date_pj, GETDATE()) * 1)
- isSunday
, holidays_currentDateCount= holidays_now
, Currentday_Removeholiday_RemoveSundays_tilldate= DATEDIFF(dd, start_date_pj, GETDATE()+1)
-(DATEDIFF(wk, start_date_pj, GETDATE()) * 1)
- isSunday
- holidays_now
FROM (
SELECT
name
, start_date_pj
, enddate = ISNULL(ed_date_pj, expectedend_date_pj)
, isSunday = CASE WHEN DATENAME(dw, start_date_pj) = 'Sunday' THEN 1 ELSE 0 END
FROM dbo.Project_pj
) p
OUTER APPLY (
SELECT
holidays = SUM(holidays)
, holidays_now = SUM(holidays_now)
FROM (
SELECT
holidays = CASE WHEN h.date_hd BETWEEN p.start_date_pj AND p.enddate THEN 1 ELSE 0 END
, holidays_now = CASE WHEN h.date_hd BETWEEN p.start_date_pj AND GETDATE() THEN 1 ELSE 0 END
FROM dbo.holiday_hd h
) t
) t

Resources