Convert String into array in sql/ power BI /excel - sql-server

I have a scenario to convert a field of data type varachar 03/28 8:00-7:30 03/29 8:00-7:30 03/30 8:00-6:00 04/02 8:00-7:30 04/03 8:00-7:30 04/04 8:00-7:30 04/05 8:00-7:30 04/06 8:00-7:30 04/07 8:00-6:00 04/09 8:00-7:30 04/10 8:00-7:30 04/11 8:00-7:30 04/12 8:00-7:30 04/13 8:00-7:30 04/14 8:00-3:00
into an array using sql or power BI or excel using power query?
Data now looks like 
Emp_ID  Emp_name date_hrs_operation
1             ABD           "03/28 8:00-7:30 03/29 8:00-7:30 03/30 8:00-6:00 04/02 8:00-7:30 04/03 8:00-7:30 04/04 8:00-7:30 04/05 8:00-7:30 04/06 8:00-7:30 04/07 8:00-6:00 04/09 8:00-7:30 04/10 8:00-7:30 04/11 8:00-7:30
04/12 8:00-7:30 04/13 8:00-7:30 04/14 8:00-3:00" 
I need to be in this format, 
Emp_ID  Emp_Name  date_hrs_operation
1             ABD             03/28 8:00-7:30
1             ABD             03/29 8:00-7:30
1            ABD              03/30 8:00-6:00
1            ABD              04/02 8:00-7:30
etc.. By any method can I achieve this?
Thanks in advance!!

