multiple row column values into single row - sql

I have multiple row with different column values sharing the same id .
for e.g
col-A col-B col-C col-D Col-E
1 12 2012-12-01 1900-12-01 2:00:00 1900-12-01 3:30:00
2 12 2012-12-02 1900-12-01 3:00:00 1900-12-01 4:O0:00
I would like to get in single row preferably with separator ( * )
12 2012-12-01 2:00 - 3:30 * 2012-12-02 3:00 - 4:00
To avoid confusion - Edited the separator uses * instead of newline .

It is a pain to eliminate the [col-B] on the second line. The following formats the datetime's the way you seem to want them:
select [col-B],
(convert(varchar(19), [col-C] + [col-d], 121) + ' - ' +
right(convert(varchar(19), [col-E], 121), 8)
) col
from t

As noted by others, this sort of formatting is really not a database issue, but something for your application to handle. That said, and with thanks to Gordon Linoff for the conversion:
declare #TimeRanges as Table
( [col-A] Int Identity, [col-B] Int, [col-C] Date, [col-D] DateTime, [col-E] DateTime );
insert into #TimeRanges ( [col-B], [col-C], [col-D], [col-E] ) values
( 12, '20121201', '19001201 02:00:00', '19001201 03:30:00' ),
( 12, '20121202', '19001201 03:00:00', '19001201 04:00:00' ),
( 13, '20121219', '19001201 09:00:00', '19001201 17:00:00' );
select * from #TimeRanges;
select [col-A],
case when RN = 1 then Cast( [col-B] as VarChar(10) ) else '' end as [col-B], Range
from (
select [col-B], Row_Number() over ( partition by [col-B] order by [col-A] ) as RN,
( Convert( VarChar(19), [col-C] + [col-D], 121 ) + ' - ' +
Right( Convert( VarChar(19), [col-E], 121 ), 8 ) ) as Range
from #TimeRanges ) as ArbitraryPlaceholder
order by [col-A];

Related

SQL Server Difference of Aggregate Window

I want to fetch some data from an SQL Server table by creating windows of 15 mins from the start time and find the difference between the MAX and MIN of a certain column (value) in these windows.
The SQL Table stores some telemetry data captured every minute.
Are there any T-SQL functionalities that I can utilize to get such output without running a loop (Windowed Aggregation sort, etc.)?
I could implement this using a loop, but this is very slow as it would take data from every window and add it to a temp table. Below is a dumbed-down snippet of the code:
WHILE(#startTime <#endTimeInput)
BEGIN
INSERT INTO #energyDataTable
SELECT MIN(timeStamp) as startTime, MAX(timeStamp) as endTime, ROUND(MAX (value) - MIN (value),2) as value
FROM PLANT1.DATA_TABLE as ts
WHERE ts.unixTimestamp>=#startTime and ts.unixTimestamp<= #endTime
GROUP BY ts.logicalId
--Increment startTime and endTime to goto next window
SET #startTime = #endTime;
SET #endTime = #startTime + 15 * 60000; --converting 15 mins to millisecs
END
Timestamp
value
unixTime
2021-11-08 00:00:09.000
1527.6
1636329609000
2021-11-08 00:01:09.000
1528.1
1636329669000
2021-11-08 00:02:09.000
1528.6
1636329729000
....
....
....
2021-11-08 00:13:09.000
1534.5
1636330389000
2021-11-08 00:14:09.000
1535.3
1636330479000
2021-11-08 00:15:09.000
1535.5
1636330509000
2021-11-08 00:16:09.000
1536.0
1636330569000
2021-11-08 00:17:09.000
1528.6
1636330629000
....
....
....
2021-11-08 00:28:09.000
1542.5
1636331289000
2021-11-08 00:29:09.000
1543.3
1636331379000
Needs to be transformed to (I don't necessarily need end_time: Added for better understanding):
start_time
end_time
value
2021-11-08 00:00:09.000
2021-11-08 00:14:39.000
7.7
2021-11-08 00:15:09.000
2021-11-08 00:29:39.000
7.8
update
the solution is to get first and last row from 15 mins sections
it can be done like this:
create table #t
(
ts datetime2
,val dec(9,2)
)
insert into #t
values
('2021-11-08 00:00:09.000', 1527.6)
,('2021-11-08 00:01:09.000', 1528.1)
,('2021-11-08 00:02:09.000', 1528.6)
,('2021-11-08 00:13:09.000', 1534.5)
,('2021-11-08 00:14:09.000', 1535.3)
,('2021-11-08 00:15:09.000', 1535.5)
,('2021-11-08 00:16:09.000', 1536.0)
,('2021-11-08 00:17:09.000', 1528.6)
,('2021-11-08 00:28:09.000', 1542.5)
,('2021-11-08 00:29:09.000', 1543.3)
;with
cte_partitions as
(
select *
from
(
select *
,row_number() over(partition by M15.[partition] order by T.ts) as row_asc_id -- first row
,row_number() over(partition by M15.[partition] order by T.ts desc) as row_desc_id -- last row
from #t T
outer apply
(
-- 15 minutes partitions = hours + minutes / 15
select format(ts, 'yyyyMMddHH') + cast(datepart(minute, ts) / 15 as char(1)) as [partition]
) M15
) T
where T.row_asc_id = 1
or T.row_desc_id = 1
)
select PF.ts
,PL.val - PF.val
from cte_partitions PF
inner join cte_partitions PL
on PF.[partition] = pl.[partition]
where PF.row_asc_id = 1
and PL.row_desc_id = 1
Although it's fully inline with your example, it will work only if you have every minute without seconds and so on and so forth.
take a look, please
create table #t
(
ts datetime2
,val int
)
insert into #t
values
('2021-11-09T01:01:00.000Z', 100)
,('2021-11-09T01:02:00.000Z', 102)
,('2021-11-09T01:03:00.000Z', 103)
,('2021-11-09T01:04:00.000Z', 105)
,('2021-11-09T01:05:00.000Z', 107)
,('2021-11-09T01:06:00.000Z', 108)
,('2021-11-09T01:07:00.000Z', 120)
,('2021-11-09T01:08:00.000Z', 123)
,('2021-11-09T01:09:00.000Z', 128)
,('2021-11-09T01:10:00.000Z', 135)
select format(ts, 'hh:mm:ss') + ' - ' + format(next_ts, 'hh:mm:ss')
,cast(next_val - val as varchar(10)) + ' (' + cast(next_val as varchar(10)) + ' - ' + cast(val as varchar(10)) + ')'
from
(
select ts
,val
,lead(ts) over(order by ts) as next_ts
,lead(val) over(order by ts) as next_val
from
(
select *, (row_number() over(order by ts) - 1) % 4 as row_id
from #t
) SQ
where row_id = 0
) SQ
where next_ts is not null
To aggregate them in windows of 5 minutes?
Then this may be as simple as also grouping by the minutes divided by 12.
SELECT
CONCAT(CONVERT(VARCHAR, MIN(t.[Timestamp]), 108), ' - ', CONVERT(VARCHAR, MAX(t.[Timestamp]), 108)) AS Agg_Timestamp,
CONCAT(MAX(t.Energy_Value)-MIN(t.Energy_Value), ' (', MAX(t.Energy_Value), '-', MIN(t.Energy_Value), ')' ) AS Agg_Energy_Value
FROM yourtable t
GROUP BY CAST(t.[Timestamp] AS DATE),
DATEPART(hour, t.[Timestamp]),
ROUND((DATEPART(minute, t.[Timestamp])/12.0), 0)
Result:
Agg_Timestamp | Agg_Energy_Value
:------------------ | :---------------
01:01:00 - 01:05:00 | 7 (107-100)
01:06:00 - 01:10:00 | 27 (135-108)

distribute accumulated working hours through days

I have Date time when engine has started working and how long was it working. but sometimes it can work more than 24 Hours.
if it worked for 28 Hours on the starting date i will have record
Name started_working Finished working hours_worked
obj-00123 07/02/2018 13:30 08/02/2018 17:30 28
I need to to have record that will show that engine has worked for 10:30 in 07 and 17:30 in 08.
Name started_working Finished working hours_worked
obj-00123 07/02/2018 13:30 07/02/2018 00:00 10:30
obj-00123 07/02/2018 13:30 08/02/2018 17:30 17:30
or something like that. I don't have any idea how can i get this done. can you give me some clues. i dont ask for writing code if its not too easy.
thank you
This might do the trick for you
--Using CTE to show sample data
;WITH cteX( Name,started_working,Finished_working)
AS
(
SELECT
'obj-00123','07/02/2018 13:30','08/02/2018 17:30' UNION ALL
SELECT 'obj-00155','07/02/2018 15:00','07/02/2018 22:30'
)
SELECT
X.Name
, X.started_working
, X.Finished_working
, HoursWorked = CONVERT(VARCHAR(12), DATEADD(minute, DATEDIFF(minute, X.started_working, X.Finished_working), 0), 114)
FROM
(
SELECT
T1.Name
,T1.started_working
,Finished_working = DATEADD(SECOND,0,DATEADD(DAY, DATEDIFF(DAY,-1,T1.started_working),0)) -- Dummy finish time # Midnight
FROM
cteX T1
WHERE
DATEDIFF(DAY,T1.started_working,T1.Finished_working) <> 0 --Create a dummy finish time #Midnight when start and end not on same day
UNION ALL
SELECT
T2.Name
,started_working = CASE WHEN DATEDIFF(DAY,T2.started_working,T2.Finished_working) <> 0
THEN DATEADD(DAY, DATEDIFF(DAY, 0, T2.Finished_working), 0) --Start # Midnight
ELSE T2.started_working
END
,T2.Finished_working
FROM
cteX T2
) X
ORDER BY
X.Name, X.started_working
OUTPUT
Name started_working Finished_working HoursWorked
obj-00123 2018-07-02 13:30:00.000 2018-07-03 00:00:00.000 10:30:00:000
obj-00123 2018-08-02 00:00:00.000 2018-08-02 17:30:00.000 17:30:00:000
obj-00155 2018-07-02 15:00:00.000 2018-07-02 22:30:00.000 07:30:00:000
According to your sample data working hours may be more than several days. In this case you need to use tally table or recursive CTE. I have used recursive CTE since it's easier to handle result fields. Also there are two columns in result named started_working and started_working2. started_working is from your expected output, but I believe you need started_working2 column
declare #T as table (
Name varchar(100)
, started_working datetime
, finished_working datetime
--, hours_worked int
)
insert into #T
values
('obj-00123', '20180207 13:30', '20180208 17:30')
, ('obj-00123', '20180208 19:00', '20180209 05:00')
, ('obj-00123', '20180209 19:00', '20180209 22:00')
, ('obj-00123', '20180210 19:00', '20180213 22:00')
;with rcte as (
select
*, started_working2 = started_working
, next_date = cast(dateadd(dd, 1, started_working) as date), 1 step
from
#T
union all
select
Name, started_working, finished_working
, cast(next_date as datetime)
, dateadd(dd, 1, next_date), step + 1
from
rcte
where
next_date < finished_working
)
select
Name, started_working, started_working2, finished_working
, right(replace(str(diff / 60), ' ', 0), 2) + ':' + right(replace(str(diff % 60), ' ', 0), 2) hours_worked
from (
select
Name, started_working
, case
when step = 1 then started_working
else started_working2
end started_working2
, case
when step = max(step) over (partition by Name, started_working)
then finished_working else next_date
end finished_working
from
rcte
) t
cross apply (select datediff(mi, started_working2, finished_working) diff) ca
I'd approach the solution something like this:
WITH dynamic_twelths_of_hr_table(datetime2_value) AS
(
SELECT '2017-01-01'
UNION ALL
SELECT DATEADD(MINUTE, 5, datetime2_value)
FROM dynamic_twelths_of_hr_table
WHERE DATEADD(MINUTE, 5, datetime2_value) <= '2019-01-01'
)
,twelths_hr_table AS
(
SELECT
DATEADD(DAY, DATEDIFF(DAY, 0, datetime2_value), 0) AS date_value
,datetime2_value
FROM dynamic_twelths_of_hr_table
)
,modified_source_table AS
(
SELECT
name
,objectid
,engine_start
,ISNULL(engine_stop, GETDATE()) AS engine_stop
,IIF(engine_start IS NULL OR engine_stop IS NULL, 1, 0) AS is_still_running
FROM [YOUR_SOURCE_TABLE]
)
SELECT
name
,objectid
,is_still_running
,date_value
,(COUNT(datetime2_value)/12.0) AS hours_run_on_this_day
FROM
modified_source_table
LEFT JOIN
twelths_hr_table AS tht
ON (tht.datetime2_value BETWEEN engine_start AND engine_stop)
GROUP BY
name, objectid, is_still_running, date_value
ORDER BY
name, objectid, is_still_running, date_value
Note I haven't tested this code so please excuse any small syntax errors.
I've also baked in an assumption about the range of dates to be considered (these can be widened, or made dynamic based on when the query runs), and it has a 5 minute resolution (based on the fact that, at a glance, I could only see one value in the engine_stop column that didn't fall on a 5-minute threshold - so I assume sub-5-minute precision is not required).
Basically what it does is expand each engine row out into 5-minute windows (twelths of an hour), and then simply groups these by day and counts the number of windows per day during which the engine was running.
For currently-running engines, it will calculate how long it has run so far. I trust you can tweak the code to your exact requirements.
thank you to all. this worked perfectly. it needed slight polishing and recursion needed to be set to 0.
But creating view is a trouble with CTE.
create view mroobjectenginerowkinghoursdeclare as
declare #T as table (
Name nvarchar(100)
, OBJECTID varchar(50)
, started_working datetime
,STOPFROM datetime
,STARTDATE datetime
,STOPDATE datetime
,MODIFIEDDATETIME datetime
,START_STOP int
,STARTDESCRIPTION nvarchar(300)
,STOPDESCRIPTION nvarchar(300)
,wattage nvarchar (50)
,purpose nvarchar(300)
,location nvarchar(300)
,finished_working datetime
,oldDiff int
)
insert into #T
select
NAME
,OBJECTID
,STOPTO
,STOPFROM
,STARTDATE
,STOPDATE
,MODIFIEDDATETIME
,START_STOP
,STARTDESCRIPTION
,STOPDESCRIPTION
,wattage
,purpose
,location
,next_stopfrom
,diff
FROM [MicrosoftDynamicsAX].[dbo].[mroobjectengineworkinghours]
;with rcte as (
select
*, started_working2 = started_working
, next_date = cast(dateadd(dd, 1, started_working) as date), 1 step
from
#T
union all
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location, finished_working,oldDiff
, cast(next_date as datetime)
, dateadd(dd, 1, next_date), step + 1
from
rcte
where
next_date < finished_working
)
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location,oldDiff, started_working2, finished_working
, right(replace(str(diff / 60), ' ', 0), 2) + ':' + right(replace(str(diff % 60), ' ', 0), 2) hours_worked
from (
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location,oldDiff
, case
when step = 1 then started_working
else started_working2
end started_working2
, case
when step = max(step) over (partition by Name, started_working)
then finished_working else next_date
end finished_working
from
rcte
) t
cross apply (select datediff(mi, started_working2, finished_working) diff) ca
OPTION (MAXRECURSION 0);

