I am trying to identify the entry and exit time of the data. The calculation of exit time is, If the rule is not in the next activity, then that is the exit time for that rule.
here is how my data looks like.
DECLARE #T AS TABLE
(
Visit_ID INT,
Line INT,
Rule_ID INT,
Activity_DTTM datetime
)
insert into #T VALUES
(123, 1, 100072, '2022-07-01 03:05:00.000' ),
(123, 1, 100173, '2022-07-01 03:05:00.000' ),
(123, 1, 718719, '2022-07-01 03:05:00.000' ),
(123, 2, 100072, '2022-07-01 04:05:00.000' ),
(123, 2, 718719, '2022-07-01 04:05:00.000' ),
(123, 3, 100072, '2022-07-01 06:00:00.000' ),
(123, 4, 100072, '2022-07-02 02:10:00.000' ),
(123, 4, 100173, '2022-07-02 02:10:00.000' )
DECLARE #Desiredresult AS TABLE
(
Visit INT,
Rule_ID INT,
Entry_Time datetime,
Exit_Time datetime
)
insert into #Desiredresult VALUES
(123,100072,'2022-07-01 03:05:00.000', null ),
(123,100173,'2022-07-01 03:05:00.000', '2022-07-01 04:05:00.000'),
(123,718719,'2022-07-01 03:05:00.000', '2022-07-01 06:00:00.000'),
(123,100173,'2022-07-02 02:10:00.000', null)
select * from #Desiredresult
Is this a version of Gaps and Islands problem? I have written a SQL and it was very inefficient where it's taking more than 2hrs. to process 10 million rows.
This is the query i tried.
select me.Visit_ID
,me.Rule_ID
, MIN(CASE WHEN prev_split.line is null then me.ACTIVITY_DTTM else null end) as ENTRY_DTTM
, MAX(CASE WHEN next_split.line is null
then next_flat.Activity_DTTM
else null end
) as EXIT_DTTM
from #T me
left join #T prev_flat on me.Visit_ID = prev_flat.Visit_ID and me.LINE-1 = prev_flat.line -- minus 1
left join #T next_flat on me.Visit_ID = next_flat.Visit_ID and me.LINE+1 = prev_flat.line -- plus 1
left join #T prev_split on me.Visit_ID = prev_split.Visit_ID and me.RULE_ID = prev_split.RULE_ID and me.LINE-1 = prev_split.line -- minus 1
left join #T next_split on me.Visit_ID = next_split.Visit_ID and me.RULE_ID = next_split.RULE_ID and me.LINE+1 = next_split.line -- plus 1
group by me.Visit_ID
,me.Rule_ID
I need to pivot a table as show below using column "channel" and grouping it based on Units.
Actual table:
The result I need is shown below
I'm not an expert with pivotting and unpivoting concepts, I'm trying the below query to achieve the above result
SELECT [service_point_ID]
,isnull([1],0) - isnull([2],0) as net_usage_value
,[units]
,[1]
,[2]
,[channel_ID]
,[date]
,[time]
,[is_estimate]
,[UTC_offset]
,[import_history_id]
FROM #temp1
AS SourceTable PIVOT(sum(usage_value) FOR channel IN([1],[2])) AS PivotTable
If I execute this query I'm getting the below result
The same logic is achieved in r -Refernce link Pivot using Mutiple columns
Here is the SQL fiddle for this one
CREATE TABLE #temp1
(
Service_point_ID varchar(10) NUll,
usage_value decimal(18,6) NULL,
units varchar(10) NUll,
[date] Date NULL,
[time] time NULL,
channel varchar(2) NULL,
[Channel_ID] varchar(2) NULL,
is_estimate varchar(2) NULL,
UTC_Offset varchar(20) NULL
)
INSERT INTO #temp1 VALUES ('123',1.000000,'kvarh','2017-01-01','0015','1','11','A','-500')
INSERT INTO #temp1 VALUES ('123',0.200000,'kvarh','2017-01-01','0015','2','11','A','-500')
INSERT INTO #temp1 VALUES ('123',0.200000,'kwh','2017-01-01','0015','1','11','A','-500')
INSERT INTO #temp1 VALUES ('123',0.400000,'kwh','2017-01-01','0015','2','11','A','-500')
Any help is much appreciated.
This is solution using pivot function:
declare #table table(
service_point_id int,
usage_value float,
units varchar(10),
[date] date,
[time] char(4),
channel int,
channel_id int,
is_estimate char(1),
utc_offset int,
import_history int,
datecreated datetime
)
--example data you provided
insert into #table values
(123, 1, 'kvarh', '2017-01-01', '0015', 1, 11, 'A', -500, 317, '2018-03-20 10:32:42.817'),
(123, 0.2, 'kwh', '2017-01-01', '0015', 1, 33, 'A', -500, 317, '2018-03-20 10:32:42.817'),
(123, 0.3, 'kvarh', '2017-01-01', '0015', 2, 11, 'A', -500, 317, '2018-03-20 10:32:42.817'),
(123, 0.4, 'kwh', '2017-01-01', '0015', 2, 33, 'A', -500, 317, '2018-03-20 10:32:42.817')
--pivot query that does the work, it's only matter of aggregation one column, as mentioned already, so pivot query is really simple and concise
select *, [1]-[2] [net_usage_value] from
(select * from #table) [t]
pivot (
max(usage_value)
for channel in ([1],[2])
) [a]
SELECT [service_point_ID]
sum(,isnull([1],0) - isnull([2],0)) as net_usage_value
,[units]
,sum(isnull([1],0))[1]
,sum(isnull([2],0))[2]
,[channel_ID]
,[date]
,[time]
,[is_estimate]
,[UTC_offset]
,[import_history_id]
FROM #temp1
AS SourceTable PIVOT(sum(usage_value) FOR channel IN([1],[2])) AS PivotTable
group by [service_point_ID], [units],[channel_ID]
,[date]
,[time]
,[is_estimate]
,[UTC_offset]
,[import_history_id]
Inner join will out perform the pivot syntax. SQL Server pivot vs. multiple join
select a.usage_value - b.usage_value as net_usage_value , other columns
from #temp1 a inner join #temp1 b on a.service_point_id = b.service_point_id
and a.units = b.units
and a.channel = 1
and b.channel = 2
gets around the group by as well.
I have a question. I have a table like this:
Actually, those dates are the start date and the end date of an employee who is working on a task. And in a month, usually they have more than one task.
What I want are the dates when they are idle or they don't have any task. So my question is, how to get those idle dates between those working dates and insert those idle dates into a temporary table?
Thank you :)
Starting with SQL 2012 there's the LEAD function.
It can be used to find gaps between ranges.
For example :
DECLARE #EmployeeAssignments TABLE (Id INT IDENTITY(1, 1), EmployeeId INT, SDate DATE, EDate DATE);
INSERT INTO #EmployeeAssignments (EmployeeId,SDate,EDate) values
(11505, '2016-10-01', '2016-10-05'),
(11505, '2016-10-09', '2016-10-12'),
(11505, '2016-10-14', '2016-10-20'),
(11506, '2016-10-02', '2016-10-05'),
(11506, '2016-10-08', '2016-10-14'),
(11506, '2016-10-15', '2016-10-19');
select *
from (
select EmployeeId,
dateadd(day,1,EDate) as StartDateGap,
dateadd(day,-1,lead(SDate) over (partition by EmployeeId order by SDate)) as EndDateGap
from #EmployeeAssignments
) as q
where StartDateGap <= EndDateGap
order by EmployeeId, StartDateGap, EndDateGap;
Returns:
EmployeeId StartDateGap EndDateGap
11505 2016-10-06 2016-10-08
11505 2016-10-13 2016-10-13
11506 2016-10-06 2016-10-07
To get those ranges as a list of dates?
One way to do that is by joining to a table with dates.
In the example below, a recursive query is used to generate those dates.
Only days between monday and friday are inserted.
Since we can expect that the employees would be idle on those days. ;)
But it's better to have a permanent table that also flags the holidays.
Also note that the first select on the #EmployeeAssignments is grouped.
Since the tasks cause a lot of duplicate date ranges.
DECLARE #EmployeeAssignments TABLE (Id INT IDENTITY(1,1), EmployeeId INT, TaskId int, SDate DATE, EDate DATE);
INSERT INTO #EmployeeAssignments (EmployeeId, TaskId, SDate, EDate) values
(11505,10,'2016-10-01','2016-10-05'),
(11505,12,'2016-10-09','2016-10-12'),
(11505,13,'2016-10-09','2016-10-12'),
(11505,14,'2016-10-14','2016-10-20'),
(11505,15,'2016-10-14','2016-10-20'),
(11506,16,'2016-10-02','2016-10-05'),
(11506,17,'2016-10-08','2016-10-14'),
(11506,18,'2016-10-15','2016-10-19');
DECLARE #Days TABLE (day DATE primary key);
declare #StartDate DATE = (select min(SDate) from #EmployeeAssignments);
declare #EndDate DATE = (select max(EDate) from #EmployeeAssignments);
-- fill up #Days with workingdays
with DAYS as (
select #StartDate as dt
union all
select dateadd(day,1,dt)
from DAYS
where dt < #EndDate
)
insert into #Days (day)
select dt from DAYS
where DATEPART(dw, dt) in (2,3,4,5,6); -- dw 2 to 6 = monday to friday
IF OBJECT_ID('tempdb..#EmployeeIdleDates') IS NOT NULL DROP TABLE #EmployeeIdleDates;
CREATE TABLE #EmployeeIdleDates (Id INT IDENTITY(1,1) primary key, EmployeeId INT, IdleDate DATE);
insert into #EmployeeIdleDates (EmployeeId, IdleDate)
select
a.EmployeeId,
d.day as IdleDate
from
(
select *
from (
select EmployeeId,
dateadd(day,1,EDate) as StartDateGap,
dateadd(day,-1,lead(SDate) over (partition by EmployeeId order by SDate)) as EndDateGap
from (
select EmployeeId, SDate, EDate
from #EmployeeAssignments
group by EmployeeId, SDate, EDate
) t
) as q
where StartDateGap <= EndDateGap
) a
inner join #Days d
on (d.day between a.StartDateGap and a.EndDateGap)
group by a.EmployeeId, d.day;
select * from #EmployeeIdleDates
order by EmployeeId, IdleDate;
What you need to work out is which dates have no corresponding time period in your source table. The easiest way I have found to tackle this problem is with a Dates table. If you don't have one of these in your database already, I highly recommend it as having a table of every date you'll need with relevant metadata such as whether it is the start or end of the month, weekends, holidays, etc is incredibly useful.
If you can't create one of these, you can derive a simple one using a recursive cte and then return all dates that aren't represented in your source table (This assumes you are reporting on one employee at a time):
declare #Tasks table(TaskID int
,EmployeeID int
,Sdate datetime
,Edate datetime
)
insert into #Tasks values
(1,1,'20160101','20160103')
,(2,1,'20160102','20160107')
,(3,1,'20160109','20160109')
,(4,1,'20160112','20160113')
,(5,1,'20160112','20160112')
,(1,2,'20160101','20160102')
,(2,2,'20160103','20160109')
declare #EmployeeID int = 1
declare #MinDate datetime = (select min(Sdate) from #Tasks where EmployeeID = #EmployeeID)
declare #MaxDate datetime = (select max(Edate) from #Tasks where EmployeeID = #EmployeeID)
;with cte as
(
select #MinDate as DateValue
union all
select dateadd(d,1,DateValue) as DateValue
from cte
where DateValue < #MaxDate
)
select #EmployeeID as EmployeeID
,c.DateValue as DatesIdle
from cte c
left join #Tasks t
on(c.DateValue BETWEEN T.Sdate AND T.Edate)
where t.EmployeeID is null
order by DatesIdle
First and foremost, please reconsider your approach to do this within the DB. The best place for data interpretation is at your application layer.
The below code will give you he gaps in the temp table #gaps. Of course, I have ignored irrelevant to the problem; you might want to add them. I used #temp in place of your table and inserted test values.
DECLARE #temp TABLE (
EmployeeID INT ,
Sdate DATE,
Edate DATE);
INSERT INTO #temp
VALUES (11505, '2016-05-26', '2016-05-26'),
(11505, '2016-05-27', '2016-05-31'),
(11505, '2016-06-01', '2016-06-01'),
(11505, '2016-06-02', '2016-06-03'),
(11505, '2016-06-02', '2016-06-03'),
(11505, '2016-06-05', '2016-06-06'),
(11505, '2016-06-05', '2016-06-06'),
(11505, '2016-06-06', '2016-06-06'),
(11505, '2016-06-06', '2016-06-06'),
(11505, '2016-06-07', '2016-06-08'),
(11505, '2016-06-07', '2016-06-07'),
(11505, '2016-06-07', '2016-06-07'),
(11505, '2016-06-07', '2016-06-07'),
(11505, '2016-06-15', '2016-06-15'),
(11505, '2016-06-16', '2016-06-20'),
(21505, '2016-05-26', '2016-05-26'),
(21505, '2016-05-27', '2016-05-31'),
(21505, '2016-06-01', '2016-06-01'),
(21505, '2016-06-02', '2016-06-03'),
(21505, '2016-06-02', '2016-06-03'),
(21505, '2016-06-02', '2016-06-06'),
(21505, '2016-06-02', '2016-06-06'),
(21505, '2016-06-06', '2016-06-06'),
(21505, '2016-06-06', '2016-06-06'),
(21505, '2016-06-07', '2016-06-08'),
(21505, '2016-07-02', '2016-07-02'),
(21505, '2016-07-03', '2016-07-03'),
(21505, '2016-07-07', '2016-07-10'),
(21505, '2016-07-14', '2016-06-14'),
(21505, '2016-06-13', '2016-06-15');
DECLARE #emp AS INT;
DECLARE #start AS DATE;
DECLARE #end AS DATE;
DECLARE #EmployeeID AS INT,
#Sdate AS DATE,
#Edate AS DATE;
DECLARE #gaps TABLE (
EmployeeID INT ,
Sdate DATE,
Edate DATE);
DECLARE RecSet CURSOR
FOR SELECT *
FROM #temp
ORDER BY EmployeeID ASC, Sdate ASC, Edate DESC;
OPEN RecSet;
FETCH NEXT FROM RecSet INTO #EmployeeID, #Sdate, #Edate;
SET #emp = #EmployeeID;
SET #start = #Sdate;
SET #end = dateadd(day, 1, #Edate);
WHILE (##FETCH_STATUS = 0)
BEGIN
IF #Sdate <= #end
BEGIN
IF #Edate > dateadd(day, -1, #end)
BEGIN
SET #end = dateadd(day, 1, #Edate);
END
END
ELSE
BEGIN
INSERT INTO #gaps
VALUES (#EmployeeID, #end, dateadd(day, -1, #Sdate));
SET #start = #Sdate;
SET #end = dateadd(day, 1, #Edate);
END
FETCH NEXT FROM RecSet INTO #EmployeeID, #Sdate, #Edate;
IF #emp != #EmployeeID
BEGIN
SET #emp = #EmployeeID;
SET #start = #Sdate;
SET #end = dateadd(day, 1, #Edate);
END
END
CLOSE RecSet;
DEALLOCATE RecSet;
SELECT *
FROM #gaps;
This gives #gaps as:
11505 2016-06-04 2016-06-04
11505 2016-06-09 2016-06-14
21505 2016-06-09 2016-06-12
21505 2016-06-16 2016-07-01
21505 2016-07-04 2016-07-06
21505 2016-07-11 2016-07-13
I can't see how to solve this without unrolling the days within a Scope.
Hence I use a Tally table in this example.
I provide here an example of two Persons.
For debugging simplicity I use month units.
select top 100000 identity(int, 1, 1) as Id
into #Tally
from master..spt_values as a
cross join master..spt_values as b
declare
#ScopeB date = '2015-01-01',
#ScopeE date = '2015-12-31'
declare #Task table
(
TaskID int identity,
PersonID int,
TaskB date,
TaskE date
)
insert #Task values
(1, '2015-01-01', '2015-04-30'), -- Person 1 mth 1, 2, 3, 4
(1, '2015-03-01', '2015-07-31'), -- Person 1 mth 3, 4, 5, 6, 7
(2, '2015-01-01', '2015-03-31'), -- Person 2 mth 1, 2, 3
(2, '2015-05-01', '2015-05-31'), -- Person 2 mth 5
(2, '2015-09-01', '2015-11-30') -- Person 2 mth 9, 10, 11
-- result: Person 1 free on mth 8, 9, 10, 11, 12
-- result: Person 2 free on mth 4, 6, 7, 8, 12
;
with
Scope as
(
select dateadd(day, ID - 1, #ScopeB) as Dates
from #Tally where ID <= datediff(day, #ScopeB, #ScopeE) + 1
and datename(dw, dateadd(day, ID - 1, #ScopeB)) not in ('Saturday', 'Sunday')
),
Person as
(
select distinct PersonID from #Task
),
Free as
(
select p.PersonID, s.Dates from Scope as s cross join Person as p
except
select distinct t.PersonID, s.Dates from Scope as s cross join #Task as t
where s.Dates between t.TaskB and t.TaskE
)
select PersonID, Dates,
datename(dw, Dates) from Free
order by 1, 2
drop table #Tally
If you have a Holiday table, you can use it at the WHERE condition at the final SELECT as: WHERE Dates NOT IN (SELECT Dates FROM Holiday).
I have a table like this:
I want to see data in a summary view like this:
I need help for a T-SQL script. Thank you.
Sorry for my little English.
This works:
[Setup]
CREATE TABLE #PaymentTable ( Id INT IDENTITY, AccountGroupId INT, AccountId INT, Payment INT )
INSERT INTO #PaymentTable ( AccountGroupId, AccountId, Payment )
SELECT 1, 1, 5 UNION ALL SELECT 1, 1, 5 UNION ALL
SELECT 1, 2, 5 UNION ALL SELECT 2, 4, 5 UNION ALL
SELECT 2, 3, 5 UNION ALL SELECT 2, 3, 5 UNION ALL
SELECT 2, 4, 5
CREATE TABLE #Group ( AccountGroupId INT, GroupName VARCHAR(100) )
INSERT INTO #Group ( AccountGroupId, GroupName )
SELECT 1, 'Group 1' UNION Select 2, 'Group 2'
CREATE TABLE #Account ( AccountId INT, AccountName VARCHAR(100) )
INSERT INTO #Account ( AccountId, AccountName )
SELECT 1, 'John' UNION Select 2, 'Edvard' UNION
SELECT 3, 'David' UNION SELECT 4, 'Jimi'
[Query]
SELECT
[Group],
Account,
TotalPayment
FROM
(
SELECT
#Group.AccountGroupId AS GroupId,
GroupName AS [Group],
'' AS Account,
SUM( Payment ) AS TotalPayment,
0 AS InnerOrder
FROM
#PaymentTable,
#Group
WHERE
#Group.AccountGroupId = #PaymentTable.AccountGroupId
GROUP BY
#Group.AccountGroupId,
#Group.GroupName
UNION
SELECT
AccountGroupId AS GroupId,
'' AS [Group],
AccountName AS Account,
SUM( Payment ) AS TotalPayment,
1 AS InnerOrder
FROM
#PaymentTable,
#Account
WHERE
#Account.AccountId = #PaymentTable.AccountId
GROUP BY
AccountGroupId,
AccountName
) AS inner_query
ORDER BY
GroupId,
InnerOrder,
Account