Split Single Row into Multiple Rows Based on Two Timestamp Columns - sql-server

I want to split the following records:
DeviceId StartTime EndTime
------------------------------------------------------------
1001 2022-02-12 07:27:00.000 2022-02-12 16:23:00.000
1002 2022-02-14 03:36:00.000 2022-02-14 04:36:00.000
Into:
DeviceId Timestamp State
-------------------------------------------------
1001 2022-02-12 07:27:00.000 1
1001 2022-02-12 16:23:00.000 2
1002 2022-02-14 03:36:00.000 1
1002 2022-02-14 04:36:00.000 2
The new State column should be based on whether the Timestamp is a StartTime ( = 1), or an EndTime ( = 2).
What would be the t-sql query to achieve this ?

You may unpivot the table using VALUES table value constructor:
SELECT t.DeviceId, v.[Timestamp], v.[State]
FROM YourTable t
CROSS APPLY (VALUES
(t.StartTime, 1),
(t.EndTime, 2)
) v ([Timestamp], [State])
If you want to add a condition while generating the values, a different statement is needed:
SELECT t.DeviceId, v.[Timestamp], v.[State]
FROM YourTable t
CROSS APPLY (VALUES
(CASE WHEN t.Status IN (0, 1) THEN t.StartTime END, 1),
(t.EndTime, 2)
) v ([Timestamp], [State])

Related

Bigquery - Add full date range to each id

How can i apply GENERATE_DATE_ARRAY(start_date, end_date[, INTERVAL INT64_expr date_part]) to each record in a dataset. I understand how to apply it to get a single date range from start to end, but don't know how to apply the same date array to each id.
Say i have two distinct ID's x and y with the following dates:
|id|date
--------------
1 |x |2021-01-01
2 |x |2021-01-03
3 |y |2021-01-06
4 |y |2021-01-09
and i want to fill in the date gap for each ID
How can i achieve the following output?
|id|date
--------------
1 |x |2021-01-01
2 |x |2021-01-02
3 |x |2021-01-03
4 |y |2021-01-06
5 |y |2021-01-07
6 |y |2021-01-08
7 |y |2021-01-09
Below is for BigQuery Standard SQL
select id, date from (
select id, date, lead(date) over(partition by id order by date) next_date
from `project.dataset.table`
), unnest(generate_date_array(date, next_date)) date
where not next_date is null
-- order by date
if to apply to sample data from your question - output is
Try the following in standard SQL in BigQuery:
with data as (
select 'x' as id, date '2021-01-01' as date
UNION ALL
select 'x' as id, date '2021-01-03' as date
UNION ALL
select 'y' as id, date '2021-01-06' as date
UNION ALL
select 'y' as id, date '2021-01-09' as date
)
select d1.id, date
from data d1
join data d2
on d1.id = d2.id
and d1.date < d2.date, unnest(GENERATE_DATE_ARRAY(d1.date, d2.date, INTERVAL 1 DAY)) as date;

MsSQL Fetching Data based on Previous and Current value in ZicZac way