Im powerquery, if you have that long string in a column, the below code will split it in the desired method
Basically ... (a) split by space (b) add index (c) modify index to show 0/1 every other row using Number.Mod (d) filter for 0 and combine that column with a filter for 1 (e) remove extra columns (f) rename [not done here]
sample code to transform blue on right to green on left:
you can click select the two columns and merge them if you want
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Split Column by Delimiter" = Table.ExpandListColumn(Table.TransformColumns(Source, {{"Column3", Splitter.SplitTextByDelimiter(" ", QuoteStyle.Csv), let itemType = (type nullable text) meta [Serialized.Text = true] in type {itemType}}}), "Column3"),
#"Added Index" = Table.AddIndexColumn(#"Split Column by Delimiter", "Index", 0, 1, Int64.Type),
#"Index modulo" = Table.TransformColumns(#"Added Index",{{"Index", each Number.Mod(_,2), Int64.Type}}),
Part1= Table.ToColumns(Table.SelectRows(#"Index modulo", each ([Index] = 0))),
Part2= Table.ToColumns(Table.SelectRows(#"Index modulo", each ([Index] = 1))),
combined=Table.FromColumns(Part1&Part2),
#"Removed Columns" = Table.RemoveColumns(combined,{"Column4", "Column5", "Column6", "Column8"})
in #"Removed Columns"
optional merging of the two columns, use this ending instead:
#"Removed Columns" = Table.RemoveColumns(combined,{"Column4", "Column5", "Column6", "Column8"}),
#"Merged Columns" = Table.CombineColumns(#"Removed Columns",{"Column3", "Column7"},Combiner.CombineTextByDelimiter(" ", QuoteStyle.None),"Merged")
in #"Merged Columns"
For a different, single step solution, right click ... split column .. by number of positions. Depends on if you can have the time data always have format hh:mm instead of h:mm before noon and hh:mm after noon. For the sample data, which only has h:mm, using 16 characters works in a single step

One more method by using tokenization via XML/XQuery and mod operator.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Emp_ID INT, Emp_name VARCHAR(20), date_hrs_operation VARCHAR(1024));
INSERT INTO #tbl (Emp_ID, Emp_name, date_hrs_operation) VALUES
(1, 'ABD', '03/28 8:00-7:30 03/29 8:00-7:30 03/30 8:00-6:00 04/02 8:00-7:30 04/03 8:00-7:30 04/04 8:00-7:30 04/05 8:00-7:30 04/06 8:00-7:30 04/07 8:00-6:00 04/09 8:00-7:30 04/10 8:00-7:30 04/11 8:00-7:30 04/12 8:00-7:30 04/13 8:00-7:30 04/14 8:00-3:00');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = SPACE(1);
SELECT Emp_ID, Emp_name
, date_hrs_operation = t.c.value('(./text())[1]', 'VARCHAR(30)') + ' ' +
t.c.value('(/root/*[sql:column("seq.pos")]/text())[1]', 'VARCHAR(30)')
--, seq.pos -- just to see
FROM #tbl
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(date_hrs_operation, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
CROSS APPLY t1.c.nodes('/root/*[position() mod 2 = 1]') AS t(c)
CROSS APPLY (SELECT t.c.value('let $n := . return count(/root/*[. << $n[1]]) + 2','INT') AS pos
) AS seq;
Output
+--------+----------+--------------------+
| Emp_ID | Emp_name | date_hrs_operation |
+--------+----------+--------------------+
| 1 | ABD | 03/28 8:00-7:30 |
| 1 | ABD | 03/29 8:00-7:30 |
| 1 | ABD | 03/30 8:00-6:00 |
| 1 | ABD | 04/02 8:00-7:30 |
| 1 | ABD | 04/03 8:00-7:30 |
| 1 | ABD | 04/04 8:00-7:30 |
| 1 | ABD | 04/05 8:00-7:30 |
| 1 | ABD | 04/06 8:00-7:30 |
| 1 | ABD | 04/07 8:00-6:00 |
| 1 | ABD | 04/09 8:00-7:30 |
| 1 | ABD | 04/10 8:00-7:30 |
| 1 | ABD | 04/11 8:00-7:30 |
| 1 | ABD | 04/12 8:00-7:30 |
| 1 | ABD | 04/13 8:00-7:30 |
| 1 | ABD | 04/14 8:00-3:00 |
+--------+----------+--------------------+

Another option in SQL Server is to use an ad-hoc tally/numbers table in concert with a CROSS APPLY
Example
Select A.Emp_ID
,A.Emp_name
,C.*
From YourTable A
Cross Join (Select Top 50 N=-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) B
Cross Apply ( values (substring([date_hrs_operation],(N*16)+1,16)) )C(date_hrs_operation)
Where C.date_hrs_operation<>''
Results
Emp_ID Emp_name date_hrs_operation
1 ABD 03/28 8:00-7:30
1 ABD 03/29 8:00-7:30
1 ABD 03/30 8:00-6:00
1 ABD 04/02 8:00-7:30
1 ABD 04/03 8:00-7:30
1 ABD 04/04 8:00-7:30
1 ABD 04/05 8:00-7:30
1 ABD 04/06 8:00-7:30
1 ABD 04/07 8:00-6:00
1 ABD 04/09 8:00-7:30
1 ABD 04/10 8:00-7:30
1 ABD 04/11 8:00-7:30
1 ABD 04/12 8:00-7:30
1 ABD 04/13 8:00-7:30
1 ABD 04/14 8:00-3:00
EDIT - Rather than a CROSS JOIN you can use an explicit join and remove the WHERE
...
Join (Select Top 100 N=-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1, master..spt_values n2 ) B on N<=len([date_hrs_operation])/16
...

Related

I want to join the two table according to below

table1
no name
1 aaa
2 bbb
3 ccc
table2
date
1/1/19
2/2/19
result will be
no name date
1 aaa 1/1/19
2 bbb 1/1/19
3 ccc 1/1/19
1 aaa 2/2/19
2 bbb 2/2/19
3 ccc 2/2/19
TRY FULL JOIN
SELECT no, namem, date FROM
TABLE1 FULL JOIN table2 ON 1 = 1
Try CROSS JOIN
SELECT no, namem, date FROM TABLE1 CROSS JOIN table2
DEMO
SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table Table1(num int,name varchar(max))
create table Table2(dates date)
insert into Table1(num,name)values(1,'aaa')
insert into Table1(num,name)values(2,'bbb')
insert into Table1(num,name)values(3,'ccc')
insert into Table2(dates)values('2019-01-01')
insert into Table2(dates)values('2019-02-02')
Query 1:
SELECT num,name,dates
FROM Table1
CROSS APPLY Table2
Results:
| num | name | dates |
|-----|------|------------|
| 1 | aaa | 2019-01-01 |
| 2 | bbb | 2019-01-01 |
| 3 | ccc | 2019-01-01 |
| 1 | aaa | 2019-02-02 |
| 2 | bbb | 2019-02-02 |
| 3 | ccc | 2019-02-02 |

Update each row of the table with data from table itself

I have a problem with updating work table - values from that table come from table itself. Here is my table:
+----------+----------+-----+---------+
| EVEN_KEY | INVE_KEY | QUA | QUA_MAX |
+----------+----------+-----+---------+
| 1 | 2 | 1 | NULL |
| 2 | 2 | 2 | NULL |
| 3 | 2 | 3 | NULL |
| 1 | 1 | 2 | NULL |
| 4 | 2 | 3 | NULL |
+----------+----------+-----+---------+
What I would like to do is update column qua_max - summarize column qua for given inve_key for each row. So, results in above table should look like after update:
+----------+----------+-----+---------+
| EVEN_KEY | INVE_KEY | QUA | QUA_MAX |
+----------+----------+-----+---------+
| 1 | 2 | 1 | 9 |
| 2 | 2 | 2 | 9 |
| 3 | 2 | 3 | 9 |
| 1 | 1 | 2 | 1 |
| 4 | 2 | 3 | 9 |
+----------+----------+-----+---------+
And here is my problem - query from this example is giving me error, I can not even run it. What is wrong?
Error:
Query:
UPDATE #TEMP_FINAL
SET QUA_MAX = (SELECT SUM(QUA)
FROM #TEMP_FINAL t2
WHERE #TEMP_FINAL.INVE_KEY = t2.INVE_KEY
GROUP BY INVE_KEY
)
EXAMPLE TABLE:
DECLARE #TEMP_FINAL TABLE
(
EVEN_KEY INT,
INVE_KEY INT,
QUA INT,
QUA_MAX INT
)
insert into #TEMP_FINAL (even_key, inve_key, qua)
values(1, 2, 1),
(2,2,2),
(3,2,3),
(1,1,2),
(4,2,3)
You can try this..
UPDATE t1
SET t1.QUA_MAX = a.sum_qua
from #temp_final t1,
(SELECT SUM(QUA) as sum_qua,inve_key
FROM #TEMP_FINAL t2
GROUP BY INVE_KEY
) a
where t1.INVE_KEY = a.INVE_KEY
You are looking for window function
sum(QUA) over (partition by INVE_KEY)
Note that your sample data for #TEMP_FINAL has QUA = 2 for INVE_KEY = 1 which isn't what the original sample data was.
select
*,
QUA_MAX = sum(QUA) over (partition by INVE_KEY)
from #TEMP_FINAL
And one way to update it would be with a correlated subquery
update t1
set QUA_MAX = (select top 1 sum(QUA) over (partition by INVE_KEY) from #TEMP_FINAL t2 where t2.INVE_KEY = t1.INVE_KEY)
from #TEMP_FINAL t1
select * from #TEMP_FINAL
Or a CTE
;with cte as(
select
EVEN_KEY,
INVE_KEY,
QUA,
QUA_MAX = sum(QUA) over (partition by INVE_KEY)
from #TEMP_FINAL)
update #TEMP_FINAL
set QUA_MAX = c.QUA_MAX
from cte c
where c.INVE_KEY = [#TEMP_FINAL].INVE_KEY
select * from #TEMP_FINAL
i think that you should add temp_final in the from clause, because you're doing an implicit selfjoin, but you declare just a table as alias t2
So here is your complete code:
DECLARE #TEMP_FINAL TABLE
(
EVEN_KEY INT,
INVE_KEY INT,
QUA INT,
QUA_MAX INT
)
insert into #TEMP_FINAL (even_key, inve_key, qua)
values(1, 2, 1),
(2,2,2),
(3,2,3),
(1,1,2),
(4,2,3)
UPDATE #TEMP_FINAL
SET QUA_MAX = (SELECT SUM(QUA)
FROM #TEMP_FINAL t2
WHERE #TEMP_FINAL.INVE_KEY = t2.INVE_KEY
GROUP BY INVE_KEY
)
I don't know if I am looking at this wrong but to my it looks like you are doing a where clause comparing that same thing to itself? But instead of doing #TEMP_FINAL.INVE_KEY = #TEMP_FINAL.INVE_KEY you're doing #TEMP_FINAL.INVE_KEY = t2.INVE_KEY. Sorry for bringing this up I just wanted to verify that this was intentional or not?

Include rows with 0s or NULLs in the SQL query output

I am querying data (multiple columns) for different item types through a UNION of 2 different queries. If there are no values in any of those columns for a particular item type, that record does not show up. But, I need all rows (including empty ones) pertaining to each item type. The empty rows can show 0.
My data is:
create table sales_table ([yr] int, [qtr] varchar(40), [item_type] varchar(40), [sale_price] int);
create table profit_table ([yr] int, [qtr] varchar(40), [item_type] varchar(40), [profit] int);
create table item_table ([item_type] varchar(40));
insert into sales_table values
(2010,'Q1','abc',31),(2010,'Q1','def',23),(2010,'Q1','mno',12),(2010,'Q1','xyz',7),(2010,'Q2','abc',54),(2010,'Q2','def',67),(2010,'Q2','mno',92),(2010,'Q2','xyz',8);
insert into profit_table values
(2010,'Q1','abc',10),(2010,'Q1','def',6),(2010,'Q1','mno',23),(2010,'Q1','xyz',7),(2010,'Q2','abc',21),(2010,'Q2','def',13),(2010,'Q2','mno',15),(2010,'Q2','xyz',2);
insert into item_table values
('abc'),('def'),('ghi'),('jkl'),('mno'),('xyz');
My Query is:
SELECT a.yr, a.qtr, b.item_type, MAX(a.sales), MAX(a.avg_price), MAX(a.profit)
FROM
(SELECT [yr], [qtr],
CASE WHEN item_type IN ('mno', 'xyz') THEN 'Other' else upper(item_type) END AS [item_type],
COUNT(sale_price) OVER (PARTITION BY yr, qtr, item_type) [sales],
AVG(sale_price) OVER (PARTITION BY yr, qtr, item_type) [avg_price],
NULL [profit]
FROM sales_table
WHERE yr >=2010
UNION ALL
SELECT yr, qtr,
CASE WHEN item_type IN ('mno', 'xyz') THEN 'Other' else upper(item_type) END AS [item_type],
NULL [sales],
NULL [avg_price],
SUM(profit) OVER (PARTITION BY yr, qtr, item_type) [profit]
FROM profit_table
WHERE yr >=2010
) a
FULL OUTER JOIN
(SELECT
CASE WHEN item_type IN ('mno', 'xyz') THEN 'Other' else upper(item_type) END AS [item_type]
FROM item_table
WHERE item_type in ('abc','def','ghi','jkl','mno','xyz')
) b
ON a.item_type = b.item_type
GROUP BY a.yr, a.qtr, b.item_type
ORDER BY a.yr, a.qtr, b.item_type;
The current output is like this:
yr qtr item_type sales avg_price profit
(null) (null) GHI (null) (null) (null)
(null) (null) JKL (null) (null) (null)
2010 Q1 ABC 1 31 10
2010 Q1 DEF 1 23 6
2010 Q1 Other 1 12 23
2010 Q2 ABC 1 54 21
2010 Q2 DEF 1 67 13
2010 Q2 Other 1 92 15
What I want is like as shown below.
yr qtr item_type sales avg_price profit
2010 Q1 ABC 1 31 10
2010 Q1 DEF 1 23 6
2010 Q1 GHI 0 0 0
2010 Q1 JKL 0 0 0
2010 Q1 Other 2 9.5 30
2010 Q2 ABC 1 54 21
2010 Q2 DEF 1 67 13
2010 Q2 GHI 0 0 0
2010 Q2 JKL 0 0 0
2010 Q2 Other 2 50 17
Please advise.
Here you go. Just the way it's described in the comments.
I made a mini calendar table, but you'll want to spend some time making a real one. Once you have it, you'll use it all the time.
if OBJECT_ID('tempdb..#sales_table', 'U') is not null
drop table #sales_table
if OBJECT_ID('tempdb..#profit_table', 'U') is not null
drop table #profit_table
if OBJECT_ID('tempdb..#item_table', 'U') is not null
drop table #item_table
if OBJECT_ID('tempdb..#date_table', 'U') is not null
drop table #date_table
create table #sales_table
(
[yr] int
, [qtr] varchar(40)
, [item_type] varchar(40)
, [sale_price] int
);
create table #profit_table
(
[yr] int
, [qtr] varchar(40)
, [item_type] varchar(40)
, [profit] int
);
create table #item_table
(
[item_type] varchar(40)
);
create table #date_table
(
[yr] int
, [qtr] varchar(2)
);
insert into #sales_table values
(2010,'Q1','abc',31)
,(2010,'Q1','def',23)
,(2010,'Q1','mno',12)
,(2010,'Q1','xyz',7)
,(2010,'Q2','abc',54)
,(2010,'Q2','def',67)
,(2010,'Q2','mno',92)
,(2010,'Q2','xyz',8);
insert into #profit_table values
(2010,'Q1','abc',10)
,(2010,'Q1','def',6)
,(2010,'Q1','mno',23)
,(2010,'Q1','xyz',7)
,(2010,'Q2','abc',21)
,(2010,'Q2','def',13)
,(2010,'Q2','mno',15)
,(2010,'Q2','xyz',2);
insert into #item_table values
('abc'),('def'),('ghi'),('jkl'),('mno'),('xyz');
insert into #date_table values
(2010,'Q1'),(2010,'Q2'), (2010,'Q3'),(2010,'Q4');
SELECT
b.yr
, b.qtr
, b.item_type
, COALESCE(MAX(a.sales),0) AS sales
, COALESCE(MAX(a.avg_price),0) AS avg_price
, COALESCE(MAX(a.profit),0) AS profit
FROM
(
SELECT
dt.[yr]
,dt.[qtr]
,CASE
WHEN it.[item_type] IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER(it.[item_type])
END AS [item_type]
FROM
#date_table AS dt
CROSS JOIN
#item_table AS it
WHERE
dt.[yr] >=2010
GROUP BY
dt.[yr]
,dt.[qtr]
,CASE
WHEN it.[item_type] IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER(it.[item_type])
END
) AS b
LEFT JOIN
(SELECT [yr], [qtr],
CASE
WHEN item_type IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER([item_type])
END AS [item_type],
COUNT(sale_price) OVER (PARTITION BY yr, qtr, item_type) [sales],
AVG(sale_price) OVER (PARTITION BY yr, qtr, item_type) [avg_price],
NULL [profit]
FROM #sales_table
WHERE yr >=2010
UNION ALL
SELECT yr, qtr,
CASE
WHEN item_type IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER([item_type])
END AS [item_type],
NULL [sales],
NULL [avg_price],
SUM(profit) OVER (PARTITION BY yr, qtr, item_type) [profit]
FROM #profit_table
WHERE yr >=2010
) a
ON
a.[yr] = b.[yr]
AND
a.[qtr] = b.[qtr]
AND
a.[item_type] = b.[item_type]
GROUP BY
b.yr, b.qtr, b.item_type
ORDER BY b.yr, b.qtr, b.item_type;
Results:
+------+-----+-----------+-------+-----------+--------+
| yr | qtr | item_type | sales | avg_price | profit |
+------+-----+-----------+-------+-----------+--------+
| 2010 | Q1 | ABC | 1 | 31 | 10 |
| 2010 | Q1 | DEF | 1 | 23 | 6 |
| 2010 | Q1 | GHI | 0 | 0 | 0 |
| 2010 | Q1 | JKL | 0 | 0 | 0 |
| 2010 | Q1 | Other | 1 | 12 | 23 |
| 2010 | Q2 | ABC | 1 | 54 | 21 |
| 2010 | Q2 | DEF | 1 | 67 | 13 |
| 2010 | Q2 | GHI | 0 | 0 | 0 |
| 2010 | Q2 | JKL | 0 | 0 | 0 |
| 2010 | Q2 | Other | 1 | 92 | 15 |
| 2010 | Q3 | ABC | 0 | 0 | 0 |
| 2010 | Q3 | DEF | 0 | 0 | 0 |
| 2010 | Q3 | GHI | 0 | 0 | 0 |
| 2010 | Q3 | JKL | 0 | 0 | 0 |
| 2010 | Q3 | Other | 0 | 0 | 0 |
| 2010 | Q4 | ABC | 0 | 0 | 0 |
| 2010 | Q4 | DEF | 0 | 0 | 0 |
| 2010 | Q4 | GHI | 0 | 0 | 0 |
| 2010 | Q4 | JKL | 0 | 0 | 0 |
| 2010 | Q4 | Other | 0 | 0 | 0 |
+------+-----+-----------+-------+-----------+--------+

sql server set consumption value based on first older record

i have a table containing sensor data in Sql server 2014, with multiple sensors each with its own sensor id. I want to calculate and set / update the consumption field by calculating the consumption between the current value and subtracting the previous value based on the logdate for a single sensorid, for example sensorid = 8555.
Current table structure for sensorid 8555:
+----------+-------------------------+---------+-------------+
| SensorId | LogDate | Value | Consumption |
+----------+-------------------------+---------+-------------+
| 8555 | 2016-10-03 13:00:00.000 | 0.00000 | Null |
| 2478 | 2016-11-09 09:00:00.000 | 0.00000 | 0.00000 |
| 2478 | 2016-11-09 10:00:00.000 | 0.00000 | 0.00000 |
| 8555 | 2016-10-03 14:00:00.000 | 1.00000 | Null |
| 8555 | 2016-10-03 15:00:00.000 | 1.00000 | Null |
| 8555 | 2016-10-03 16:00:00.000 | 1.00000 | Null |
| 8555 | 2016-10-03 17:00:00.000 | 2.00000 | Null |
| 8555 | 2016-10-03 18:00:00.000 | 2.00000 | Null |
| 8555 | 2016-10-03 19:00:00.000 | 4.00000 | Null |
+----------+-------------------------+---------+-------------+
The end result should be this:
+----------+-------------------------+---------+-------------+
| SensorId | LogDate | Value | Consumption |
+----------+-------------------------+---------+-------------+
| 8555 | 2016-10-03 13:00:00.000 | 0.00000 | 0.00000 |
| 2478 | 2016-11-09 09:00:00.000 | 0.00000 | 0.00000 |
| 2478 | 2016-11-09 10:00:00.000 | 0.00000 | 0.00000 |
| 8555 | 2016-10-03 14:00:00.000 | 1.00000 | 1.00000 |
| 8555 | 2016-10-03 15:00:00.000 | 1.00000 | 0.00000 |
| 8555 | 2016-10-03 16:00:00.000 | 1.00000 | 0.00000 |
| 8555 | 2016-10-03 17:00:00.000 | 2.00000 | 1.00000 |
| 8555 | 2016-10-03 18:00:00.000 | 2.00000 | 0.00000 |
| 8555 | 2016-10-03 19:00:00.000 | 4.00000 | 2.00000 |
+----------+-------------------------+---------+-------------+
I have scoured google for a solution, and i could only find a version of the answer for Mysql, which i do not know how to convert to sql server.
I know that the update statement should contain LAG and probably use CTEs but i haven't found an easy to understand explanation on how to use those on the web.
Any help would be appreciated.
BEGIN TRAN
CREATE TABLE #T (SensorId INT , LogDate DATETIME, Value DECIMAL(15,4), Consumption DECIMAL(15,4))
INSERT INTO #T
SELECT 8555 , '2016-10-03 13:00:00.000' ,0.00000 ,Null UNION ALL
SELECT 2478 , '2016-11-09 09:00:00.000' ,0.00000 ,0.0000 UNION ALL
SELECT 2478 , '2016-11-09 10:00:00.000' ,0.00000 ,0.0000 UNION ALL
SELECT 8555 , '2016-10-03 14:00:00.000' ,1.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 15:00:00.000' ,1.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 16:00:00.000' ,1.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 17:00:00.000' ,2.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 18:00:00.000' ,2.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 19:00:00.000' ,4.00000 ,Null
GO
SELECT * FROM #T
;with CTE as (
select SensorId,
ROW_NUMBER() OVER (Order By SensorId,LogDate) Rownum,
LogDate, Value
FROM #T
)
UPDATE #T SET Consumption= ISNULL(curr.Value - prev.Value,0)
from #T INNER JOIN
CTE curr ON #T.LogDate= curr.LogDate
left join CTE prev on curr.Rownum = (prev.Rownum + 1)
SELECT * FROM #T
ROLLBACK TRAN

SQL Server time interval to multiple rows

I have this record with StartDateTime and EndDateTime and I want the result to be in rows with 15 minutes interval:
Sample data:
FName | StartDateTime | EndDateTime
:----- | -----------: | :--------------:
Juan | 08/01/2017 1:00| 08/01/2017 8:00
Result:
FName | Interval
:----- | -----------:
Juan | 08/01/2017 1:00
Juan | 08/01/2017 1:15
Juan | 08/01/2017 1:30
Juan | 08/01/2017 1:45
Juan | 08/01/2017 2:00
Until it reaches to its EndDateTime, how can I achieve this in SQL. I'm thinking using looping statement or cte.
If you are open to a TVF (Table-Valued Function)
I'll often use this udf to create dynamic date/time ranges. It is faster than a recursive CTE and parameter driven.
The parameters are Date1, Date2, DatePart, and Increment
Example
Declare #YourTable Table ([FName] varchar(50),[StartDateTime] datetime,[EndDateTime] datetime)
Insert Into #YourTable Values
('Juan','08/01/2017 1:00','08/01/2017 8:00')
Select A.FName
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Range-Date](A.StartDateTime,A.EndDateTime,'MI',15) B
Returns
FName RetSeq RetVal
Juan 1 2017-08-01 01:00:00.000
Juan 2 2017-08-01 01:15:00.000
Juan 3 2017-08-01 01:30:00.000
Juan 4 2017-08-01 01:45:00.000
Juan 5 2017-08-01 02:00:00.000
...
Juan 29 2017-08-01 08:00:00.000
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
You could use a recursive query like this
DECLARE #SampleData AS TABLE
(
FName varchar(20),
StartDatetime datetime,
EndDatetime datetime
)
INSERT INTO #SampleData
(
FName,
StartDatetime,
EndDatetime
)
VALUES
('Juan', '2017-08-01 1:00', '2017-08-01 8:00')
;WITH temp AS
(
SELECT sd.FName, sd.StartDatetime AS Interval, sd.EndDatetime
FROM #SampleData sd
UNION ALL
SELECT t.FName, dateadd(minute, 15, t.Interval) AS Interval, t.EndDatetime
FROM temp t
WHERE dateadd(minute, 15, t.Interval) <= t.EndDatetime
)
SELECT t.FName,
t.Interval
FROM temp t
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/CELP88345
It is better to use date table but you can generate as below:
Select Fname, RowN as Interval from #dates
cross apply (
Select top(datediff(hh,startdatetime, enddatetime)*4+1) RowN =dateadd(mi, (row_number() over(order by (Select NULL)) -1)*15 ,startdatetime)
from master..spt_values s1, master..spt_values s2 ) a
Output as below:
+-------+-------------------------+
| Fname | Interval |
+-------+-------------------------+
| Juan | 2017-08-01 01:00:00.000 |
| Juan | 2017-08-01 01:15:00.000 |
| Juan | 2017-08-01 01:30:00.000 |
| Juan | 2017-08-01 01:45:00.000 |
| Juan | 2017-08-01 02:00:00.000 |
| Juan | 2017-08-01 02:15:00.000 |
| Juan | 2017-08-01 02:30:00.000 |
| Juan | 2017-08-01 02:45:00.000 |
| Juan | 2017-08-01 03:00:00.000 |
| Juan | 2017-08-01 03:15:00.000 |
| Juan | 2017-08-01 03:30:00.000 |
| Juan | 2017-08-01 03:45:00.000 |
| Juan | 2017-08-01 04:00:00.000 |
| Juan | 2017-08-01 04:15:00.000 |
| Juan | 2017-08-01 04:30:00.000 |
| Juan | 2017-08-01 04:45:00.000 |
| Juan | 2017-08-01 05:00:00.000 |
| Juan | 2017-08-01 05:15:00.000 |
| Juan | 2017-08-01 05:30:00.000 |
| Juan | 2017-08-01 05:45:00.000 |
| Juan | 2017-08-01 06:00:00.000 |
| Juan | 2017-08-01 06:15:00.000 |
| Juan | 2017-08-01 06:30:00.000 |
| Juan | 2017-08-01 06:45:00.000 |
| Juan | 2017-08-01 07:00:00.000 |
| Juan | 2017-08-01 07:15:00.000 |
| Juan | 2017-08-01 07:30:00.000 |
| Juan | 2017-08-01 07:45:00.000 |
| Juan | 2017-08-01 08:00:00.000 |
+-------+-------------------------+
You can use Recursive CTE to achieve the expected results.
Demo - http://sqlfiddle.com/#!6/821a5/11
with cte as
(
select Fname,StartDateTime
from Table1
union all
select Fname,DATEADD(mi,15,StartDateTime) as StartDateTime
from cte
where DATEADD(mi,15,StartDateTime) <= '2017-01-08 08:00:00:000'
)
select Fname, StartDateTime from cte;

Resources