I'm very new to SQL Server and I want to have dates from today up to 30 days ahead of todays date in one column, which way is the most considered efficient and "correct" way? ( I'm not asking for code ).
I read that loops should preferably be avoided in SQL Server, is that correct? Also, I thought of solving the date-issue with using a logon trigger (adding 30 days ahead of today whenever a logon happens), anyone know a more efficient and "correct" way?
Thanks
You can use recursive CTE to get sequential dates for next 30 days.
CREATE TABLE Dates
(
allDates DATE
)
;WITH MyCTE
AS (SELECT getdate() AS ddate,
dateadd(day, 30, getdate()) AS lastDate
UNION ALL
SELECT dateadd(day, 1, ddate),
lastDate
FROM MyCTE
WHERE dateadd(day, 1, ddate) <= lastDate)
INSERT INTO Dates(allDates)
SELECT ddate FROM MyCTE
SELECT * FROM Dates
SQL Fiddle Demo
The most efficient way to do this would be a Job. SQL Server Agent provides the ability to run any script you want on any interval you choose. A very simplistic approach would be to create a job which runs nightly and inserts a row for [Today + 30 Days].
I believe you are seeking 30 rows from a query with each row representing a date starting at today, and finishing 30 days after today.
There are many potential solutions for this that don't use a cursor/loop, for example
select
dateadd(day,nums.number,nums.today) as a_date
from (
select
number
, cast(getdate() as date) as today
FROM master.dbo.spt_values as sv
WHERE sv.type = 'P'
AND sv.number BETWEEN 0 and 29
) nums
see: this SQLfiddle demo
Note that query is using master.dbo.spt_values and some prefer not to use this (refer here). So instead you could use a small union all with cross join to generate the rows, or you can use a recursive "common table expression" (CTE) as an alternative.
;WITH
Digits AS (
SELECT 0 AS digit UNION ALL
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
)
, Tally AS (
SELECT [tens].digit * 10 + [ones].digit AS number
FROM Digits [ones]
CROSS JOIN Digits [tens]
)
select
dateadd(day,nums.number,nums.today) as a_date
from (
select
number
, cast(getdate() as date) as today
FROM tally
WHERE number BETWEEN 0 and 29
) nums
To get todays date + 30 days do this:
select dateadd(dd,30,getdate())
Related
I need a query that returns all the hours of the day in 12 hour format
ex: 12:00 am, 1:00am, 2:00am etc. This is going to be used in SSRS as a selection field for a parameter for time. I need to select records within a date range and then from a time range in that date range. I have this query which returns the time in 24 hour format but it is not working properly in SSRS:
With CTE(N)
AS
(
SELECT 0
UNION ALL
SELECT N+30
FROM CTE
WHERE N+5<24*60
)
SELECT CONVERT(TIME,DATEADD(minute,N,0) ,108)
FROM CTE
OPTION (MAXRECURSION 0)
This is how I would do it:
DECLARE #t time(1) = '00:00'; --I use 1 as when I use REPLACE later it means that I can "identify" the correct :00 to remove
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT TOP 24 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2),
Times AS(
SELECT DATEADD(HOUR, I,#t) AS [Time]
FROM Tally)
SELECT T.[Time],
REPLACE(CONVERT(varchar(12),T.Time,9),':00.0',' ') AS TimeString
FROM Times T
ORDER BY T.[Time] ASC;
Note that I return both a time and varchar datatype; both are important as the ordering of the data for a varchar would be quite different to start with and if you are using SSRS, I suspect you want the value of TimeString as a presentation thing and not the actual value.
Sample data in tblData:
RowID SID Staken DateTaken
---------------------------------------------
1 1 1 2014-09-15 14:18:11.997
2 1 1 2014-09-16 14:18:11.997
3 1 1 2014-09-17 14:18:11.997
I would like to get the daywise count of SIDs and also a cumulative sum like
Date ThisDayCount TotalCount
-----------------------------------
2014-09-15 1 1
2014-09-16 10 11
2014-09-17 30 41
This is what I have now in my stored procedure with the start & end date parameters. Is there a more elegant way to do this?
;WITH TBL AS
(
SELECT
CONVERT(date, asu.DateTaken) AS Date,
COUNT(*) AS 'ThisDayCount'
FROM
tblData asu
WHERE
asu.SID = 1
AND asu.STaken = 1
AND asu.DateTaken >= #StartDate
AND asu.DateTaken <= #EndDate
GROUP BY
CONVERT(date, asu.DateTaken)
)
SELECT
t1.Date, t1.ThisDayCount, SUM(t1.ThisDayCount) AS 'TotalCount'
FROM
TBL t1
INNER JOIN
TBL t2 ON t1.date >= t2.date
GROUP BY
t1.Date, t1.ThisDayCount
I am not aware of a more elegant way to do that, other than perhaps with a subquery for your running total. What you have is pretty elegant by T-SQL standards.
But, depending on how many records you have to process and what your indexes look like, this could be very slow. You don't say what the destination of this information is, but if it's any kind of report or web page, I'd consider doing the running total as part of the processing at the destination rather than in the database.
I want to split date part like year and assign it to a variable in a stored procedure.
I run that stored procedure in sql azure. it throws error "Reference to database and/or server name in 'MASTER..spt_values' is not supported in this version of SQL Server."
Code:
declare #Year int
SET #Year =DATEPART(YYYY,GETDATE())
create table #SundayDates (Sunday datetime,NextSunday datetime)
INSERT INTO #SundayDates(Sunday,NextSunday)
SELECT max(dates),MAX(DATEADD(DD,+7,dates)) AS last_sunday from
(
SELECT dateadd(day,number-1,DATEADD(year,#year-1900,0)) AS dates
FROM MASTER..spt_values WHERE type='p' and
number between 1 and DATEDIFF(day,DATEADD(year,#year-1900,0),DATEADD(year,#year-1900+1,0))
) as t
WHERE DATENAME(weekday,dates)='sunday' GROUP BY DATEADD(month,datediff(month,0,dates),0)
This query gives the same results (as a result set, rather than inserting into a temp table, but can be easily adapted to do so) and doesn't rely on the spt_values table that the error message tells you isn't allowed:
;With Numbers (Num) as (
select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all select 8 union all select 9 union all select 10 union all select 11
), MonthEnds as (
select DATEADD(month,DATEDIFF(year,'20010101',CURRENT_TIMESTAMP)*12 + n.Num,'20010131') as EndDate
from Numbers n
), LastSundays as (
select DATEADD(day,-n.Num,EndDate) as EndDate
from
MonthEnds me
cross join
Numbers n
where
n.Num between 0 and 6 and
DATEPART(weekday,DATEADD(day,-n.Num,EndDate)) = DATEPART(weekday,'20130512')
)
select EndDate,DATEADD(day,7,EndDate) as FollowingSunday
from LastSundays
I have an ordertable with orders. I want to calculate the amount of subscriptiondays for each user (preffered in a set-based way) for a specific day.
create table #orders (orderid int, userid int, subscriptiondays int, orderdate date)
insert into #orders
select 1, 2, 10, '2011-01-01'
union
select 2, 1, 10, '2011-01-10'
union
select 3, 1, 10, '2011-01-15'
union
select 4, 2, 10, '2011-01-15'
declare #currentdate date = '2011-01-20'
--userid 1 is expected to have 10 subscriptiondays left
(since there is 5 left when the seconrd order is placed)
--userid 2 is expected to have 5 subscriptionsdays left
I'm sure this has been done before, I just dont know what to search for.
Pretty much like a running total?
So when I set #currentdate to '2011-01-20' I want this result:
userid subscriptiondays
1 10
2 5
When I set #currentdate to '2011-01-25'
userid subscriptiondays
1 5
2 0
When I set #currentdate to '2011-01-11'
userid subscriptiondays
1 9
2 0
Thanks!
I think you would need to use a recursive common table expression.
EDIT: I've also added a procedural implementation further below instead of using a recursive common table expression. I recommend using that procedural approach, as I think there may be a number of data scenarios that the recursive CTE query that I've included probably doesn't handle.
The query below gives the correct answers for the scenarios that you've provided, but you would probably want to think up some additional complex scenarios and see whether there are any bugs.
For instance, I have a feeling that this query may break down if you have multiple previous orders overlapping with a later order.
with CurrentOrders (UserId, SubscriptionDays, StartDate, EndDate) as
(
select
userid,
sum(subscriptiondays),
min(orderdate),
dateadd(day, sum(subscriptiondays), min(orderdate))
from #orders
where
#orders.orderdate <= #currentdate
-- start with the latest order(s)
and not exists (
select 1
from #orders o2
where
o2.userid = #orders.userid
and o2.orderdate <= #currentdate
and o2.orderdate > #orders.orderdate
)
group by
userid
union all
select
#orders.userid,
#orders.subscriptiondays,
#orders.orderdate,
dateadd(day, #orders.subscriptiondays, #orders.orderdate)
from #orders
-- join any overlapping orders
inner join CurrentOrders on
#orders.userid = CurrentOrders.UserId
and #orders.orderdate < CurrentOrders.StartDate
and dateadd(day, #orders.subscriptiondays, #orders.orderdate) > CurrentOrders.StartDate
)
select
UserId,
sum(SubscriptionDays) as TotalSubscriptionDays,
min(StartDate),
sum(SubscriptionDays) - datediff(day, min(StartDate), #currentdate) as RemainingSubscriptionDays
from CurrentOrders
group by
UserId
;
Philip mentioned a concern about the recursion limit on common table expressions. Below is a procedural alternative using a table variable and a while loop, which I believe accomplishes the same thing.
While I've verified that this alternative code does work, at least for the sample data provided, I'd be glad to hear anyone's comments on this approach. Good idea? Bad idea? Any concerns to be aware of?
declare #ModifiedRows int
declare #CurrentOrders table
(
UserId int not null,
SubscriptionDays int not null,
StartDate date not null,
EndDate date not null
)
insert into #CurrentOrders
select
userid,
sum(subscriptiondays),
min(orderdate),
min(dateadd(day, subscriptiondays, orderdate))
from #orders
where
#orders.orderdate <= #currentdate
-- start with the latest order(s)
and not exists (
select 1
from #orders o2
where
o2.userid = #orders.userid
and o2.orderdate <= #currentdate
-- there does not exist any other order that surpasses it
and dateadd(day, o2.subscriptiondays, o2.orderdate) > dateadd(day, #orders.subscriptiondays, #orders.orderdate)
)
group by
userid
set #ModifiedRows = ##ROWCOUNT
-- perform an extra update here in case there are any additional orders that were made after the start date but before the specified #currentdate
update co set
co.SubscriptionDays = co.SubscriptionDays + #orders.subscriptiondays
from #CurrentOrders co
inner join #orders on
#orders.userid = co.UserId
and #orders.orderdate <= #currentdate
and #orders.orderdate >= co.StartDate
and dateadd(day, #orders.subscriptiondays, #orders.orderdate) < co.EndDate
-- Keep attempting to update rows as long as rows were updated on the previous attempt
while(#ModifiedRows > 0)
begin
update co set
SubscriptionDays = co.SubscriptionDays + overlap.subscriptiondays,
StartDate = overlap.orderdate
from #CurrentOrders co
-- join any overlapping orders
inner join (
select
#orders.userid,
sum(#orders.subscriptiondays) as subscriptiondays,
min(orderdate) as orderdate
from #orders
inner join #CurrentOrders co2 on
#orders.userid = co2.UserId
and #orders.orderdate < co2.StartDate
and dateadd(day, #orders.subscriptiondays, #orders.orderdate) > co2.StartDate
group by
#orders.userid
) overlap on
overlap.userid = co.UserId
set #ModifiedRows = ##ROWCOUNT
end
select
UserId,
sum(SubscriptionDays) as TotalSubscriptionDays,
min(StartDate),
sum(SubscriptionDays) - datediff(day, min(StartDate), #currentdate) as RemainingSubscriptionDays
from #CurrentOrders
group by
UserId
EDIT2: I've made some adjustments to the code above to address various special cases, such as if there just happen to be two orders for a user that both end on the same date.
For instance, changing the setup data to the following caused issues with the original code, which I've now corrected:
insert into #orders
select 1, 2, 10, '2011-01-01'
union
select 2, 1, 10, '2011-01-10'
union
select 3, 1, 10, '2011-01-15'
union
select 4, 2, 6, '2011-01-15'
union
select 5, 2, 4, '2011-01-17'
EDIT3: I've made some additional adjustments to address other special cases. In particular, the previous code ran into issues with the following setup data, which I've now corrected:
insert into #orders
select 1, 2, 10, '2011-01-01'
union
select 2, 1, 6, '2011-01-10'
union
select 3, 1, 10, '2011-01-15'
union
select 4, 2, 10, '2011-01-15'
union
select 5, 1, 4, '2011-01-12'
If my clarifying comment/question is correct, then you want to use DATEDIFF:
DATEDIFF(dd, orderdate, #currentdate)
My interpretation of the problem:
On day X, customer buys a “span” of subscription days (i.e. good for N days)
The span starts on the day of purchase and is good for X through day X + (N - 1)... but see below
If customer purchases a second span after the first expires (or any new span after all existing spans expire), repeat process. (A single 10-day purchase 30 days ago has no impact on a second purhcase made today.)
If customer purchases a span while existing span(s) are still in effect, the new span applies to day immediately after end of current span(s) through that date + (N – 1)
This is iterative. If customer buys 10-day spans on Jan 1st, Jan 2nd, and Jan 3rd, it would look something like:
As of 1st: Jan 1 – Jan 10
As of 2nd: Jan 1 – Jan 10, Jan 11 – Jan 20 (in effect, Jan 1 to Jan 20)
As of 3rd: Jan 1 – Jan 10, Jan 11 – Jan 20, Jan 21 – Jan 30 (in effect, Jan 1 to Jan 30)
If this is indeed the problem, then it is a horrible problem to solve in T-SQL. To deterimine the “effective span” of a given purchase, you have to calculate the effective span of all prior purchases in the order that they were purchased, because of that overall cumulative effect. This is a trivial problem with 1 user and 3 rows, but non-trivial with thousands of users with dozens of purchases (which, presumably, is what you want).
I would solve it like so:
Add column EffectiveDate of datatype date to the table
Build a one-time process to walk through every row user-by-user and orderdate by orderdate, and calculate the EffectiveDate as discussed above
Modify the process used to insert the data to calculate the EffectiveDate at the time a new entry is made. Done this way, you’d only ever have to reference the most recent purchase made by that user.
Wrangle out subsequent issues regarding deleting (cancelled?) or updating (mis-set?) orders
I may be wrong, but I don't see any way to address this using set-based tactics. (Recursive CTEs and the like would work, but they can only recurse to so many levels, and we don't know the limit for this problem -- let alone how often you'll need to run it, or how well it must perform.) I'll watch and upvote anyone who solves this without recursion!
And of course this only applies if my understanding of the problem is correct. If not, please disregard.
In fact, we need calculate summ of subscriptiondays minus days beetwen first subscrible date and #currentdate like:
select userid,
sum(subsribtiondays)-
DATEDIFF('dd',
(select min(orderdate)
from #orders as a
where a.userid=userid), #currentdate)
from #orders
where orderdate <= #currentdata
group by userid
I have a table with timestamps. What is the proper query to get the records counts for each minute for the last hour.
I.e. if now is 2:25, I want to know how many record were between 1:25 and 1:26, 1:26 and 1:27, and so on, so I have 60 results.
This will return a count of results for each minute (where you have records) in the last hour
SELECT DATEPART(n, time_stamp) AS minute, COUNT(*) as results
FROM table_name
WHERE time_stamp > DATEADD(hh, -1, GETDATE())
GROUP BY DATEPART(n, time_stamp)
This may return less than 60 results, depending on the data. If you have to have 60 results, the query is slightly different. This uses a Common Table Expression to generate a list of 60 numbers and a correlated sub-query to get the results for each minute:
WITH numbers ( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < 60 )
SELECT num AS minute,
(SELECT COUNT(*) AS results
FROM table_name
WHERE DATEPART(n, time_stamp) = num
AND time_stamp > DATEADD(hh, -1, GETDATE())
FROM numbers
To see the results, replace DATEADD(hh, -1, GETDATE()) with DATEADD(mi, -15, GETDATE()) and you'll get the results for the last 15 minutes and 0 for other minutes.
This is an alternative I have found useful for determining how many records are inserted or updated per minute. The nice thing about having your date format as a variable up front is that you can easily change it to analyze per hour instead. Hope this helps!
DECLARE #dateFormat as varchar(max) = 'yyyy-MM-dd HH:mm'
SELECT format(timeColumn, #dateFormat) AS minute, COUNT(*) as results
FROM yourTable
WHERE timeColumn > DATEADD(hh, -1, GETDATE())
GROUP BY format(timeColumn, #dateFormat)
ORDER BY 1
As you edited the question, I edit my answer. If I have understood you correctly, you want to look only at the past hour - that is, a timespan from one hour before the request is made to the current time. This is how I'd do it:
SELECT
COUNT(yourTimeStamp)
FROM yourTable
WHERE DATEADD('hh', -1, GetDate()) <= yourTimeStamp
AND yourTimeStamp < GetDate()
GROUP BY DATEPART('mm', yourTimeStamp)
I am not entirely sure that the syntax is exact. When coding in MSSQL, I would use the CURRENT_TIMESTAMP for the current time, MINUTE instead of DATEPART etc, but you get the idea for the solution.
DATEPART is what you're looking for:
declare #times table
(
someTime datetime
)
INSERT INTO #Times (sometime) values ('jan 12 2008 12:23')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:34')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:35')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:25')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:02')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:09')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:35')
select DATEPART(mi,sometime) AS Minute, count(*) AS NumOccurances
from #Times
WHERE SomeTime BETWEEN #Lower AND #Upper
GROUP BY DATEPART(mi, sometime)
order by NumOccurances DESC
Result:
Minute NumOccurances
35 2
2 1
9 1
23 1
25 1
34 1
If you want to group results by minute, then you can use a formatted string. This will group by number of minutes since 1/1/1900 not minute within day.
WITH formatted AS (
SELECT FORMAT(<your_datetime_column>, 'yyyy-MM-dd HH:mm') AS minute_text
FROM <your_table>
)
SELECT minute_text, COUNT(*) AS number_of_rows
FROM formatted
GROUP BY minute_text
ORDER BY 1 DESC
Here's my fixed up version of Robin's answer. I made it output the errors in the correct order and output the time as well instead of just the number which isn't super useful if you're charting this out.
WITH numbers ( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < 60 )
SELECT (SELECT DATEADD(n, -num, GETDATE())) AS TimeStamp,
(SELECT COUNT(*) AS results
FROM ErrorLogs
WHERE DATEPART(n, TimeStamp) = DATEPART(n, DATEADD(n, -num, GETDATE()))
AND TimeStamp > DATEADD(hh, -1, GETDATE())) as Count
FROM numbers
SELECT COUNT (TS) from TABLE where TABLE.TS BETWEEN(starttime, endtime)