My Table has
----------------------------------------------------------
|RunningId PreviousValue CurrentValue CreatedDate |
----------------------------------------------------------
|1 1000 1001 2018-04-20 |
----------------------------------------------------------
|2 1001 1002 2018-04-21 |
----------------------------------------------------------
|3 1002 1003 2018-04-22 |
----------------------------------------------------------
|4 2000 2003 2018-04-22 |
----------------------------------------------------------
|5 2003 2004 2018-04-23 |
----------------------------------------------------------
If I search for 1002, query should return previous and current value from the begining
Eg:
----------------------------------------------------------
PreValue CurrrentValue:
----------------------------------------------------------
1000 1001
----------------------------------------------------------
1001 1002
----------------------------------------------------------
1002 1003
----------------------------------------------------------
I want to take the reference in ZicZac way. If I search for 1000, 1001, 1002, 1003, all the result should return row# 1,2 & 3.
Same way if I search for 2000, 2003, 2004, it should return row# 4,5. And the values are random. not in sequence way.
First row have some value in the beginning, then it changed to some other value, then changed to some other value and so on. so Pair can be[pre-cur value], 1-3, 3-7, 7-2, 2-100.... If I search for 7, it should return both way 1,3,7,2,100
How to query this?
this will solve your problem
select Prevalue, CurrentValue FROM Table1 WHERE Prevalue = 1002 or
CurrentValue = 1002 or CurrentValue = (Select Prevalue from Table1
where CurrentValue = 1002 ) ORDER BY id
Try with this recursive query. It can be optimized a little, but will give you the basic idea of how to link each relationship.
DECLARE #ValueToFind INT = 1002
;WITH ForwardRelationships AS
(
SELECT
SourceValue = #ValueToFind,
CurrentValue = I.CurrentValue,
PreviousValue = I.PreviousValue
FROM
IDs AS I
WHERE
I.CurrentValue = #ValueToFind OR I.PreviousValue = #ValueToFind
UNION ALL
SELECT
SourceValue = F.SourceValue,
CurrentValue = I.CurrentValue,
PreviousValue = I.PreviousValue
FROM
ForwardRelationships AS F -- We are referencing the CTE that we are declaring (recursively)
INNER JOIN IDs AS I ON F.CurrentValue = I.PreviousValue
),
BackwardRelationships AS
(
SELECT
SourceValue = #ValueToFind,
CurrentValue = I.CurrentValue,
PreviousValue = I.PreviousValue
FROM
IDs AS I
WHERE
I.CurrentValue = #ValueToFind OR I.PreviousValue = #ValueToFind
UNION ALL
SELECT
SourceValue = F.SourceValue,
CurrentValue = I.CurrentValue,
PreviousValue = I.PreviousValue
FROM
BackwardRelationships AS F
INNER JOIN IDs AS I ON F.PreviousValue = I.CurrentValue
)
SELECT
F.PreviousValue,
F.CurrentValue
FROM
ForwardRelationships AS F
UNION
SELECT
B.PreviousValue,
B.CurrentValue
FROM
BackwardRelationships AS B
OPTION
(MAXRECURSION 30000)
You can use below code to get all the values from the table, prior to 1002, assuming you want from the beginning.
select Prevalue, CurrentValue
FROM MyTable
WHERE id <=
(SELECT Id from MyTable
WHERE PreValue = 1002)
ORDER BY id
You can also use row_number() function to get your desired results.
create table t (RunningId int,PreviousValue int,CurrentValue int,CreatedDate date)
;
insert into t values
(1, 1000, 1001, '04-20-2018')
,(2, 1001, 1002, '04-21-2018')
,(3, 1002, 1003, '04-22-2018')
,(4, 2000, 2003, '04-22-2018')
,(5, 2003, 2004, '04-23-2018');
; with cte as (
select *,r=row_number() over (order by runningid asc) from
t
)
select c1.* from cte c1 join cte
c2 on c1.r<=c2.r and c2.previousvalue=1002
Also if your RunningId values are always increasing then you don't need row_number and simple self join will work like below
select t1.*
from t t1
join t t2
on t1.RunningId<=t2.RunningId and t2.previousvalue=1002

How can i use sql query for the following

My data table sampletime in one column and sample value in another column contain data like follow
sampletime value
----------------------------
2016-03-02 08:31:14 1
2016-03-02 09:31:14 2
2016-03-02 12:31:14 3
2016-03-04 08:31:14 4
2016-03-04 09:31:14 5
2016-03-05 08:31:14 3
I need two minimum sample time in each day. How can I group?
Query
SELECT rn.sampletime AS stime
FROM rn_qos_data_0007 rn
INNER JOIN s_qos_data qos
ON qos.table_id = rn.table_id
AND qos.qos = 'QOS_CPU_USAGE'
AND Substring(qos.origin, 1, 4) = 'A0C3'
AND qos.host = '10.98.48.100'
WHERE rn.sampletime BETWEEN '2016/01/01' AND '2016/06/22'
GROUP BY rn.sampletime
You need ROW_NUMBER window function
Select * From
(
select row_number()over(partition by cast(sampletime as date) order by sampletime) RN,*
From ..
) A
Where RN <=2

