I'd like to allow my users to setup a schedule for their events. It could be a single day, or for convenience I'd like to allow them to specify a reoccurring event (similar to an Outlook appointment).
For the single event it seems pretty easy (pseudo-code):
Just have a DateOfEvent column that has the date on it.
To grab future events:
Select * from events where DateOfEvent > {DateTime.Now}
But how could I store and query a reoccurring event? I don't need to do times, as I'd just store that seperately, and if they needed a different time I'd just have them create another event. So no: Every wednesday at 5 and thursdays at 3.
Examples:
Every mon, tues, wed, thu, fri, every week
Every wed every week
Every second tuesday of the month
What I used to query
I added a start and end dates. If the user selected a single date I set both start and end dates to the chosen date. I just had to modify the answer's code a bit.
DECLARE
#StartDate SMALLDATETIME,
#EndDate SMALLDATETIME;
SELECT
#StartDate = '20091129',
#EndDate = '20101220';
SELECT
d.CurrentDate,
m.*
FROM
Calendar AS d
INNER JOIN Meet AS m
ON
(
(d.CurrentDate = m.StartDate AND d.CurrentDate = m.EndDate)
OR d.DaysOfTheMonth = m.DayOfTheMonth
OR (d.DaysOfTheWeek = m.DayOfTheWeek AND COALESCE(m.WeekOfTheMonth, d.WeekOfTheMonth) = d.WeekOfTheMonth)
OR d.DaysOfTheWeek IN (1,7) AND m.OnWeekends = 1
OR d.DaysOfTheWeek BETWEEN 2 AND 6 AND m.OnWeekDays = 1
)
-- now you can inner join to the event table
-- to list the name, other details, etc.
WHERE
d.CurrentDate BETWEEN #StartDate AND #EndDate
AND d.CurrentDate BETWEEN m.StartDate AND m.EndDate
ORDER BY d.CurrentDate;
GO
Filling in the Calandar table:
INSERT dbo.Calendar(CurrentDate, DaysOfTheMonth, DaysOfTheWeek, WeekOfTheMonth, IsWeekDay, Even)
SELECT
CurrentDate, DaysOfTheMonth, DaysOfTheWeek,
WeekOfTheMonth = DATEDIFF(WEEK, DATEADD(DAY, 1-DaysOfTheMonth, CurrentDate), CurrentDate) + 1,
CASE WHEN DaysOfTheWeek IN (1, 7) THEN 0 ELSE 1 END AS IsWeekDay,
CASE WHEN w % 2 = 1 THEN 0 ELSE 1 END AS Even
FROM
(
SELECT
CurrentDate,
DaysOfTheMonth = DATEPART(DAY, CurrentDate),
DaysOfTheWeek = DATEPART(WEEKDAY, CurrentDate),
w = DATEPART(WEEK, CurrentDate)
FROM
(
SELECT CurrentDate = DATEADD(DAY, n-1, '20141001')
FROM
(
SELECT TOP 900
n = ROW_NUMBER() OVER (ORDER BY c1.[object_id])
FROM sys.all_columns AS c1
CROSS JOIN sys.all_columns AS c2
ORDER BY n
) AS x) AS y) AS z;
You could do something like this:
CREATE TABLE dbo.EventSchedule
(
EventID INT, -- FOREIGN KEY to event details
EventDate SMALLDATETIME, -- if one-time event
DayOfMonth TINYINT, -- if once a month, e.g. 3rd of every month
DayOfWeek TINYINT, -- if once a week, e.g. every Tuesday = 3
WeekDays BIT, -- if only on weekdays, e.g. 1 = mon-fri
Weekends BIT, -- if only on weekends, e.g. 1 = sat-sun
-- the next two are combined, e.g. 2/2 = 2nd Monday of each month
MonthlyInstance TINYINT,
MonthlyWeekday TINYINT
);
So then if you wanted to find all the events scheduled to happen on a given date, you could do:
DECLARE
#dt SMALLDATETIME,
#dm TINYINT,
#dw TINYINT,
#inst TINYINT;
SELECT
#dt = '20091201',
#dm = DATEPART(DAY, #dt)
#dw = DATEPART(WEEKDAY, #dt),
#inst = DATEDIFF(WEEK, DATEADD(DAY, 1-#dm, #dt), #dt) + 1;
SELECT EventID
FROM dbo.EventSchedule
WHERE EventDate = #dt
OR DayOfMonth = #dm
OR DayOfWeek = #dw
OR (Weekdays = 1 AND #dw BETWEEN 2 AND 6)
OR (Weekends = 1 AND #dw IN (1,7))
OR (MonthlyInstance = #inst AND MonthlyWeekday = #dw);
This is untested (I am posting from a Mac on Thanksgiving, after all), and relies on SET DATEFIRST being the default (where Sunday = 1, Monday = 2, etc). I'm also not 100% confident on the 2nd Tuesday part, as it likely needs some additional math depending on the weekday of the first day of the month. But I thought this could give you a start for the other parts, and I'll come back and revisit when I have a chance.
For some much more complicated options, see: http://www.codeproject.com/KB/database/sqlscheduleselector.aspx
And here is a more complete example, with a populated calendar table, some sample events of different types, and a query that retrieves all of the expected events given a date range.
CREATE DATABASE test;
GO
USE test;
GO
SET NOCOUNT ON;
GO
CREATE TABLE dbo.Calendar
(
dt SMALLDATETIME PRIMARY KEY,
dm TINYINT,
dw TINYINT,
mw TINYINT,
wd BIT
);
GO
-- populate the table with the days from 2009
-- (you will want more obviously; change TOP or add WHERE)
INSERT dbo.Calendar(dt, dm, dw, mw, wd)
SELECT
dt, dm, dw,
mw = DATEDIFF(WEEK, DATEADD(DAY, 1-dm, dt), dt) + 1,
CASE WHEN dw IN (1, 7) THEN 0 ELSE 1 END
FROM
(
SELECT
dt,
dm = DATEPART(DAY, dt),
dw = DATEPART(WEEKDAY, dt)
FROM
(
SELECT dt = DATEADD(DAY, n-1, '20090101')
FROM
(
SELECT TOP 365
n = ROW_NUMBER() OVER (ORDER BY c1.[object_id])
FROM sys.all_columns AS c1
CROSS JOIN sys.all_columns AS c2
ORDER BY n
) AS x) AS y) AS z;
GO
-- create your schedule table:
CREATE TABLE dbo.EventSchedule
(
EventID INT, -- FOREIGN KEY to event details
EventDate SMALLDATETIME, -- if one-time event
[DayOfMonth] TINYINT, -- if once a month, e.g. 3rd of every month
[DayOfWeek] TINYINT, -- if once a week, e.g. every Tuesday = 3
Weekdays BIT, -- if only on weekdays, e.g. 1 = mon-fri
Weekends BIT, -- if only on weekends, e.g. 1 = sat-sun
-- if you want every day, set Weekdays+Weekends = 1
WeekOfMonth TINYINT -- if only the nth Tuesday etc.
);
-- create some events:
INSERT dbo.EventSchedule
(
EventID,
EventDate,
[DayOfMonth],
[DayOfWeek],
Weekdays,
Weekends,
WeekOfMonth
)
-- one on Jan 5th:
SELECT 1, '20090105', NULL, NULL, NULL, NULL, NULL
-- one on the 3rd of every month:
UNION ALL SELECT 2, NULL, 3, NULL, NULL, NULL, NULL
-- one every Tuesday:
UNION ALL SELECT 3, NULL, NULL, 3, NULL, NULL, NULL
-- one the 2nd Wednesday of each month:
UNION ALL SELECT 4, NULL, NULL, 4, NULL, NULL, 2
-- one only on weekends:
UNION ALL SELECT 5, NULL, NULL, NULL, NULL, 1, NULL
-- one only on weekdays:
UNION ALL SELECT 6, NULL, NULL, NULL, 1, NULL, NULL;
-- now, we have 6 events, some of which will happen
-- multiple times in any given date range. So let's
-- try it:
DECLARE
#StartDate SMALLDATETIME,
#EndDate SMALLDATETIME;
SELECT
#StartDate = '20090101',
#EndDate = '20090108';
SELECT
d.dt,
e.EventID
FROM
dbo.Calendar AS d
INNER JOIN dbo.EventSchedule AS e
ON
(
d.dt = e.EventDate
OR d.dm = e.[DayOfMonth]
OR (d.dw = e.[DayOfWeek] AND COALESCE(e.WeekOfMonth, d.mw) = d.mw)
OR d.dw IN (1,7) AND e.Weekends = 1
OR d.dw BETWEEN 2 AND 6 AND e.Weekdays = 1
)
-- now you can inner join to the event table
-- to list the name, other details, etc.
WHERE
d.dt BETWEEN #StartDate AND #EndDate
ORDER BY d.dt, e.EventID;
GO
DROP TABLE dbo.Calendar, dbo.EventSchedule;
GO
USE [master];
GO
DROP DATABASE test;
GO
For storing the schedule, take a look at my answer in this question
What is the best way to represent "Recurring Events" in database?
Here I describe how SQL Server stores schedule information in their sysschedules and related tables. They have a really nice schema design which supports all the scheduling cases you are asking about. The documentation on MSDN is also very helpful.
To get all the schedule dates between a date range for outlook type display, take a look at Peter Larsson's blog, he created an excellent sproc to calculate these values.
CREATE PROCEDURE dbo.uspGetScheduleTimes
(
#startDate DATETIME,
#endDate DATETIME
)
AS
/*
This code is blogged here
http://weblogs.sqlteam.com/peterl/archive/2008/10/10/Keep-track-of-all-your-jobs-schedules.aspx
*/
SET NOCOUNT ON
-- Create a tally table. If you already have one of your own please use that instead.
CREATE TABLE #tallyNumbers
(
num SMALLINT PRIMARY KEY CLUSTERED
)
DECLARE #index SMALLINT
SET #index = 1
WHILE #index <= 8640
BEGIN
INSERT #tallyNumbers
(
num
)
VALUES (
#index
)
SET #index = #index + 1
END
-- Create a staging table for jobschedules
CREATE TABLE #jobSchedules
(
rowID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
serverName SYSNAME NOT NULL,
jobName SYSNAME NOT NULL,
jobDescription NVARCHAR(512) NOT NULL,
scheduleName SYSNAME NOT NULL,
scheduleID INT NOT NULL,
categoryName SYSNAME NOT NULL,
freq_type INT NOT NULL,
freq_interval INT NOT NULL,
freq_subday_type INT NOT NULL,
freq_subday_interval INT NOT NULL,
freq_relative_interval INT NOT NULL,
freq_recurrence_factor INT NOT NULL,
startDate DATETIME NOT NULL,
startTime DATETIME NOT NULL,
endDate DATETIME NOT NULL,
endTime DATETIME NOT NULL,
jobEnabled INT NOT NULL,
scheduleEnabled INT NOT NULL
)
/*
-- Popoulate the staging table for JobSchedules with SQL Server 2000
INSERT #jobSchedules
(
serverName,
jobName,
jobDescription,
scheduleName,
scheduleID,
categoryName,
freq_type,
freq_interval,
freq_subday_type,
freq_subday_interval,
freq_relative_interval,
freq_recurrence_factor,
startDate,
startTime,
endDate,
endTime,
jobEnabled,
scheduleEnabled
)
SELECT sj.originating_server,
sj.name,
COALESCE(sj.description, ''),
sjs.name,
sjs.schedule_id,
sc.name,
sjs.freq_type,
sjs.freq_interval,
sjs.freq_subday_type,
sjs.freq_subday_interval,
sjs.freq_relative_interval,
sjs.freq_recurrence_factor,
COALESCE(STR(sjs.active_start_date, 8), CONVERT(CHAR(8), GETDATE(), 112)),
STUFF(STUFF(REPLACE(STR(sjs.active_start_time, 6), ' ', '0'), 3, 0, ':'), 6, 0, ':'),
STR(sjs.active_end_date, 8),
STUFF(STUFF(REPLACE(STR(sjs.active_end_time, 6), ' ', '0'), 3, 0, ':'), 6, 0, ':'),
sj.enabled,
sjs.enabled
FROM msdb..sysjobschedules AS sjs
INNER JOIN msdb..sysjobs AS sj ON sj.job_id = sjs.job_id
INNER JOIN msdb..syscategories AS sc ON sc.category_id = sj.category_id
WHERE sjs.freq_type IN (1, 4, 8, 16, 32)
ORDER BY sj.originating_server,
sj.name,
sjs.name
*/
-- Popoulate the staging table for JobSchedules with SQL Server 2005 and SQL Server 2008
INSERT #JobSchedules
(
serverName,
jobName,
jobDescription,
scheduleName,
scheduleID,
categoryName,
freq_type,
freq_interval,
freq_subday_type,
freq_subday_interval,
freq_relative_interval,
freq_recurrence_factor,
startDate,
startTime,
endDate,
endTime,
jobEnabled,
scheduleEnabled
)
SELECT srv.srvname,
sj.name,
COALESCE(sj.description, ''),
ss.name,
ss.schedule_id,
sc.name,
ss.freq_type,
ss.freq_interval,
ss.freq_subday_type,
ss.freq_subday_interval,
ss.freq_relative_interval,
ss.freq_recurrence_factor,
COALESCE(STR(ss.active_start_date, 8), CONVERT(CHAR(8), GETDATE(), 112)),
STUFF(STUFF(REPLACE(STR(ss.active_start_time, 6), ' ', '0'), 3, 0, ':'), 6, 0, ':'),
STR(ss.active_end_date, 8),
STUFF(STUFF(REPLACE(STR(ss.active_end_time, 6), ' ', '0'), 3, 0, ':'), 6, 0, ':'),
sj.enabled,
ss.enabled
FROM msdb..sysschedules AS ss
INNER JOIN msdb..sysjobschedules AS sjs ON sjs.schedule_id = ss.schedule_id
INNER JOIN msdb..sysjobs AS sj ON sj.job_id = sjs.job_id
INNER JOIN sys.sysservers AS srv ON srv.srvid = sj.originating_server_id
INNER JOIN msdb..syscategories AS sc ON sc.category_id = sj.category_id
WHERE ss.freq_type IN(1, 4, 8, 16, 32)
ORDER BY srv.srvname,
sj.name,
ss.name
-- Only deal with jobs that has active start date before #endDate
DELETE
FROM #JobSchedules
WHERE startDate > #endDate
-- Only deal with jobs that has active end date after #startDate
DELETE
FROM #JobSchedules
WHERE endDate < #startDate
-- Deal with first, second, third, fourth and last occurence
DECLARE #tempStart DATETIME,
#tempEnd DATETIME
SELECT #tempStart = DATEADD(MONTH, DATEDIFF(MONTH, '19000101', #startDate), '19000101'),
#TempEnd = DATEADD(MONTH, DATEDIFF(MONTH, '18991231', #endDate), '18991231')
CREATE TABLE #dayInformation
(
infoDate DATETIME PRIMARY KEY CLUSTERED,
weekdayName VARCHAR(9) NOT NULL,
statusCode INT NOT NULL,
lastDay TINYINT DEFAULT 0
)
WHILE #tempStart <= #tempEnd
BEGIN
INSERT #dayInformation
(
infoDate,
weekdayName,
statusCode
)
SELECT #tempStart,
DATENAME(WEEKDAY, #tempStart),
CASE
WHEN DATEPART(DAY, #tempStart) BETWEEN 1 AND 7 THEN 1
WHEN DATEPART(DAY, #tempStart) BETWEEN 8 AND 14 THEN 2
WHEN DATEPART(DAY, #tempStart) BETWEEN 15 AND 21 THEN 4
WHEN DATEPART(DAY, #tempStart) BETWEEN 22 AND 28 THEN 8
ELSE 0
END
SET #tempStart = DATEADD(DAY, 1, #tempStart)
END
UPDATE di
SET di.statusCode = di.statusCode + 16
FROM #dayInformation AS di
INNER JOIN (
SELECT DATEDIFF(MONTH, '19000101', infoDate) AS theMonth,
DATEPART(DAY, MAX(infoDate)) - 6 AS theDay
FROM #dayInformation
GROUP BY DATEDIFF(MONTH, '19000101', infoDate)
) AS x ON x.theMonth = DATEDIFF(MONTH, '19000101', di.infoDate)
WHERE DATEPART(DAY, di.infoDate) >= x.theDay
UPDATE di
SET di.lastDay = 16
FROM #dayInformation AS di
INNER JOIN (
SELECT DATEDIFF(MONTH, '19000101', infoDate) AS theMonth,
MAX(infoDate) AS theDay
FROM #dayInformation
GROUP BY DATEDIFF(MONTH, '19000101', infoDate)
) AS x ON x.theMonth = DATEDIFF(MONTH, '19000101', di.infoDate)
WHERE di.infoDate = x.theDay
UPDATE #dayInformation
SET lastDay = DATEPART(DAY, infoDate)
WHERE DATEPART(DAY, infoDate) BETWEEN 1 AND 4
-- Stage all individual schedule times
CREATE TABLE #scheduleTimes
(
rowID INT NOT NULL,
infoDate DATETIME NOT NULL,
startTime DATETIME NOT NULL,
endTime DATETIME NOT NULL,
waitSeconds INT DEFAULT 0
)
CREATE CLUSTERED INDEX IX_rowID ON #scheduleTimes(rowID)
-- Insert one time only schedules
INSERT #scheduleTimes
(
rowID,
infoDate,
startTime,
endTime
)
SELECT rowID,
startDate,
startTime,
endTime
FROM #jobSchedules
WHERE freq_type = 1
AND startDate >= #StartDate
AND startDate <= #EndDate
-- Insert daily schedules
INSERT #scheduleTimes
(
rowID,
infoDate,
startTime,
endTime,
waitSeconds
)
SELECT js.rowID,
di.infoDate,
js.startTime,
js.endTime,
CASE js.freq_subday_type
WHEN 1 THEN 0
WHEN 2 THEN js.freq_subday_interval
WHEN 4 THEN 60 * js.freq_subday_interval
WHEN 8 THEN 3600 * js.freq_subday_interval
END
FROM #jobSchedules AS js
INNER JOIN #dayInformation AS di ON di.infoDate >= #startDate
AND di.infoDate <= #endDate
WHERE js.freq_type = 4
AND DATEDIFF(DAY, js.startDate, di.infoDate) % js.freq_interval = 0
-- Insert weekly schedules
INSERT #scheduleTimes
(
rowID,
infoDate,
startTime,
endTime,
waitSeconds
)
SELECT js.rowID,
di.infoDate,
js.startTime,
js.endTime,
CASE js.freq_subday_type
WHEN 1 THEN 0
WHEN 2 THEN js.freq_subday_interval
WHEN 4 THEN 60 * js.freq_subday_interval
WHEN 8 THEN 3600 * js.freq_subday_interval
END
FROM #jobSchedules AS js
INNER JOIN #dayInformation AS di ON di.infoDate >= #startDate
AND di.infoDate <= #endDate
WHERE js.freq_type = 8
AND 1 = CASE
WHEN js.freq_interval & 1 = 1 AND di.weekdayName = 'Sunday' THEN 1
WHEN js.freq_interval & 2 = 2 AND di.weekdayName = 'Monday' THEN 1
WHEN js.freq_interval & 4 = 4 AND di.weekdayName = 'Tuesday' THEN 1
WHEN js.freq_interval & 8 = 8 AND di.weekdayName = 'Wednesday' THEN 1
WHEN js.freq_interval & 16 = 16 AND di.weekdayName = 'Thursday' THEN 1
WHEN js.freq_interval & 32 = 32 AND di.weekdayName = 'Friday' THEN 1
WHEN js.freq_interval & 64 = 64 AND di.weekdayName = 'Saturday' THEN 1
ELSE 0
END
AND(DATEDIFF(DAY, js.startDate, di.infoDate) / 7) % js.freq_recurrence_factor = 0
-- Insert monthly schedules
INSERT #scheduleTimes
(
rowID,
infoDate,
startTime,
endTime,
waitSeconds
)
SELECT js.rowID,
di.infoDate,
js.startTime,
js.endTime,
CASE js.freq_subday_type
WHEN 1 THEN 0
WHEN 2 THEN js.freq_subday_interval
WHEN 4 THEN 60 * js.freq_subday_interval
WHEN 8 THEN 3600 * js.freq_subday_interval
END
FROM #jobSchedules AS js
INNER JOIN #dayInformation AS di ON di.infoDate >= #startDate
AND di.infoDate <= #endDate
WHERE js.freq_type = 16
AND DATEPART(DAY, di.infoDate) = js.freq_interval
AND DATEDIFF(MONTH, js.startDate, di.infoDate) % js.freq_recurrence_factor = 0
-- Insert monthly relative schedules
INSERT #scheduleTimes
(
rowID,
infoDate,
startTime,
endTime,
waitSeconds
)
SELECT js.rowID,
di.infoDate,
js.startTime,
js.endTime,
CASE js.freq_subday_type
WHEN 1 THEN 0
WHEN 2 THEN js.freq_subday_interval
WHEN 4 THEN 60 * js.freq_subday_interval
WHEN 8 THEN 3600 * js.freq_subday_interval
END
FROM #jobSchedules AS js
INNER JOIN #dayInformation AS di ON di.infoDate >= #startDate
AND di.infoDate <= #endDate
WHERE js.freq_type = 32
AND 1 = CASE
WHEN js.freq_interval = 1 AND di.weekdayName = 'Sunday' THEN 1
WHEN js.freq_interval = 2 AND di.weekdayName = 'Monday' THEN 1
WHEN js.freq_interval = 3 AND di.weekdayName = 'Tuesday' THEN 1
WHEN js.freq_interval = 4 AND di.weekdayName = 'Wednesday' THEN 1
WHEN js.freq_interval = 5 AND di.weekdayName = 'Thursday' THEN 1
WHEN js.freq_interval = 6 AND di.weekdayName = 'Friday' THEN 1
WHEN js.freq_interval = 7 AND di.weekdayName = 'Saturday' THEN 1
WHEN js.freq_interval = 8 AND js.freq_relative_interval = di.lastDay THEN 1
WHEN js.freq_interval = 9 AND di.weekdayName NOT IN('Sunday', 'Saturday') THEN 1
WHEN js.freq_interval = 10 AND di.weekdayName IN('Sunday', 'Saturday') THEN 1
ELSE 0
END
AND di.statusCode & js.freq_relative_interval = js.freq_relative_interval
AND DATEDIFF(MONTH, js.startDate, di.infoDate) % js.freq_recurrence_factor = 0
-- Get the daily recurring schedule times
INSERT #scheduleTimes
(
rowID,
infoDate,
startTime,
endTime,
waitSeconds
)
SELECT st.rowID,
st.infoDate,
DATEADD(SECOND, tn.num * st.waitSeconds, st.startTime),
st.endTime,
st.waitSeconds
FROM #scheduleTimes AS st
CROSS JOIN #tallyNumbers AS tn
WHERE tn.num * st.waitSeconds <= DATEDIFF(SECOND, st.startTime, st.endTime)
AND st.waitSeconds > 0
-- Present the result
SELECT js.scheduleID,
js.serverName,
js.jobName,
js.jobDescription,
js.scheduleName,
js.categoryName,
st.infoDate,
st.startTime,
st.endTime,
js.jobEnabled,
js.scheduleEnabled
FROM #scheduleTimes AS st
INNER JOIN #jobSchedules AS js ON js.rowID = st.rowID
-- Clean up
DROP TABLE #jobSchedules,
#dayInformation,
#scheduleTimes,
#tallyNumbers
For getting a meaningful short description of the schedule like "Every Monday at 2:00pm", take a look at the sp_get_schedule_description sproc.
We have created sql'server job, that periodically (looking at you claim hourly is enough) called some stored procedure. On other turn this procedure selects does 2 things:
calculate NEXT user event basing on some recursive info from table [A]. And place this event to table [B]
Selects ready to fire events from table [B]
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
let's say i have 2 table
First one is "Orders"
Select * from Orders
give me this results.
Order_ID Date_Start Date_End Order_Name
2059 2020-11-13 00:00:00.000 2020-11-14 00:00:00.000 order1
2060 2020-12-12 00:00:00.000 2020-12-22 00:00:00.000 order2
and second table say it "Dates"
This is desired results for Dates table.i need to insert dates between two dates to that table for each order ID.
Date Type1 Type2 Type3 Type4 Type5 Order_ID
2020-11-13 00:00:00.000 NULL NULL NULL NULL NULL 2059
2020-11-14 00:00:00.000 NULL NULL NULL NULL NULL 2059
i hope this is more clear now.
Actually quite simple using OVER/PARTITION and then dateadd().
First, we need to get however many records you want in your final list of records. To do this, pick any table that has at least as many rows as you want. Could be an employee table, customers, orders, whatever. For your example, as long as it had 14 days. From that, lets just create a temp result set giving you a simple run of numbers 1 through whatever... 10, 14, 127, whatever, as long as the table has that many records.
Now, the partition by order by is part of the trick. You can't partition by constants, but you CAN do based on an equation. So, pick the "ID" column of whatever table and multiply by 0 will always give you 0. So your partitioning will group all values with an equated value of 0... Tricky huh... So now, all the records fall into this one group and get assigned a row number within that group. Finish that off with a "TOP 14", and you get your 14 records to start your list basis.
SELECT top 10
ROW_NUMBER() OVER(PARTITION BY SomeTableID * 0 order by SomeTableID * 0) AS "MyRow"
FROM
SomeTable
So now, I have a result set with 10 rows in it with the values running from 1 to 10.
Now, lets build the dates. As long as you are building consecutively, such as per day, per month, per year, or whatever pattern, use one date as your baseline and keep adding. In the sample below, I am using the current date and just keep adding 1 month, but again, you can do for days, weeks, whatever.
select
dateadd( month, Counter.MyRow, convert( date, getdate() )) ListOfDates
from
( SELECT top 10 ROW_NUMBER()
OVER(PARTITION BY SomeTableID * 0 order by SomeTableID * 0) AS "MyRow"
FROM SomeTable ) Counter
So, in the above example, it will return 10 rows starting with today and generate
2020-11-20
2020-12-20
2021-01-20
...
2021-08-20
FOLLOW-UP.
Your query is failing because you are explicitly concatenating strings to build your command... BAD technique. You should parameterize your queries. Build a SQL Command object, add parameters, and THEN call your fill.
var sqlcmd = new SqlCommand("", con);
sqlcmd.CommandText =
#"WITH theDates AS
(
SELECT #parmStartDate as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM theDates
WHERE DATEADD(day, 1, theDate) <= #parmEndDate
)
SELECT theDate
FROM theDates
OPTION(MAXRECURSION 0)";
sqlcmd.Parameters.AddWithValue("parmStartDate", dataGridView.CurrentRow.Cells[2] );
sqlcmd.Parameters.AddWithValue("parmEndDate", dataGridView.CurrentRow.Cells[3] );
var ds = new DataSet();
var dtbl2 = new DataTable();
// pass the pre-formatted and parameterized query command to the SQL Data Adapter
var sda2 = new SqlDataAdapter(sqlcmd);
Here is some sql to build a dynamic date range table. You will need to customize it for your needs in the /* replace with your column after join / and / Join your table sections */
/*
script to build table with dynamic columns
*/
DROP TABLE IF EXISTS #tempDateRange
DROP TABLE IF EXISTS #dateRangeTable
DECLARE #StartDate datetime = DATEADD(DAY, -14, GETDATE()),
#EndDate datetime = GETDATE()
/*
Generate date range table
*/
SELECT DATEADD(DAY, nbr - 1, #StartDate) AS [Date],
UPPER(LEFT(DATENAME(mm, DATEADD(DAY, nbr - 1, #StartDate)), 3)) AS [MonthShort],
MONTH( DATEADD(DAY, nbr - 1, #StartDate)) AS [Month],
YEAR(DATEADD(DAY, nbr - 1, #StartDate)) AS [Year],
CONCAT(UPPER(LEFT(DATENAME(mm, DATEADD(DAY, nbr - 1, #StartDate)), 3)), '-', YEAR(DATEADD(DAY, nbr - 1, #StartDate))) AS MonthYear
INTO #tempDateRange
FROM ( SELECT TOP(DATEDIFF(DAY, #StartDate, #EndDate)) ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n AS Nbr
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n BETWEEN 1 AND DATEDIFF(DAY, #StartDate, #EndDate)
ORDER BY 1
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
/*
Generate columns for date range
*/
DECLARE
#columns NVARCHAR(MAX) = ''
SELECT #columns+=QUOTENAME(convert(nvarchar(10), Date, 120)) + ' NVARCHAR(10),'
FROM (
SELECT DISTINCT Date, [Month], [Year] FROM #tempDateRange
) x
ORDER BY x.[Year], x.[Month]
SET #columns = LEFT(#columns, LEN(#columns) - 1);
DECLARE #sql NVARCHAR(MAX) = ''
SET #sql = '
INSERT #dateRangeTable
SELECT *
FROM (
SELECT a.TestData AS Data, /* replace with your column after join */
convert(nvarchar(10), Date, 120) AS [Date]
FROM #tempDateRange [date]
/* Join your table */
LEFT JOIN (
SELECT ''start test'' AS TestData, CAST('''+CONVERT(NVARCHAR, #StartDate)+''' AS DATE) AS TargetDate
UNION
SELECT ''end test'' AS TestData, CAST('''+CONVERT(NVARCHAR, DATEADD(DAY, -1, #EndDate))+''' AS DATE) AS TargetDate
) AS a ON CAST(a.TargetDate AS DATE) = CAST(date.[date] AS DATE)
WHERE [date].[Date] BETWEEN CAST('''+CONVERT(NVARCHAR, #StartDate)+''' AS DATE) AND CAST('''+CONVERT(NVARCHAR, #EndDate)+''' AS DATE)
) o
PIVOT(
MAX(Data)
FOR [Date] IN ('+ REPLACE(#columns, 'NVARCHAR(10)', '') +')
) AS pivot_table;
'
SET #sql = N'
DROP TABLE IF EXISTS #dateRangeTable
CREATE TABLE #dateRangeTable('+#columns+')
' +
#sql
+ N'
SELECT * FROM #dateRangeTable
DROP TABLE IF EXISTS #dateRangeTable
'
PRINT (#sql)
--EXECUTE sp_executesql #sql
I do have a (SQL Server) database table that contains an activity log of a device. The table consists of a DeviceId, a timestamp (DateTime) and a value field. The device writes its state changes to the DB whenever it turns on or off (value 1 resp. 0).
Now I wonder what would be the fastest way to get "blocks of activity" from that table. What do I mean by that? I'd like to get all time periods that are defined by a "1" value and its subsequent "0" value for a given DeviceId, so that I get a list of time ranges like this (for the active blocks, the inactive times would be between a 0 value followed by a 1):
DateTime ActiveStart, DateTime ActiveEnd
I currently ended up by first getting all the entries with EF as a list, then looping over them and comparing each entry to its predecessor in order to check if the device had been turned on and off.
That does work, but I do think that there must be a better and more performant way of doing this. What would be the best way to do it? Either a pure SQL query (from which I could build me a Stored Procedure) or a LINQ to SQL query will do.
Thanks for your thoughts and comments!
--------------------------
------ sample data -------
--------------------------
declare #t table
(
DeviceId int,
Timestamp DateTime,
Value bit
)
insert into #t values
(1, '2016-01-01', 1),
(1, '2016-01-05', 1),
(1, '2016-01-07', 1),
(1, '2016-01-08', 0),
(1, '2016-01-10', 0),
(1, '2016-01-21', 0),
(1, '2016-01-22', 1),
(1, '2016-01-25', 0),
(2, '2016-01-02', 1),
(2, '2016-01-04', 0),
(2, '2016-01-06', 1),
(2, '2016-01-08', 0),
(2, '2016-01-09', 1),
(2, '2016-01-15', 0),
(2, '2016-01-18', 1)
--------------------------
---------- query ---------
--------------------------
select
DeviceId,
gr,
ActiveStart = max(case when Value = 1 then Timestamp end),
ActiveEnd = max(case when Value = 0 then Timestamp end)
from
(
select
*,
gr = Value + row_number() over(partition by DeviceId order by Timestamp)
from #t
) t
group by DeviceId, gr
-- optional sorting by dates for easier results evaluation:
--order by DeviceId,
-- case when max(case when value = 1 then Timestamp end) is NULL
-- then max(case when value = 0 then Timestamp end)
-- else max(case when value = 1 then Timestamp end) end
You might try it like this:
CREATE TABLE #deviceLog (DeviceID INT, Activity DATETIME,Stat INT);
INSERT INTO #deviceLog VALUES
(1,{ts'2016-04-04 11:20:00'},1)
,(1,{ts'2016-04-04 11:30:00'},0)
,(1,{ts'2016-04-04 11:33:00'},1)
,(1,{ts'2016-04-04 11:38:00'},0)
,(2,{ts'2016-04-04 12:33:00'},1)
,(2,{ts'2016-04-04 12:40:00'},0)
,(3,{ts'2016-04-04 10:33:00'},1)
,(3,{ts'2016-04-04 11:38:00'},0);
WITH AllOn AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY DeviceID ORDER BY Activity) AS Inx,*
FROM #deviceLog
WHERE Stat=1
)
,AllOff AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY DeviceID ORDER BY Activity) AS Inx,*
FROM #deviceLog
WHERE Stat=0
)
SELECT AllOn.*,AllOff.Activity AS OffActivity
FROM AllOn
INNER JOIN AllOff ON AllOn.DeviceID=AllOff.DeviceID AND AllOn.Inx=AllOff.Inx;
DROP TABLE #deviceLog;
The result
Inx DeviceID Activity Stat OffActivity
1 1 2016-04-04 11:20:00.000 1 2016-04-04 11:30:00.000
2 1 2016-04-04 11:33:00.000 1 2016-04-04 11:38:00.000
1 2 2016-04-04 12:33:00.000 1 2016-04-04 12:40:00.000
1 3 2016-04-04 10:33:00.000 1 2016-04-04 11:38:00.000
SQL Server 2012+ supports cumulative sums. You can get blocks of activity by counting the number of *0*s cumulatively. A block of activity will have a constant value. Then you can aggregate (and filter) to get the periods of activity:
select deviceid, min(timestamp), max(timestamp)
from (select t.*,
sum(case when value = 0 then 1 else 0 end) over
(partition by deviceid order by timestamp) as grp
from t
) t
where value <> 0
group by deviceid, grp;
In earlier versions of SQL Server, you can do something similar using outer apply (and by other methods).
Please help with this SQL Table Query, I Have a Table Like
+---------------------------------------------+
| DateCol1 DateCol2 DateCol3 DateCol4 |
+---------------------------------------------+
| 2014-10-01 null null 2014_10_04 |
| 2014_10_02 2014_10_01 null 2014_10_04 |
| 2014_10_02 null 2014_10_01 2014_10_04 |
| 2014_10_02 null 2014_10_01 null |
+---------------------------------------------+
If the user is giving input like FromDate = '2014-10-01' and ToDate = '2014-10=05'
I have To Show Like
+------------------------------------------------------------------+
| ActivityDate DateCol1_Cnt DateCol2_Cnt DateCol3_Cnt DateCol4_Cnt |
+------------------------------------------------------------------+
| 2014-10-01 1 1 2 0 |
| 2014-10-02 3 0 0 0 |
| 2014-10-03 0 0 0 0 |
| 2014-10-04 0 0 0 3 |
+------------------------------------------------------------------+
what i have to show is number of activity in a Particluar date
what i did so far is
Select 'Date1_ACty' Activity ,dateColumn1 Activity_Date, Count(1) Activity_count
From MyTable where dateColumn1 between #FromDate and #ToDate
union all
Select 'Date2_ACty' Activity ,dateColumn2 Activity_Date, Count(1) Activity_count
From MyTable where dateColumn2 between #FromDate and #ToDate
union all .....
So Far For Remaining column
Please help me with good solution
Problem is I am Getting DateCol_Acty, DateCo2_Acty etc as column values not as column itself :(
Maybe doing anUNPIVOTfollowed by a newPIVOTwould work? It's not beautiful, but it seems to get the correct results I think, except for missing the empty dates, but that could be fixed by left joining a column with all dates in the range.
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table t (id int, DateCol1 date, DateCol2 date, DateCol3 date, DateCol4 date);
insert t values
(1, '2014-10-01', null, null, '2014-10-04'),
(2, '2014-10-02', '2014-10-01', null, '2014-10-04'),
(3, '2014-10-02', null, '2014-10-01', '2014-10-04'),
(4, '2014-10-02', null, '2014-10-01', null);
Query 1:
DECLARE #FromDate date = '2014-10-01', #ToDate date = '2014-10-05'
SELECT
ActivityDate,
DateCol1 AS DateCol1_Cnt,
DateCol2 AS DateCol2_Cnt,
DateCol3 AS DateCol3_Cnt,
DateCol4 AS DateCol4_Cnt
FROM (
SELECT d, Dates AS ActivityDate, dates as d2
FROM (SELECT DateCol1, DateCol2, DateCol3, DateCol4 FROM t) AS p
UNPIVOT (Dates FOR d IN (DateCol1, DateCol2, DateCol3, DateCol4)) AS unpvt
) derived
PIVOT (COUNT(d2) FOR d IN (DateCol1, DateCol2, DateCol3, DateCol4)) pvt
WHERE ActivityDate BETWEEN #FromDate AND #ToDate
Results:
| ACTIVITYDATE | DATECOL1_CNT | DATECOL2_CNT | DATECOL3_CNT | DATECOL4_CNT |
|--------------|--------------|--------------|--------------|--------------|
| 2014-10-01 | 1 | 1 | 2 | 0 |
| 2014-10-02 | 3 | 0 | 0 | 0 |
| 2014-10-04 | 0 | 0 | 0 | 3 |
Is something like this what you are looking for?
declare #table table(id int, date1 date, date2 date, date3 date, date4 date)
declare #min date = '2014-10-01', #max date = '2014-10-04';
insert into #table
values
(1, '2014-10-01', null, null, '2014-10-04'),
(2, '2014-10-02', '2014-10-01', null, '2014-10-04'),
(3, '2014-10-02', null, '2014-10-01', '2014-10-04'),
(4, '2014-10-02', null, '2014-10-01', null);
with date1
as (
select date1 date_,
count(*) count_
from #table
where date1 between #min
and #max
group by date1
),
date2
as (
select date2 date_,
count(*) count_
from #table
where date2 between #min
and #max
group by date2
),
date3
as (
select date3 date_,
count(*) count_
from #table
where date3 between #min
and #max
group by date3
),
date4
as (
select date4 date_,
count(*) count_
from #table
where date4 between #min
and #max
group by date4
),
dates
as (
select #min date_
union all
select dateadd(day, 1, date_)
from dates
where dateadd(day, 1, date_) <= #max
)
select d.date_,
ISNULL(d1.count_, 0) DateCol1_Cnt,
ISNULL(d2.count_, 0) DateCol2_Cnt,
ISNULL(d3.count_, 0) DateCol3_Cnt,
ISNULL(d4.count_, 0) DateCol4_Cnt
from dates d
left join date1 d1 on d1.date_ = d.date_
left join date2 d2 on d2.date_ = d.date_
left join date3 d3 on d3.date_ = d.date_
left join date4 d4 on d4.date_ = d.date_;
I have some dirty resource usage records in t_resourcetable which looks like this
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-03 00:00:00.000
1 2 2012-01-03 00:00:00.000 2012-01-04 00:00:00.000
1 2 2012-01-04 00:00:00.000 2012-01-04 16:23:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
I need those dirty rows to be merged in such way
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-04 16:23:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
This should get updated to the same table. I have more than 40k rows so cannot use a cursor. Please help me clean up this through more optimized sql statements.
Solution provided does not encounter the scenario like
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-03 00:00:00.000
1 2 2012-01-03 00:00:00.000 2012-01-04 00:00:00.000
1 2 2012-01-04 00:00:00.000 2012-01-04 16:23:00.000
1 2 2012-01-14 10:09:00.000 2012-01-15 00:00:00.000
1 2 2012-01-15 00:00:00.000 2012-01-16 00:00:00.000
1 2 2012-01-16 00:00:00.000 2012-01-16 03:00:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
I need those dirty rows to be merged in such way
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-04 16:23:00.000
1 2 2012-01-14 10:09:00.000 2012-01-16 03:00:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
Please assist me with this dirty data problem.
MERGE INTO t_resourcetable AS TARGET
USING (
SELECT
resNo, subres,
MIN(startdate) as startdate,
MAX(enddate) as enddate
FROM t_resourcetable
GROUP BY resNo, subres
) AS SOURCE
ON TARGET.resNo = SOURCE.resNo
AND TARGET.subres = SOURCE.subres
AND TARGET.startdate = SOURCE.startdate
-- Set enddate on the first record in the group
WHEN MATCHED THEN
UPDATE SET TARGET.enddate = SOURCE.enddate
-- Delete the remaining items
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
Edit: To respect the gaps in the intervals:
MERGE INTO t_resourcetable AS TARGET
USING (
-- Find the first item in each interval group
SELECT
resNo, subres, startdate,
row_number() over (partition by resNo, subres order by startdate) as rn
FROM t_resourcetable t1
WHERE NOT EXISTS (
-- No other intervals that intersect this from behind
SELECT NULL
FROM t_resourcetable t2
WHERE t2.resNo = t1.resNo
AND t2.subres = t1.subres
AND t2.startdate < t1.startdate
AND t2.enddate >= t1.startdate
)
) AS SOURCE_start
INNER JOIN (
-- Find the last item in each interval group
SELECT
resNo, subres, enddate,
row_number() over (partition by resNo, subres order by startdate) as rn
FROM t_resourcetable t1
WHERE NOT EXISTS (
-- No other intervals that intersect this from ahead
SELECT NULL
FROM t_resourcetable t2
WHERE t2.resNo = t1.resNo
AND t2.subres = t1.subres
AND t2.startdate <= t1.enddate
AND t2.enddate > t1.enddate
)
) AS SOURCE_end
ON SOURCE_start.resNo = SOURCE_end.resNo
AND SOURCE_start.subres = SOURCE_end.subres
AND SOURCE_start.rn = SOURCE_end.rn -- Join by row number
ON TARGET.resNo = SOURCE_start.resNo
AND TARGET.subres = SOURCE_start.subres
AND TARGET.startdate = SOURCE_start.startdate
-- Set enddate on the first record in the group
WHEN MATCHED THEN
UPDATE SET TARGET.enddate = SOURCE_end.enddate
-- Delete the remaining items
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
Result:
resNo subres startdate enddate
1 2 2012-01-02 22:03 2012-01-04 16:23
1 2 2012-01-14 10:09 2012-01-16 03:00
1 3 2012-01-06 16:23 2012-01-06 22:23
2 2 2012-01-04 05:23 2012-01-06 16:23
Edit: If there is any risk of concurrent edits on the target table, you might want to add the HOLDLOCK hint. This will prevent any primary key violation errors, and be slighty more resource effective. (Thanks Joey):
MERGE INTO t_resourcetable WITH (HOLDLOCK) AS TARGET
...
For SQL Server 2005 you could do something like this:
create table #temp
(
resNo int,
subres int,
enddate datetime,
primary key (resNo, subres)
)
-- Store the values you need for enddate in a temp table
insert into #temp
select resNo,
subres,
max(enddate) as enddate
from t_resourcetable
group by resNo, subres
-- Delete duplicates keeping the row with min startdate
delete T
from (
select row_number() over(partition by resNo, subres order by startdate) as rn
from t_resourcetable
) as T
where rn > 1
-- Set enddate where needed
update T set enddate = tmp.enddate
from t_resourcetable as T
inner join #temp as tmp
on T.resNo = tmp.resNo and
t.subres = tmp.subres
where T.enddate <> tmp.enddate
drop table #temp
I would create a temp table.
Now you can fill the temp table with the new and cleaned data.
I think, you must make a combined key with resNo and subres and select min startdate and max enddate.
At least, delete all data in the old table and fill it with data from temp table.
You could first store the result in a temporary table like this:
DECLARE #tmp TABLE
(
resNo INT,
subres INT,
startdate DATETIME,
enddate DATETIME
)
INSERT #tmp
SELECT resNo, subres, MIN(startdate), MAX(enddate)
FROM t_resourcetable
GROUP BY resNo, subres
To update t_resourcetable table you could do this:
DELETE t_resourcetable
INSERT t_resourcetable
SELECT *
FROM #tmp
And run all of this in a transaction.
Okay so here's my issue.
The user can go onto my site and retrieve 8 records at a time, then he/she is given the option to load more. These 8 records can be sorted by a param passed into the proc. Now when I get these 8 records on the front end, I have their ID's (hidden to the user though obviously), but their ID's are not in any specific order because the records are sorted by a variety of possible things.
When they click "Load More", I should be able to get the next 8 records from the database, sorted in the SAME fashion as the first 8 were.
For example, "Give me the top 8 records sorted by age". -> Click Load More -> Give me the next 8 oldest records without showing me the onces I just saw.
How can I call the proc and make sure none from the first result set are returned though? I only want to return 8 records at a time for efficiency reasons.
SELECT TOP 8
m.message,
m.votes,
(geography::Point(#latitude, #longitude, 4326).STDistance(m.point)) * 0.000621371192237334 as distance,
m.location,
datediff(hour,m.timestamp, getdate()) as age,
m.messageId,
ml.voted,
ml.flagged
FROM
tblMessages m
left join tblIPMessageLink ml on m.messageid = ml.messageid
WHERE
m.timestamp >= DATEADD(day, DATEDIFF(day, 0, #date), 0)
and
m.timestamp < DATEADD(day, DATEDIFF(day, 0, #date), 1)
ORDER BY
CASE WHEN #sort = 'votes1' THEN m.votes END DESC,
CASE WHEN #sort = 'votes2' THEN m.votes END ASC,
CASE WHEN #sort = 'age1' THEN datediff(hour,m.timestamp, getdate()) END ASC,
CASE WHEN #sort = 'age2' THEN datediff(hour,m.timestamp, getdate()) END DESC,
CASE WHEN #sort = 'distance1' THEN (geography::Point(#latitude, #longitude, 4326).STDistance(m.point)) * 0.000621371192237334 END ASC,
CASE WHEN #sort = 'distance2' THEN (geography::Point(#latitude, #longitude, 4326).STDistance(m.point)) * 0.000621371192237334 END DESC
END
That's my current query. How would I change it to work with paging?
use row_number
example
call 1
;WITH cte AS(SELECT *,row_number() OVER( ORDER BY name) AS rows FROM sysobjects)
SELECT * FROM cte WHERE ROWS BETWEEN 1 AND 8
ORDER BY rows
call 2
;WITH cte AS(SELECT *,row_number() OVER( ORDER BY name) AS rows FROM sysobjects)
SELECT * FROM cte WHERE ROWS BETWEEN 9 AND 16
ORDER BY rows
of course you want to use parameters instead of hardcoding the numbers, this way you can reuse the query, if the column can be sorted arbitrarily then you might need to use dynamic SQL
edit, here is what it should look like, you probably also want to return the max rownumber so that you know how many rows can be potentially returned
also you can make rows per page dynamic, in that case it would be something like
where Rows between #StartRow and (#StartRow + #RowsPerPage) -1
make sure to read Dynamic Search Conditions in T-SQL Version for SQL 2008 to see how you can optimize this to get plan reuse and a better plan in general
anyway, here is the proc, untested of course since I can't run it here
DECLARE #StartRow INT,#EndRow INT
--SELECT #StartRow =1, #EndRow = 8
;WITH cte AS (SELECT ROW_NUMBER() OVER (ORDER BY
CASE WHEN #sort = 'votes1' THEN m.votes END DESC,
CASE WHEN #sort = 'votes2' THEN m.votes END ASC,
CASE WHEN #sort = 'age1' THEN datediff(hour,m.timestamp, getdate()) END ASC,
CASE WHEN #sort = 'age2' THEN datediff(hour,m.timestamp, getdate()) END DESC,
CASE WHEN #sort = 'distance1' THEN (geography::Point(#latitude, #longitude, 4326).STDistance(m.point)) * 0.000621371192237334 END ASC,
CASE WHEN #sort = 'distance2' THEN (geography::Point(#latitude, #longitude, 4326).STDistance(m.point)) * 0.000621371192237334 END DESC
END) AS rows
m.message,
m.votes,
(geography::Point(#latitude, #longitude, 4326).STDistance(m.point)) * 0.000621371192237334 as distance,
m.location,
datediff(hour,m.timestamp, getdate()) as age,
m.messageId,
ml.voted,
ml.flagged
FROM
tblMessages m
left join tblIPMessageLink ml on m.messageid = ml.messageid
WHERE
m.timestamp >= DATEADD(day, DATEDIFF(day, 0, #date), 0)
and
m.timestamp < DATEADD(day, DATEDIFF(day, 0, #date), 1)
)
SELECT *
FROM cte WHERE ROWS BETWEEN #StartRow AND #EndRow
ORDER BY rows
David Hayden has a nice article on paging. You'll just need to keep track of the number of records and offset.
Also you'll still need to merge and resort the records on the client every time they load more
Here's the SP from that article
CREATE PROCEDURE dbo.ShowLog
#PageIndex INT,
#PageSize INT
AS
BEGIN
WITH LogEntries AS (
SELECT ROW_NUMBER() OVER (ORDER BY Date DESC)
AS Row, Date, Description
FROM LOG)
SELECT Date, Description
FROM LogEntries
WHERE Row between
(#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
END