I have a table containing a single date column say special date (stored as yyyymmdd )
How do i create a date range among the small subset of rows?
Example table contains a date column with following values 01-jan-2010, 01-feb-2010, 01-mar-2010
I need
01-jan-2010 - 01-feb-2010
01-feb-2010 - 01-mar-2010
....
....
Please help.
You can try something like
DECLARE #Table TABLE(
DateVal DATETIME
)
INSERT INTO #Table SELECT '01 Jan 2010'
INSERT INTO #Table SELECT '01 Feb 2010'
INSERT INTO #Table SELECT '01 Mar 2010'
;WITH DateVals AS (
SELECT *,
ROW_NUMBER() OVER(ORDER BY DateVal) RowID
FROM #Table
)
SELECT s.DateVal StartDate,
e.DateVal EndDate
FROM DateVals s INNER JOIN
DateVals e ON s.RowID + 1 = e.RowID
Output
StartDate EndDate
2010-01-01 00:00:00.000 2010-02-01 00:00:00.000
2010-02-01 00:00:00.000 2010-03-01 00:00:00.000
You can avoid the CTE by using
SELECT s.DateVal StartDate,
MIN(e.DateVal) EndDate
FROM #Table s LEFT JOIN
#Table e ON s.DateVal < e.DateVal
GROUP BY s.DateVal
HAVING MIN(e.DateVal) IS NOT NULL
But I do not see why you wish to do so.
Related
Given the following data:
Effective Date
--------------
2014-01-01
2015-01-01
2016-01-01
2017-01-01
2018-01-01
I want to order by date relative to (for example) 2016-06-01, where all descending values below 2016-06-01 come before all ascending values above 2016-06-01.
Relative to date: 2016-06-01 the desired ordering is
Effective Date
--------------
2016-01-01
2015-01-01
2014-01-01
2017-01-01
2018-01-01
The best way I have come up with to achieve the desired ordering is:
CREATE TABLE #Dates
(
EffectiveDate DATETIME2
);
DECLARE #CurrentDate DATETIME2 = '2016-06-01';
DECLARE #MinDate DATETIME2 = '0001-01-01';
INSERT INTO #Dates (EffectiveDate) VALUES ('2014-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2015-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2016-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2017-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2018-01-01');
SELECT
*
FROM
#Dates
ORDER BY
CASE
WHEN #Dates.EffectiveDate < #CurrentDate
THEN DATEDIFF(DAY, #Dates.EffectiveDate, #CurrentDate)
ELSE
DATEDIFF(DAY, #CurrentDate, #Dates.EffectiveDate) - DATEDIFF(DAY, #CurrentDate, #MinDate)
END;
DROP TABLE #Dates;
Is there a better way to achieve the desired ordering?
If you have 2012 and up.
SELECT
*
FROM
#Dates
ORDER BY
IIF(#Dates.EffectiveDate <= #CurrentDate, 0,1) ASC
, ABS(DATEDIFF(day,#Dates.EffectiveDate, #CurrentDate)) ASC;
If you have 2008:
SELECT
*
FROM
#Dates
ORDER BY
CASE
WHEN #Dates.EffectiveDate <= #CurrentDate THEN 0
ELSE 1 END ASC
, ABS(DATEDIFF(day,#Dates.EffectiveDate, #CurrentDate)) ASC;
This is a little bit of a different approach. Let me know if this will suit your needs. I used a table variable rather than temp table.
DECLARE #Dates TABLE
(
EffectiveDate DATETIME2
);
DECLARE #CurrentDate DATETIME2 = '2016-06-01';
INSERT INTO #Dates (EffectiveDate) VALUES ('2014-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2015-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2016-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2017-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2018-01-01');
SELECT *
FROM (SELECT TOP 1000000 *
FROM #Dates t1
WHERE EffectiveDate <= #CurrentDate
ORDER BY EffectiveDate DESC
UNION ALL
SELECT TOP 1000000 *
FROM #Dates t2
WHERE EffectiveDate NOT IN (
SELECT *
FROM #Dates
WHERE EffectiveDate <= #CurrentDate
)
ORDER BY EffectiveDate
) t
******* Edit *******
As mentioned by HABO, the above result set wouldn't necessarily have a guaranteed order. The below edit should take care of that.
SELECT *
FROM (SELECT *, ROW_NUMBER() OVER(ORDER BY EffectiveDate DESC) Sort
FROM #Dates t1
WHERE EffectiveDate <= #CurrentDate
UNION ALL
SELECT *
,(SELECT MAX(Sort)
FROM (SELECT *, ROW_NUMBER() OVER(ORDER BY EffectiveDate DESC) Sort
FROM #Dates t1
WHERE EffectiveDate <= #CurrentDate
) t2
)
+ ROW_NUMBER() OVER(ORDER BY EffectiveDate ASC) Sort
FROM #Dates t3
WHERE EffectiveDate NOT IN (SELECT *
FROM #Dates
WHERE EffectiveDate <= #CurrentDate)
) t
ORDER BY t.Sort
For this specific situation the solution is straight forward; split less than current and greater than current dates in two separate columns and sort accordingly:
WITH CTE AS (
SELECT
EffectiveDate,
CASE WHEN EffectiveDate <= #CurrentDate THEN EffectiveDate END AS DL,
CASE WHEN EffectiveDate > #CurrentDate THEN EffectiveDate END AS DG
FROM #Dates
)
SELECT * FROM CTE
ORDER BY DL DESC, DG
Result:
EffectiveDate | DL | DG
====================+=====================+====================
2016-01-01 00:00:00 | 2016-01-01 00:00:00 | NULL
2015-01-01 00:00:00 | 2015-01-01 00:00:00 | NULL
2014-01-01 00:00:00 | 2014-01-01 00:00:00 | NULL
2017-01-01 00:00:00 | NULL | 2017-01-01 00:00:00
2018-01-01 00:00:00 | NULL | 2018-01-01 00:00:00
Note that it is possible to write the query without using CTEs.
Try this...
IF OBJECT_ID(N'tempdb..#Dates') IS NOT NULL
DROP TABLE #Dates
GO
CREATE TABLE #Dates
(
EffectiveDate DATETIME2
);
DECLARE #CurrentDate DATETIME2 = '2016-06-01';
DECLARE #MinDate DATETIME2 = '0001-01-01';
INSERT INTO #Dates (EffectiveDate) VALUES ('2014-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2015-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2016-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2017-01-01');
INSERT INTO #Dates (EffectiveDate) VALUES ('2018-01-01');
SELECT
EffectiveDate
FROM
(
SELECT
CASE
WHEN EffectiveDate <= #CurrentDate THEN 1
ELSE 2
END GroupId,
CASE
WHEN EffectiveDate <= #CurrentDate THEN EffectiveDate
ELSE NULL
END Group1,
CASE
WHEN EffectiveDate <= #CurrentDate THEN NULL
ELSE EffectiveDate
END Group2,
EffectiveDate
FROM
#Dates
)A
ORDER BY
GroupId,
Group1 DESC,
Group2 ASC
I am having trouble compiling a query than can do the following:
I have a table which has a startDate and endDate [tblPayments]
I have a column which stores a specific paymentDay [tblPayments]
Data
paymentID startDate endDate paymentDay
1 2016-01-01 2016-12-31 25
2 2015-06-01 2016-06-30 16
I am trying to generate a SELECT query which will split this specific table into separate lines based on the amount of months between these two dates, and set the paymentDay as the day for these queries
Example Output
paymentID expectedDate
1 2016-01-25
1 2016-02-25
1 2016-03-25
1 2016-04-25
1 2016-05-25
1 2016-06-25
1 2016-07-25
1 2016-08-25
1 2016-09-25
1 2016-10-25
1 2016-11-25
1 2016-12-25
2 2015-06-16
2 2015-07-16
2 2015-08-16
2 2015-09-16
2 2015-10-16
2 2015-11-16
2 2015-12-16
2 2016-01-16
2 2016-02-16
2 2016-03-16
2 2016-04-16
2 2016-05-16
I have found a query which will select the months between these dates but its adapting it to my table above, and multiple startDates and endDates I am struggling with
spliting the months
declare #start DATE = '2015-01-01'
declare #end DATE = '2015-12-31'
;with months (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(MM,1,date)
from months
where DATEADD(MM,1,date)<=#end
)
select Datename(MM,date) from months
This query is limited to just one startDate and endDate, so I haven't expanded it to change the DAY of the date.
Use a date table and a simple inner join
DECLARE #tblPayments table (paymentID int identity(1,1), startDate date, endDate date, paymentDay int)
INSERT #tblPayments VALUES
('2016-01-01', '2016-12-31', 25),
('2015-06-01', '2016-06-30', 16)
;WITH dates AS -- Build date within the range of startDate and endDate
(
SELECT MIN(startDate) AS Value, MAX(endDate) AS MaxDate FROM #tblPayments
UNION ALL
SELECT DATEADD(DAY, 1, Value), MaxDate
FROM dates WHERE DATEADD(DAY, 1, Value) <= MaxDate
)
SELECT pay.paymentID, dates.Value AS expectedDate
FROM
#tblPayments pay
INNER JOIN dates ON
dates.Value BETWEEN pay.startDate AND pay.endDate
AND DAY(dates.Value) = paymentDay
OPTION (maxrecursion 0)
I would create an in memory calendar table and then perform a simple query by joining to that:
-- Create a table with all the dates between the min and max dates in the
-- data table
DECLARE #Calendar TABLE
(
[CalendarDate] DATETIME
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT #StartDate = MIN(startdate), #EndDate = MAX(enddate) FROM YourDataTable
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Calendar (CalendarDate)
SELECT #StartDate
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
-- Join to return only dates between the start and end date that match the Payment Day
SELECT D.PaymentId, C.CalendarDate FROM YourDataTable D
INNER JOIN #Calendar C ON C.CalendarDate BETWEEN D.StartDate AND D.EndDate
AND DATEPART(day, C.CalendarDate) = D.PaymentDay
Morning
I am trying to return the distinct dates of an outcome by a unique identifer.
For example:
ID|Date
1 | 2011-10-01 23:57:59
1 | 2011-10-01 23:58:59
1 | 2010-10-01 23:59:59
2 | 2010-09-01 23:59:59
2 | 2010-09-01 23:58:29
3 | 2010-09-01 23:58:39
3 | 2010-10-01 23:59:14
3 | 2010-10-01 23:59:36
The times are not important just the dates. So for example on ID 1 I can't do a distinct on the ID as that would return only one of my dates. So I would want to return:
1|2011-10-01
1|2010-10-01
I Have tried the following query:
Drop Table #Temp
select Distinct DateAdd(dd, DateDiff(DD,0, Date),0) as DateOnly
,ID
Into #Temp
From Table
Select Distinct (Date)
,ID
From #Temp
I am getting the following results however:
ID|Date
1 | 2011-10-01 00:00:00
1 | 2011-10-01 00:00:00
1 | 2010-10-01 00:00:00
I'm new to SQL so apologies I may have made a glaring mistake. I have got so far by searching through the previously asked questions.
As always any help and pointers is greatly appreciated.
You can use the T-SQL convert function to extract the Date.
Try
CONVERT(char(10), GetDate(),126)
so, in your case, do
Drop Table #Temp
select Distinct CONVERT(char(10), DateAdd(dd, DateDiff(DD,0, Date),0), 126) as DateOnly
,ID
Into #Temp
From Table
Select Distinct (Date)
,ID
From #Temp
further informations: Getting the Date Portion of a SQL Server Datetime field
hope this helps!
If you are using Sql Server 2008 - you can cast DateTime column to a built in Date type , otherwise to get rid of time you should cast to VARCHAR() only day/month/year parts and then convert back to datetime so time part would be zeroed:
declare #dates table(id int, dt datetime)
INSERT INTO #dates VALUES(1, '2011-10-01 23:57:49')
INSERT INTO #dates VALUES(2, '2011-10-02 23:57:59')
INSERT INTO #dates VALUES(2, '2011-10-02 23:57:39')
SELECT stripped.id, stripped.dateOnly
FROM
(
-- this will return dates with zeroed time part 2011-10-01 00:00:00.000
SELECT id,
CONVERT(datetime,
CAST(YEAR(dt) as VARCHAR(4)) + '-' +
CAST(MONTH(dt) AS VARCHAR(2)) + '-' +
CAST(DAY(dt) AS VARCHAR(2))) as dateOnly
FROM #dates
) stripped
GROUP BY stripped.id, stripped.dateOnly
I want to update a Table with auto incrementing dates in date column in Sql server 2005.
To label newly inserted rows, you could combine an identity column with a calculated field. For example:
declare #t table
(
dayNumber int identity,
Date as dateadd(day, dayNumber-1, '1970-01-01')
)
insert into #t default values
insert into #t default values
insert into #t default values
select * from #t
This prints:
dayNumber Date
1 1970-01-01 00:00:00.000
2 1970-01-02 00:00:00.000
3 1970-01-03 00:00:00.000
To update columns in an existing table with increasing dates, use row_number, like:
declare #t2 table (id int identity, name varchar(50), DateColumn datetime)
insert #t2 (name) values ('Maggie'), ('Tom'), ('Phillip'), ('Stephen')
update t2
set DateColumn = DATEADD(day, rn-1, '1970-01-01')
from #t2 t2
join (
select ROW_NUMBER() over (order by id) rn
, id
from #t2
) t2_numbered
on t2_numbered.id = t2.id
select * from #t2
This prints:
id name DateColumn
1 Maggie 1970-01-01 00:00:00.000
2 Tom 1970-01-02 00:00:00.000
3 Phillip 1970-01-03 00:00:00.000
4 Stephen 1970-01-04 00:00:00.000
I have a tricky problem that has had me scratching my head for a little while. I have some data that represents the delivery of "widgets" over a variable number of days, broken down into half hourly slots.
For example (apologies for formatting - haven't quite got to grips with it):
Date Time NoOfUnits
01-Mar-2010 00:00 0
01-Mar-2010 00:30 0
01-Mar-2010 01:00 0
.... (following half hour intervals have NoOfUnits = 0)
01-Mar-2010 23:00 10
01-Mar-2010 23:30 10
02-Mar-2010 00:00 10
.... (following half hour intervals have NoOfUnits = 1)
02-Mar-2010 07:00 10
02-Mar-2010 07:30 0
.... (following half hour intervals have NoOfUnits = 0)
02-Mar-2010 23:30 0
I need to generate a query that will allow me to group this data into all the distinct blocks where I am delivering a unit. In the above example I need to identify only 1 block - 23:00 to 07:00, and the sum of units for that block (160). The required result would therefore be StartTime, EndTime, TotalNoOfUnits.
However, the complexity comes when we have different patterns of delivery - maybe we have a day where we deliver units for 24 hours.
I need to be able to query data in the format above and identify all the unique StartTime, EndTime and TotalNoOfUnits combinations where NoOfUnits <> 0.
Apologies again for formatting and a slightly rambling question. Please ask any questions you need for me to clarify things.
EDIT: Just to be clear, the data will always run from 00:00 to 23:30 for each day of delivery, and each half hour slot will always be present. It is only the number of days and unit per half hour slot that may vary for any given data set.
EDIT2: Below is a script that will populate a table with 2 days worth of schedule data. The schedule is the same for both days. The result I would expect to see, based on my requirements, would be 13:00, 00:00, 230. As you will see from the below my SQL skills are not great hence the head scratching!
declare #DayCount int
declare #HalfHourCount int
declare #HH int
declare #CurrentDate datetime
declare #BaseDate datetime
declare #NoOfUnits int
set #HalfHourCount = 48
set #DayCount = 4
set #BaseDate = '1 Jan 1900'
create table #Schedule
(
Date datetime
, Time datetime
, NoOfUnits int
)
while #DayCount > 0
begin
set #CurrentDate = dateadd(dd, #DayCount * -1, CONVERT(VARCHAR(10),GETDATE(),111))
set #HH = #HalfHourCount
while #HH > 0
begin
if #HH > 24
set #NoOfUnits = 10
else
begin
if #DayCount = 4 and #HH < 10
set #NoOfUnits = 10
else
set #NoOfUnits = 0
end
insert into #Schedule(Date, Time, NoOfUnits)
values (#CurrentDate, dateadd(mi, #HH / 2.0 * 60, #BaseDate), #NoOfUnits)
select #HH = #HH - 1
end
set #DayCount = #DayCount - 1
end
Expected result (although test data should start from 00:00 and go to 23:00 instead of 00:30 to 00:00):
StartTime TotalUnits EndDate
----------------------- ----------- -----------------------
1900-01-01 00:30:00.000 90 1900-01-01 04:30:00.000
1900-01-01 12:30:00.000 960 1900-01-02 00:00:00.000
This should work, based on the data you have provided. It can almost certainly be simplified:
;WITH dateCTE
AS
(
SELECT *
,date + [time] dt
FROM #Schedule
)
,seqCTE
AS
(
SELECT NoOfUnits
,dt
,ROW_NUMBER() OVER (ORDER BY dt) AS rn
FROM dateCTE
)
,recCTE
AS
(
SELECT NoOfUnits
,dt
,1 AS SEQUENCE
,rn
FROM seqCTE
WHERE rn = 1
UNION ALL
SELECT s.NoOfUnits
,s.dt
,CASE WHEN (s.NoOfUnits > 0
AND r.NoOfUnits > 0
)
OR (s.NoOfUnits = 0
AND r.NoOfUnits = 0
)
THEN r.SEQUENCE
ELSE r.SEQUENCE + 1
END
,s.rn
FROM recCTE AS r
JOIN seqCTE AS s
ON s.rn = r.rn + 1
)
,summaryCTE
AS
(
SELECT RIGHT(CONVERT(varchar(23),MIN(dt),120),8) AS startTime
,RIGHT(CONVERT(varchar(23),MAX(dt),120),8) AS endTime
,SUM(NoOfUnits) AS NoOfUnits
FROM recCTE
GROUP BY SEQUENCE
HAVING SUM(NoOfUnits) != 0
)
SELECT startTime
,endTime
,SUM(NoOfUnits)
FROM summaryCTE
group by startTime
,endTime
OPTION (MAXRECURSION 0)
If I understand you correctly, have a look at this example. Please let me know if you have more questions.
DECLARE #Table TABLE(
DateVal DATETIME,
NoOfUnits INT
)
INSERT INTO #Table SELECT '01 Mar 2010 00:30', 0
INSERT INTO #Table SELECT '01 Mar 2010 23:00', 10
INSERT INTO #Table SELECT '01 Mar 2010 23:30', 10
INSERT INTO #Table SELECT '02 Mar 2010 00:30', 10
INSERT INTO #Table SELECT '02 Mar 2010 01:00', 0
INSERT INTO #Table SELECT '02 Mar 2010 02:30', 20
INSERT INTO #Table SELECT '02 Mar 2010 03:30', 30
INSERT INTO #Table SELECT '02 Mar 2010 04:30', 40
INSERT INTO #Table SELECT '02 Mar 2010 05:00', 0
SELECT *
FROM #Table
;WITH DateValues AS(
SELECT t.DateVal,
t.NoOfUnits,
MIN(tNext.DateVal) MinDate
FROM #Table t LEFT JOIN
#Table tNext ON t.DateVal < tNext.DateVal
AND tNext.NoOfUnits = 0
WHERE t.NoOfUnits != 0
GROUP BY t.DateVal,
t.NoOfUnits
)
, DateRanges AS(
SELECT DateVal StartDate,
NoOfUnits,
ISNULL((SELECT MAX(DateVal) FROM #Table WHERE DateVal < MinDate), (SELECT MAX(DateVal) FROM #Table)) EndDateEndDate
FROM DateValues
)
SELECT MIN(StartDate) StartDate,
SUM(NoOfUnits) TotalUnits,
EndDate
FROM DateRanges
GROUP BY EndDate
Ouput
StartDate TotalUnits EndDate
----------------------- ----------- -----------------------
2010-03-01 23:00:00.000 30 2010-03-02 00:30:00.000
2010-03-02 02:30:00.000 90 2010-03-02 04:30:00.000