SQL Server: split a column value into two separate columns based on another column

I have a table Access:
logId empid empname inout tim
----------------------------------------------------
230361 0100 XYZ 0 2015-08-01 10:00:03
230362 0106 XYZ 0 2015-08-01 10:30:00
230363 0100 XYZ 1 2015-08-01 12:00:00
which records each employee's in time and out time. inout=0 means in and inout=1 means out
I would like to create a table as below from this table
empid empname timIn timOut
-------------------------------------------------------------
0100 XYZ 2015-08-01 10:00:03 2015-08-01 12:00:00
0106 XYZ 2015-08-01 10:30:00
First I tried case as follows:
select
empid, empname, inout,
case when inout = 0 then tim end as 'timIn',
case when inout = 1 then tim end as 'timout'
But NULLs were a problem the result was
0100 xyz 2015-08-01 10:00:03 NULL
0100 xyz NULL 2015-08-01 12:00:00
Second I tried PIVOT, but the problem was I had to use an aggregate function. I need all in-out times and cannot take an aggregate of that.
Is there any alternative way to get the desired result?
You can use APPLY, in conjunction with TOP 1 and the correct ORDER BY to get the next out event after each in event
SELECT i.empID,
i.empname,
TimeIn = i.tim,
TimeOut = o.tim
FROM Access AS i
OUTER APPLY
( SELECT TOP 1 tim
FROM Access AS o
WHERE o.EmpID = i.EmpID
AND o.InOut = 1
AND o.tim > i.tim
ORDER BY o.Tim
) AS o
WHERE i.InOut = 0;
So you are simply selecting all in events (table aliased i), then for each in event, finding the next out event, if there is not one, then the time out field will be null.
FULL WORKING EXAMPLE
DECLARE #Access TABLE (LogID INT NOT NULL, EmpID CHAR(4) NOT NULL, empname VARCHAR(50), InOut BIT NOT NULL, tim DATETIME2 NOT NULL);
INSERT #Access (LogID, EmpID, empname, InOut, tim)
VALUES
(230361, '0100', 'XYZ', 0, '2015-08-01 10:00:03'),
(230362, '0106', 'XYZ', 0, '2015-08-01 10:30:00'),
(230363, '0100', 'XYZ', 1, '2015-08-01 12:00:00');
SELECT i.empID,
i.empname,
TimeIn = i.tim,
TimeOut = o.tim
FROM #Access AS i
OUTER APPLY
( SELECT TOP 1 tim
FROM #Access AS o
WHERE o.EmpID = i.EmpID
AND o.InOut = 1
AND o.tim > i.tim
ORDER BY o.Tim
) AS o
WHERE i.InOut = 0;
So what I think you want to do is find the first time out after each time in. The following SQL should do that.
Select
empid,
empname,
tim as timein
(select top 1 tim
from my_table outTimes
where outTimes.inout = 1 and
outTimes.empid = inTimes.empid and
outTimes.tim > inTimes.tim
orderby outTimes.tim asc
) as timeout
from my_table inTimes
when inout=0
The critical bit here is the orderby asc and the top 1. This is what gives you the next time in the table.
update: Based on comment that I should improve this query to take all dates data and not just last date's data, updated query simply includes a new date column
select empid,empname,d,[0] as [timin],[1] as [timOut]
from
(select empid,empname, cast(tim as DATE)as d,inout,tim from tbl) s
pivot
(max(tim) for inout in ([0],[1]))p
updated fiddle link http://sqlfiddle.com/#!6/f1bc7/1
try PIVOT query like this:
select empid,empname,[0] as [timin],[1] as [timOut]
from
(select empid,empname,inout,tim from tbl) s
pivot
(max(tim) for inout in ([0],[1]))p
added SQL fiddle link http://sqlfiddle.com/#!6/6c3bf/1

