Related
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'
I need to get last day of all previous months including current month, upto a specified month. For example, I need last days of september, aug, july, june, may, april, march, feb, jan, dec 2015 like so:
temptable_mytable:
last_day_of_month
-----------------
2016-09-30
2016-08-31
2016-07-31
2016-06-30
2016-05-31
2016-04-30
2016-03-31
2016-02-30
2016-01-31
2015-12-31
I need to specify the month and year to go back to - in above case it's December 2015, but it could also be September 2015 and such. Is there a way that I can do a loop and do this instead of having to calculate separately for each month end?
Use a recursive CTE with the EOMONTH function.
DECLARE #startdate DATE = '2016-01-01'
;WITH CTE
AS
(
SELECT EOMONTH(GETDATE()) as 'Dates'
UNION ALL
SELECT EOMONTH(DATEADD(MONTH, -1, [Dates]))
FROM CTE WHERE Dates > DATEADD(MONTH, 1, #startdate)
)
SELECT * FROM CTE
with temp as (select -1 i union all
select i+1 i from temp where i < 8)
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+i*-1,0)) from temp
declare #LASTMONTH date = '2018-10-01';
WITH MTHS AS (
SELECT dateadd(month,month(getdate()),dateadd(year,year(getdate()) - 1900, 0)) aday
UNION ALL
SELECT DATEADD(month,1,aday) from MTHS WHERE aday <= #LASTMONTH
),
LASTDAYS AS (SELECT DATEADD(day,-1,aday) finaldayofmonth from MTHS)
select * from LASTDAYS
Here is a version that goes forward or backwards as appropriate
declare #LASTMONTH date = '2013-10-01';
WITH DIF AS (SELECT CASE WHEN
YEAR(#LASTMONTH) * 12 + MONTH(#LASTMONTH)
>= YEAR(GETDATE()) * 12 + MONTH(getdate()) THEN 1 ELSE -1 END x),
MTHS AS (
SELECT dateadd(month,month(getdate()),dateadd(year,year(getdate()) - 1900, 0)) aday
UNION ALL
SELECT DATEADD(month,(SELECT X from dif),aday) from MTHS
WHERE month(aday) != month(dateadd(month,1,#LASTMONTH)) or YEAR(aday) != YEAR(dateadd(month,1,#LASTMONTH))
),
LASTDAYS AS (SELECT DATEADD(day,-1,aday) finaldayofmonth from MTHS)
select * from LASTDAYS order by finaldayofmonth
Here's one approach, using a CTE to generate a list of incrementing numbers to allow us to then have something to select from and use in a DATEADD to go back for the appropriate number of months.
Typically, if you're doing this quite frequently, instead of generating numbers on the fly like this with the CROSS JOIN, I'd recommend just creating a "Numbers" table that just holds numbers from 1 to "some number high enough to meet your needs"
DECLARE #Date DATE = '20151201'
DECLARE #MonthsBackToGo INTEGER
SELECT #MonthsBackToGo = DATEDIFF(mm, #Date, GETDATE()) + 1;
WITH _Numbers AS
(
SELECT TOP (#MonthsBackToGo) ROW_NUMBER() OVER (ORDER BY o.object_id) AS Number
FROM sys.objects o
CROSS JOIN sys.objects o2
)
SELECT EOMONTH(DATEADD(mm, -(Number- 1), GETDATE())) AS last_day_of_month
FROM _Numbers
This should scale out no matter how far you go back or forward for your originating table or object.
SET NOCOUNT ON;
DECLARE #Dates TABLE ( dt DATE)
DECLARE #Start DATE = DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)
DECLARE #End DATE = DATEADD(YEAR, 1, #Start)
WHILE #Start <= #End
BEGIN
INSERT INTO #Dates (dt) VALUES (#Start)
SELECT #Start = DATEADD(DAY, 1, #Start)
END
; With x as
(
Select
dt
, ROW_NUMBER() OVER(PARTITION BY DATEPART(YEAR, Dt), DATEPART(MONTH, Dt) ORDER BY Dt Desc) AS rwn
From #Dates
)
Select *
From x
WHERE rwn = 1
ORDER BY Dt
This was cribbed together quick based on a couple different SO answers for the parts:
DECLARE #startdate datetime, #enddate datetime
set #startdate = '2015-12-01'
set #enddate = getdate()
;WITH T(date)
AS
(
SELECT #startdate
UNION ALL
SELECT DateAdd(day,1,T.date) FROM T WHERE T.date < #enddate
)
SELECT DISTINCT
DATEADD(
day,
-1,
CAST(CAST(YEAR(date) AS varchar) + '-' + CAST(MONTH(date)AS varchar) + '-01' AS DATETIME))
FROM T OPTION (MAXRECURSION 32767);
I can really use some help. I'm trying to subtract 1 from a VARCHAR report parameter that is within a CTE. I'm working in SQL 2008 R2
This works fine:
AND MONTH(CAST(c.DATETIME AS DATE)) = CONVERT(INT,#Maand)
But this just doesn't seem to work:
AND MONTH(CAST(c.DATETIME AS DATE)) = CONVERT(INT,#Maand) -1
Thanks
Here is some of my code:
ALTER PROCEDURE [rpt].[usp_F_Telefonie_Bereikbaarheid](#Jaar AS VARCHAR(4), #Maand AS VARCHAR(2))
AS
BEGIN
WITH CTE AS(
SELECT
C.DATETIME AS Datum
,d.NAME
,COUNT(Callid) AS Aantal
,'Vorige' AS Gesprekken
,DURATIONSEC
FROM DM.dm.F_CDRS AS c
JOIN DM.dm.D_DEPARTMENTS AS d
ON d.DEPARTMENTID = c.DEPARTMENTID
WHERE Direction = 1
AND YEAR(CAST(c.DATETIME AS DATE)) = #Jaar
AND MONTH(CAST(c.DATETIME AS DATE))= CONVERT(INT,#Maand)
AND WAITTIMESEC <=10
GROUP BY LEFT(D.NAME, 2), D.NAME, C.DATETIME, DURATIONSEC
)
SELECT
,CASE CTE.Gesprekken
WHEN 'Vorige'
THEN SUM(CTE.Aantal)
END AS Vorige
FROM CTE
WHERE MONTH(CAST(CTE.Datum AS DATE)) = #Maand
AND YEAR(CAST(CTE.Datum AS DATE)) = #Jaar
GROUP BY CTE.NAME, CTE.Gesprekken, CTE.DURATIONSEC, CTE.Aantal
You need to convert varchar --> datetime and then use datediff function.
Instead this:
YEAR(CAST(c.DATETIME AS DATE)) = #Jaar
AND MONTH(CAST(c.DATETIME AS DATE))= CONVERT(INT,#Maand)
use:
datediff(month, cast(#Jaar + right('0' + #Maand, 2) + '01' as datetime), c.[datetime]) = 1
I am looking for the count of records as below.
PLANNED_SHIP_From_Date PLANNED_SHIP_To Date Total_Lines_Count ....
1) 09-04-2016 07:00:01 09-04-2016 18:59:59 165 .....
2) 09-04-2016 19:00:00 10-04-2016 07:00:00 121 .....
3) 10-04-2016 07:00:01 10-04-2016 18:59:59 165 .....
4) 10-04-2016 19:00:00 11-04-2016 07:00:00 123 .....
5) 11-04-2016 07:00:01 11-04-2016 18:59:59 234 .....
.
Currently my query is counting the records as per date.
SELECT
cast(shdr.PLANNED_SHIP_DATE as date),
SUM(sdtl_1_1.TOTAL_LINES_COUNT) AS TOTAL_LINES_COUNT
FROM
dbo.SHIPMENT_HEADER AS shdr WITH (NOLOCK)
INNER JOIN
(
SELECT
SHIPMENT_ID,
COUNT(*) AS TOTAL_LINES_COUNT
FROM
dbo.SHIPMENT_DETAIL AS SHIPMENT_DETAIL_1 WITH (NOLOCK)
WHERE
(
STATUS1 >= 401
)
AND (
DATEDIFF(day, PLANNED_SHIP_DATE, CONVERT(date, SYSDATETIME())) < 4
)
GROUP BY
SHIPMENT_ID
) AS sdtl_1_1
ON sdtl_1_1.SHIPMENT_ID = shdr.SHIPMENT_ID
WHERE
(
shdr.TRAILING_STS >= 401
)
AND (
DATEDIFF(day, shdr.PLANNED_SHIP_DATE, CONVERT(date, SYSDATETIME())) < 4
)
GROUP BY
cast(shdr.PLANNED_SHIP_DATE as date)
Try this -
DECLARE #ReportDays int = 30,
#StartHr int = 7,
#Today DATETIME2 = CAST(SYSDATETIME() AS DATE);
--http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx
WITH
a AS (SELECT 1 AS i UNION ALL SELECT 1),
b AS (SELECT 1 AS i FROM a AS x, a AS y),
c AS (SELECT 1 AS i FROM b AS x, b AS y),
d AS (SELECT 1 AS i FROM c AS x, c AS y),
e AS (SELECT 1 AS i FROM d AS x, d AS y),
numbers as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS number FROM e),
StartDates AS (
SELECT
DATEADD(
HH,
#StartHr + (n2.number * 12),
DATEADD(D, 0-n1.number, #Today)
) AS StartDT
FROM
(SELECT * FROM numbers WHERE Number BETWEEN 0 AND #ReportDays) n1
CROSS JOIN (SELECT * FROM numbers WHERE Number IN (0,1)) n2
),
DateRanges AS
(SELECT StartDT, DATEADD(hh, 12, StartDT) AS EndDT FROM StartDates),
Shipments AS
(SELECT
StartDT AS PLANNED_SHIP_From_Date,
EndDT AS PLANNED_SHIP_To_Date,
1 AS Shipment
FROM
DateRanges dr
LEFT JOIN dbo.SHIPMENT_DETAIL sd
ON sd.Status1 >=401
AND sd.PLANNED_SHIP_DATE BETWEEN dr.StartDT AND dr.EndDT)
SELECT
PLANNED_SHIP_From_Date,
PLANNED_SHIP_To_Date,
SUM(Shipment) AS TOTAL_LINES_COUNT
FROM
Shipments
ORDER BY
PLANNED_SHIP_From_Date;
What we're doing is -
Building a numbers table
Using that to pull a list of days, with two records per day
Working out the start & finish times for each time window
Joining the time windows to the records and summing
Hope that helps :-)
Add another column to your select....
CASE
WHENE DATEPART(HOUR, Planned_SHIP_DATE) < 12 THEN 'AM' ELSE 'PM'
END AS ShipPeriod
You could then add that column into a GROUPING to seperate the 'AM's from 'PM's
Of course I have assuumed you wanted AM/PM. But you can modify the CASE statement to break the hours up as you see fit.
Hope this helps
Thank you all for helping me out.
I have created a SQL query which worked for me. This query gives the count of records from morning 7 AM to 7 PM as MORNING_SHIFT count and 7PM to next day 7AM morning as EVENING_SHIFT for dates greater than 14 days in the past.
SELECT
CASE
WHEN convert(VARCHAR(50), sh.PLANNED_SHIP_DATE, 120) BETWEEN
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 07:00:00') AND
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 18:59:59')
THEN (CONCAT(cast(sh.PLANNED_SHIP_DATE as date),' ','morning_shift'))
WHEN convert(VARCHAR(50), sh.PLANNED_SHIP_DATE, 120) BETWEEN
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 00:00:00') AND
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 06:59:59')
then (CONCAT(cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date),' ','EVENING_shift'))
when
convert(VARCHAR(50), DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) , 120) BETWEEN (convert(VARCHAR(10), cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date), 120) + ' 19:00:00') AND
(convert(VARCHAR(10), cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date), 120) + ' 23:59:59')
THEN (CONCAT(cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date),' ','EVENING_shift'))
END AS 'actual_date_time', sh.PLANNED_SHIP_DATE
FROM dbo.SHIPMENT_HEADER AS sh WITH (nolock)
WHERE (shdr.TRAILING_STS >= 401) AND (DATEDIFF(day, shdr.ACTUAL_SHIP_DATE_TIME, CONVERT(date, SYSDATETIME())) < 14)
group by sh.ACTUAL_SHIP_DATE_TIME;
I am using SQL Server and have a table (Table_Date) with a 'date' field. I want to insert all 2015 dates in this field.
It should have 365 distinct rows, 1 row for each day of 2015.
One method is with a recursive CTE:
with dates as (
select cast('2015-01-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2015-12-31'
)
select *
from dates
option (maxrecursion 0);
An alternative is to use a table that has at least 365 rows. master..spt_values is often used for this purpose:
select dateadd(day, seqnum - 1, '2015-01-01')
from (select row_number() over (order by ()) as seqnum
from master..spt_values
) t
where seqnum <= 365;
Here's one way:
CREATE TABLE #nums(num INT);
INSERT INTO #nums VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
WITH cteDays AS
(
SELECT 100*d100.num + 10*d10.num + d1.num AS YearDay
FROM #nums AS d1
CROSS JOIN #nums AS d10
CROSS JOIN #nums AS d100
WHERE d100.num <=3
)
SELECT CAST('2015-01-01' AS DATETIME) + YearDay AS YearDate
FROM cteDays
WHERE YEAR(CAST( CAST('2015-01-01' AS DATETIME) + YearDay AS DATETIME)) = 2015
Something like this could work as well:
declare #count int = 0
while (#count < 365)
begin
--make this the insert
select DATEADD(DD, #count, getdate())
set #count = #count + 1
end
Not sure what context this will applied to though... This is very basic, but if this is a one-time event it won't matter.