How to add hh:mm:ss in Sql Query

table Column is :
time date
----- -------------
21:02:03 2/1/2018
22:05:13 2/1/2018
20:25:13 2/1/2018
I want to add only time 21:02:03 + 22:05:13 + 20:25:13 = 63:32:29 for a particular date
output as 63:32:29
table name is schedule
I need a query for Sum of total time.
Just try this,
SELECT
IFNULL(SEC_TO_TIME( SUM( TIME_TO_SEC( `time1` ) ) ),'00:00:00') AS Time
FROM schedule ;
You can convert the time to seconds and SUM up that values
SELECT DATEADD(ms, SUM(DATEDIFF(ms, 0, [timecolumn])), 0)
Or, just calculate the count down time in seconds, and convert it to your format
SELECT count_down_sec = SUM(DATEDIFF(SECOND, '0:00:00', [timecolumn]))
Full query here
SELECT CAST(tottime /60/60 AS nvarchar)
+':'+CAST(tottime /60%60 AS nvarchar)
+':'+ CAST(tottime % 60 AS nvarchar)
FROM (
SELECT SUM(DATEDIFF(SECOND, 0, [time])) AS tottime
FROM [schedule]
WHERE [date]='2/1/2018'
) AS T
Output:
count_down_sec
63:32:29
Apply the SUM function to the column you want to add time (assuming the data type of the column is TIME).
https://www.techonthenet.com/sql/sum.php
In MySQL, you can use the below sql,
SELECT date(timedatecol) date1,
sec_to_time(SUM(time_to_sec(time(timedatecol)))) timetotal
FROM schedule
GROUP BY date1;
Below are the details:
CREATE TABLE `schedule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`timedatecol` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
Inserted Data:
'1', '2018-02-02 06:19:05'
'2', '2018-02-02 06:19:06'
'3', '2018-02-02 06:19:07'
'4', '2018-02-03 06:19:08'
'5', '2018-02-03 06:19:08'
'6', '2018-02-04 06:19:08'
'7', '2018-02-04 06:19:09'
'8', '2018-02-02 06:19:09'
output for below sql
SELECT date(timedatecol) date1,
sec_to_time(SUM(time_to_sec(time(timedatecol)))) timetotal
FROM schedule
GROUP BY date1;
date1 timetotal
'2018-02-02', '25:16:27'
'2018-02-03', '12:38:16'
'2018-02-04', '12:38:17'
try This approach
DECLARE #T TABLE
(
MyTime TIME,
MyDate DATE DEFAULT('2/1/2018')
)
INSERT INTO #T(MyTime)
VALUES('01:02:03'),('01:05:13'),('03:25:59')
;WITH CTE
AS
(
SELECT
MyDate,
NewTime = CAST(MyDate AS DATETIME) + CAST(MyTime AS DATETIME)
FROM #T
),C2
AS
(
SELECT
MyDate,
MyMin = SUM(DATEDIFF(MINUTE, MyDate, NewTime))
FROM CTE
GROUP BY MyDate
)
SELECT
MyDate,
CAST(MyDate AS DATETIME)+CAST(RTRIM(MyMin/60) + ':' + RIGHT('0' + RTRIM(MyMin%60),2) AS DATETIME)
FROM C2
My Result
MyDate MyTime
---------- -----------------------
2018-02-01 2018-02-01 05:32:00.000
Please try like this
select dateadd(second,datediff(second,0,time1),time2) as Time3 from your_table
I think this will work

I still get a "Arithmetic overflow" when I filter on a cast datetime even if I use IsDate()

I have a data set that I need to filter a date that is stored as a string (changing the source column to a DateTime is NOT a option, this data is coming from a 3rd party source that I can not control).
One of the dates is malformed so if I do the following query I get one result
select ClientID, StartDate from boarding_appts where isdate(StartDate) = 0
ClientID StartDate
---------- --------------------
5160 5/6/210 12:00:00
If I do a cast(StartDate as datetime) I get "Arithmetic overflow error converting expression to data type datetime.", which I expected. and if I filter by IsDate alone everything works fine
select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age from boarding_appts where isdate(StartDate) = 1
ClientID StartDate age
---------- ----------------------- ----------
10207 2012-06-09 12:00:00.000 1
2843 2012-06-23 12:00:00.000 1
2843 2012-06-23 12:00:00.000 1
8292 2012-05-11 12:00:00.000 1
7935 2012-04-24 12:00:00.000 1
... (1000's of more rows) ...
Here is my problem:
I want to filter out records so only records a year old or newer show up, however no-matter how I attempt to perform the filter every one of these queries give me an arithmetic overflow error.
select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age
from boarding_appts
where isdate(StartDate) = 1
and datediff(year, cast(StartDate as dateTime), getdate()) < 1 --If you comment out this line it works fine
select *
from (select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age from boarding_appts where isdate(StartDate) = 1) as Filtered
where age < 1 --If you comment out this line it works fine
select *
from (select ClientID, cast(StartDate as dateTime) as StartDateCast from boarding_appts where isdate(StartDate) = 1) as Filtered
where datediff(year, StartDateCast, getdate()) < 1 --If you comment out this line it works fine
;with Filtered as
(select ClientID, cast(StartDate as dateTime) as StartDateCast from boarding_appts where isdate(StartDate) = 1)
select * from Filtered
where datediff(year, StartDateCast, getdate()) < 1 --If you comment out this line it works fine
;with Filtered as
(select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age from boarding_appts where isdate(StartDate) = 1)
select * from Filtered
where age < 1 --If you comment out this line it works fine
Here is a test set of data on SQL Fiddle for you to try out any solutions on. I am out of ideas on how to fix this. The ONLY solution I could think of that worked was selecting in to a temporary table first then selecting it out
select ClientID, StartDate, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age
into #t
from boarding_appts
where isdate(StartDate) = 1
select * from #t where age < 1 --Works.
SQL is a declarative language. The SQL optimizer is free to rearrange parts of the where clause as long as it retains its original meaning. So it can run datediff before isdate even if you specify isdate first. A subquery or CTE provides no sure relief, since that too can be rewritten.
The second suggestion from Aaron Bertrand in the comments:
WHERE CASE ISDATE(StartDate)
WHEN 1 THEN StartDate
ELSE '19000101'
END >= DATEADD(YEAR, -1, GETDATE());
Makes it unlikely that SQL Server will cast StartDate to a datetime when ISDATE = 0. That seems like the best solution.
I've marked this answer community wiki, if Aaran Bertrand posts an answer, accept that :)
SQL Server's DateTime has the domain 1753-01-01 00:00:00.000 ≤ x ≤ 9999-12-31 23:59:59.997. The year 210 CE is outside that domain. Hence the problem.
If you were using SQL Server 2008 or later, you could cast it to a DateTime2 datatype and you'd be golden (its domain is 0001-01-01 00:00:00.0000000 &le x ≤ 9999-12-31 23:59:59.9999999. But with SQL Server 2005, you're pretty much SOL.
This is really a problem of data cleaning. My inclination in cases like this is to load the 3rd party data into a staging table with each field as character strings. Then cleanse the data in place, replacing, for instance, invalid dates with NULL. Once cleansed, then do the necessary conversion work to move it to its final destination.
Another approach is to use pattern matching and do the date filtering without converting anything to datetime. ISO 8601 date/time values are character strings that have the laudable property of being (A) human-readable and (B) collating and comparing properly.
What I've done in the past is some analytical work to identify all the patterns in the datetime field by replacing decimal digits with a 'd' and then running group by to compute the counts of each different pattern found. Once you have that you can create some pattern tables to guide you. Something like these:
create table #datePattern
(
pattern varchar(64) not null primary key clustered ,
monPos int not null ,
monLen int not null ,
dayPos int not null ,
dayLen int not null ,
yearPos int not null ,
yearLen int not null ,
)
insert #datePattern values ( '[0-9]/[0-9]/[0-9] %' ,1,1,3,1,5,1)
insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9] %' ,1,1,3,1,5,2)
insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9][0-9] %' ,1,1,3,1,5,3)
insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9][0-9][0-9] %' ,1,1,3,1,5,4)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9] %' ,1,1,3,2,6,1)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9] %' ,1,1,3,2,6,2)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9][0-9] %' ,1,1,3,2,6,3)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] %' ,1,1,3,2,6,4)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9] %' ,1,2,4,1,6,1)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9] %' ,1,2,4,1,6,2)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9][0-9] %' ,1,2,4,1,6,3)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9][0-9][0-9] %' ,1,2,4,1,6,4)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9] %' ,1,2,4,2,7,1)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9] %' ,1,2,4,2,7,2)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9] %' ,1,2,4,2,7,3)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] %' ,1,2,4,2,7,4)
create table #timePattern
(
pattern varchar(64) not null primary key clustered ,
hhPos int not null ,
hhLen int not null ,
mmPos int not null ,
mmLen int not null ,
ssPos int not null ,
ssLen int not null ,
)
insert #timePattern values ( '[0-9]:[0-9]:[0-9]' ,1,1,3,1,5,1 )
insert #timePattern values ( '[0-9]:[0-9]:[0-9][0-9]' ,1,1,3,1,5,2 )
insert #timePattern values ( '[0-9]:[0-9][0-9]:[0-9]' ,1,1,3,2,6,1 )
insert #timePattern values ( '[0-9]:[0-9][0-9]:[0-9][0-9]' ,1,1,3,2,6,2 )
insert #timePattern values ( '[0-9][0-9]:[0-9]:[0-9]' ,1,2,4,1,6,1 )
insert #timePattern values ( '[0-9][0-9]:[0-9]:[0-9][0-9]' ,1,2,4,1,6,2 )
insert #timePattern values ( '[0-9][0-9]:[0-9][0-9]:[0-9]' ,1,2,4,2,7,1 )
insert #timePattern values ( '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]' ,1,2,4,2,7,2 )
You could combine these two tables into 1 but the number of combinations tends to explode things, though it greatly simplifies the query then.
Once you have that, the query is [fairly] easy, given that SQL is not exactly the world's best language choice for string processing:
---------------------------------------------------------------------
-- first, get your lower bound in ISO 8601 format yyyy-mm-dd hh:mm:ss
-- This will compare/collate properly
---------------------------------------------------------------------
declare #dtLowerBound varchar(255)
set #dtLowerBound = convert(varchar,dateadd(year,-1,current_timestamp),121)
-----------------------------------------------------------------
-- select rows with a start date more recent than the lower bound
-----------------------------------------------------------------
select isoDate = + right( '0000' + substring( t.startDate , coalesce(dt.yearPos,1) , coalesce(dt.YearLen,0) ) , 4 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.monPos,1) , coalesce(dt.MonLen,0) ) , 2 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.dayPos,1) , coalesce(dt.dayLen,0) ) , 2 )
+ case
when tm.pattern is not null then
' ' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.hhPos , tm.hhLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.mmPos , tm.mmLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.ssPos , tm.ssLen ) , 2 )
else ''
end
,*
from someTableWithBadData t
left join #datePattern dt on t.startDate like dt.pattern
left join #timePattern tm on ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) )
like tm.pattern
where #lowBound <= + right( '0000' + substring( t.startDate , coalesce(dt.yearPos,1) , coalesce(dt.YearLen,0) ) , 4 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.monPos,1) , coalesce(dt.MonLen,0) ) , 2 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.dayPos,1) , coalesce(dt.dayLen,0) ) , 2 )
+ case
when tm.pattern is not null then
' ' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.hhPos , tm.hhLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.mmPos , tm.mmLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.ssPos , tm.ssLen ) , 2 )
else ''
end
Like I said, SQL not the best choice for munging strings.
This should get you ... 90% there. Experience tells me that you'll still find more bad dates: months less than 1 or greater than 12 , days less than 1 or greater than 31, or days out of range for that month (nothing like February 31st to make the computer whine), etc. Old cobol programs in particular, loved to use a field of all 9s to indicate missing data, for instance (though that is an easy case to deal with).
My preferred technique is to write a perl script to scrub the data and bulk load it into SQL Server, using perl's BCP facilities. That's exactly the sort of problem space perl is designed for.

Get every hour for a time range

So what I am trying to is generate all the hours that are inside a specific time range.
So given the range 11 AM to 2:00 PM, I would get:
11:00 AM
12:00 PM
1:00 PM
2:00 PM
I am trying to avoid having to store every specific hour a store might be open and just store the range (I need to compare the hours against other times)
Thanks
No loops, recursive CTEs or numbers table required.
DECLARE
#start TIME(0) = '11:00 AM',
#end TIME(0) = '2:00 PM';
WITH x(n) AS
(
SELECT TOP (DATEDIFF(HOUR, #start, #end) + 1)
rn = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_columns ORDER BY [object_id]
)
SELECT t = DATEADD(HOUR, n-1, #start) FROM x ORDER BY t;
You could use a recursive CTE. This would generate the hours between 11 and 14:
;with Hours as
(
select 11 as hr
union all
select hr + 1
from Hours
where hr < 14
)
select *
from Hours
Live example at SQL Fiddle.
If you have a numbers table (click the link to create one if you don't)...
create table test(
startTime time
, endTime time
)
insert into test
select '11:00', '14:00'
select
dateadd(hh, n.n, t.startTime) as times
from test t
inner join Numbers n
-- assuming your numbers start at 1 rather than 0
on n.n-1 <= datediff(hh, t.startTime, t.endTime)
If this is specialized, you can create an hours table with just 24 values.
create table HoursInADay(
[hours] time not null
, constraint PK_HoursInADay primary key ([hours])
)
-- insert
insert into HoursInADay select '1:00'
insert into HoursInADay select '2:00'
insert into HoursInADay select '3:00'
insert into HoursInADay select '4:00'
insert into HoursInADay select '5:00'
insert into HoursInADay select '6:00'
insert into HoursInADay select '7:00'
...
select
h.[hours]
from test t
inner join HoursInADay h
on h.[hours] between t.startTime and t.endTime
The easiest way I can think of to do this is to have only 1 permanent table with a list of all hours; 24 entries total.
Create table dbo.Hours (Hourly_Time Time NOT NULL)
Insert into dbo.Hours ...
Then, given times A & B:
select * from dbo.Hours where Hourly_Time<=A and Hourly_Time>=B
#Andomar Thanks a lot, you helped me, there is my add above your code.
*----------------------------
create view vw_hoursalot as
with Hours as
(
select DATEADD(
dd, 0, DATEDIFF(
dd, 0, DATEADD (
year , -5 , getDate()
)
)
) as dtHr
union all
select DATEADD (minute , 30 , dtHr )
from Hours
where dtHr < DATEADD(
dd, 0, DATEDIFF(
dd, 0, DATEADD (
year , +5 , getDate()
)
)
)
)
select * from Hours
----------------------------
select * from vw_hoursalot option (maxrecursion 0)
----------------------------*