Query trick - kind of unpivot

I have the following table
SnapShotDay OperationalUnitNumber IsOpen StatusDate
1-01-2014 001 1 1-01-2014
2-01-2014 NULL NULL NULL
3-01-2014 001 0 3-01-2014
4-01-2014 NULL NULL NULL
5-01-2014 001 1 5-01-2014
I obtain this with a SELECT construct, but what I need to do now is fill in the "NULL"ed rows by taking values from the first Non nulled row before. The latter would give:
SnapShotDay OperationalUnitNumber IsOpen StatusDate
1-01-2014 001 1 1-01-2014
2-01-2014 001 1 1-01-2014
3-01-2014 001 0 3-01-2014
4-01-2014 001 0 3-01-2014
5-01-2014 001 1 5-01-2014
In functional words: I have events records that give me an event on a date for an oprrational unit; the event is: IsOpen or IsClosed. Chaining those events together according to the date gives a sort of Ranges. What I need is generate daily records for those ranges (target is a fact table).
I am trying to achieve this in plain SQL query (no stored procedure).
Can you think of a trick ?
Declare #t table(
SnapShotDay date,
OperationalUnitNumber int,
IsOpen bit,
StatusDate date
)
insert into #t
select '1-01-2014', 001 , 1 , '1-01-2014' union all
select '2-01-2014', NULL, NULL, NULL union all
select '3-01-2014', 001 , 0 ,'3-01-2014' union all
select '4-01-2014', NULL,NULL,NULL union all
select '5-01-2014', 001 ,1,'5-01-2014'
;
with CTE as
(
select *,row_number()over( order by (select 0))rn from #t
)
select *,
case when a.isopen is null then (
select IsOpen from cte where rn=a.rn-1
) else a.isopen end
from cte a
ok i got it create one more cte1 then,
,cte1 as
(
select top 1 rn ,IsOpen from cte where IsOpen is not null order by rn desc
)
--select * from Statuses
select *,
case
when a.rn<=(select b.rn from cte1 b) and a.IsOpen is null then
(
select
a1.IsOpen
from
cte a1
where
a1.rn=a.rn-1
)
when a.rn>=(select b.rn from cte1 b) and a.IsOpen is null then
(select IsOpen from cte1)
else
a.isopen
end
from
cte a
Try this. In the main query we're looking for the previous date with not null values. Then just JOIN this table with this LastDate.
WITH T1 AS
(
SELECT *, (SELECT MAX(SnapShotDay)
FROM T
WHERE SnapShotDay<=TMain.SnapShotDay
AND OPERATIONALUNITNUMBER IS NOT NULL)
as LastDate
FROM T as TMain
)
SELECT T1.SnapShotDay,
T.OperationalUnitNumber,
T.IsOpen,
T.StatusDate
FROM T1
JOIN T ON T1.LastDate=T.SnapShotDay
SQLFiddle demo
SELECT
t1.SnapShotDay,
CASE WHEN t1.OperationalUnitNumber IS NOT NUll
THEN t1.OperationalUnitNumber
ELSE (SELECT TOP 1 t2.OperationalUnitNumber FROM YourTable t2 WHERE t2.SnapShotDay < t1.SnapShotDay AND t2.OperationalUnitNumber IS NOT NULL ORDER BY SnapShotDay DESC)
END AS OperationalUnitNumber,
CASE WHEN t1.IsOpen IS NOT NUll
THEN t1.IsOpen
ELSE (SELECT TOP 1 t2.IsOpen FROM YourTable t2 WHERE t2.SnapShotDay < t1.SnapShotDay AND t2.IsOpen IS NOT NULL ORDER BY SnapShotDay DESC)
END AS IsOpen,
CASE WHEN t1.StatusDate IS NOT NUll
THEN t1.StatusDate
ELSE (SELECT TOP 1 t2.StatusDate FROM YourTable t2 WHERE t2.SnapShotDay < t1.SnapShotDay AND t2.StatusDate IS NOT NULL ORDER BY SnapShotDay DESC)
END AS StatusDate
FROM YourTable t1
You asked for 'plain sql', here is a tested attempt using SQL, with comments, that gives the required answer.
I have tested the code using 'sqlite' and 'mysql' on windows xp. It is pure SQL and should work everywhere.
SQL is about 'sets' and combining them and ordering the results.
This problem seems to be about two separate sets:
1) The 'snap shot day' that have readings.
2) the 'snap shot day' that don't have readings.
I have added extra columns so that we can easily see where values came from.
let us deal with the easy set first:
This is the set of 'supplied' readings.
SELECT dss.SnapShotDay theDay,
'supplied' readingExists,
dss.OperationalUnitNumber,
dss.IsOpen,
dss.StatusDate
FROM dailysnapshot dss
WHERE dss.OperationalUnitNumber IS NOT NULL
results:
theDay readingExists OperationalUnitNumber IsOpen StatusDate
2014-01-01 supplied 001 1 2014-01-01
2014-01-03 supplied 001 0 2014-01-03
2014-01-05 supplied 001 1 2014-01-05
Now let us deal with the set of 'days that have missing readings'. We need to get the 'most recent day that has readings that is closest to the day with the missing readings' and assume the same values from the 'most recent day' that is before the 'current' missing day.
It sounds complex but it isn't. It asks:
foreach day without a reading - get me the closest, earlier, date that has readings and i will use those readings.
Here is the query:
SELECT emptyDSS.SnapShotDay,
'missing' readingExists,
maxPrevDSS.OperationalUnitNumber,
maxPrevDSS.IsOpen,
maxPrevDSS.StatusDate
FROM dailysnapshot emptyDSS
INNER JOIN dailysnapshot maxPrevDSS ON maxPrevDSS.SnapShotDay =
(SELECT MAX(dss.SnapShotDay)
FROM dailysnapshot dss
WHERE dss.SnapShotDay < emptyDSS.SnapShotDay
AND dss.OperationalUnitNumber IS NOT NULL)
WHERE emptyDSS.OperationalUnitNumber IS NULL
results:
SnapShotDay readingExists OperationalUnitNumber IsOpen StatusDate
2014-01-02 missing 001 1 2014-01-01
2014-01-04 missing 001 0 2014-01-03
This is not about efficiency! It is about getting the correct 'result set' with the easiest to understand SQL code. I assume the database engine will optimize the query. The query can be 'tweaked' later if required.
We now need to combine the two queries and order the results in the manner we require.
The standard way of combining results from SQL queries is with set operators (union, intersection, minus).
we use 'union' and an 'order by' on the result set.
this gives the final query of:
SELECT dss.SnapShotDay theDay,
'supplied' readingExists,
dss.OperationalUnitNumber,
dss.IsOpen,
dss.StatusDate
FROM dailysnapshot dss
WHERE `OperationalUnitNumber` IS NOT NULL
UNION
SELECT emptyDSS.SnapShotDay theDay,
'missing' readingExists,
maxPrevDSS.OperationalUnitNumber,
maxPrevDSS.IsOpen,
maxPrevDSS.StatusDate
FROM dailysnapshot emptyDSS
INNER JOIN dailysnapshot maxPrevDSS ON maxPrevDSS.SnapShotDay =
(SELECT MAX(dss.SnapShotDay)
FROM dailysnapshot dss
WHERE dss.SnapShotDay < emptyDSS.SnapShotDay
AND dss.OperationalUnitNumber IS NOT NULL)
WHERE emptyDSS.OperationalUnitNumber IS NULL
ORDER BY theDay ASC
result:
theDay readingExists dss.OperationalUnitNumber dss.IsOpen dss.StatusDate
2014-01-01 supplied 001 1 2014-01-01
2014-01-02 missing 001 1 2014-01-01
2014-01-03 supplied 001 0 2014-01-03
2014-01-04 missing 001 0 2014-01-03
2014-01-05 supplied 001 1 2014-01-05
I enjoyed doing this.
It should work with most SQL engines.

Resources