SQL Query for fetching 2 results as single row - sql

I have table like this. I want to get employee records to get their current Designation(whose effectiveto is null) and the date where they FIRST joined as Trainee(min(effectivefrom) where Designation= Trainee)
+----+-------------------+---------------+-------------+
| ID | Designation | EffectiveFrom | EffectiveTo |
+----+-------------------+---------------+-------------+
| 1 | Trainee | 01/01/2000 | 31/12/2000 |
| 1 | Assistant Manager | 01/01/2001 | 31/12/2004 |
| 1 | Suspended | 01/01/2005 | 01/02/2005 |
| 1 | Trainee | 02/03/2005 | 31/03/2005 |
| 1 | Manager | 01/04/2005 | NULL |
| 2 | Trainee | 01/01/2014 | 31/12/2014 |
| 2 | Developer | 01/01/2015 | 31/12/2016 |
| 2 | Architect | 01/01/2017 | NULL |
+----+-------------------+---------------+-------------+
How to get result like this
+----+---------------------+---------------------+
| ID | Current Designation | Date First Employed |
+----+---------------------+---------------------+
| 1 | Manager | 01/01/2000 |
| 2 | Architect | 01/01/2014 |
+----+---------------------+---------------------+

The date of first employment could be located using CROSS APPLY and SELECT TOP(1)
CREATE TABLE #table1(
ID int,
Designation varchar(17),
EffectiveFrom datetime,
EffectiveTo varchar(10));
INSERT INTO #table1
(ID, Designation, EffectiveFrom, EffectiveTo)
VALUES
(1, 'Trainee', '2000-01-01 01:00:00', '31/12/2000'),
(1, 'Assistant Manager', '2001-01-01 01:00:00', '31/12/2004'),
(1, 'Suspended', '2005-01-01 01:00:00', '01/02/2005'),
(1, 'Trainee', '2005-02-03 01:00:00', '31/03/2005'),
(1, 'Manager', '2005-01-04 01:00:00', NULL),
(2, 'Trainee', '2014-01-01 01:00:00', '31/12/2014'),
(2, 'Developer', '2015-01-01 01:00:00', '31/12/2016'),
(2, 'Architect', '2017-01-01 01:00:00', NULL);
select t.ID, t.Designation [Current Designation],
ef.EffectiveFrom [Date First Employed]
from #table1 t
cross apply (select top(1) cast(tt.EffectiveFrom as date) EffectiveFrom
from #table1 tt
where t.ID=tt.ID
and Designation='Trainee'
order by tt.EffectiveFrom) ef
where t.EffectiveTo is null;
ID Current Designation Date First Employed
1 Manager 2000-01-01
2 Architect 2014-01-01

One method is conditional aggregation. It is a bit unclear how you define "current", but assuming this is associated with EffectiveTo being NULL:
select id,
max(case when EffectiveTo is null then designation end) as current_designation,
min(effectivefrom) as start_ate
from t
group by id;

You can try below query:
select id,max(current_designation) current_designation,min(date_first_employee) date_first_employee from
(select id,
max(case when EffectiveTo is null then designation end) over (partition by id) as current_designation,
(case when Designation='Trainee' then EffectiveFrom end) Date_First_Employee
from desig) t
group by id
Output:

This is another possilbe solution
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE table1
(`ID` int, `Designation` varchar(17), `EffectiveFrom` datetime, `EffectiveTo` varchar(10))
;
INSERT INTO table1
(`ID`, `Designation`, `EffectiveFrom`, `EffectiveTo`)
VALUES
(1, 'Trainee', '2000-01-01 01:00:00', '31/12/2000'),
(1, 'Assistant Manager', '2001-01-01 01:00:00', '31/12/2004'),
(1, 'Suspended', '2005-01-01 01:00:00', '01/02/2005'),
(1, 'Trainee', '2005-02-03 01:00:00', '31/03/2005'),
(1, 'Manager', '2005-01-04 01:00:00', NULL),
(2, 'Trainee', '2014-01-01 01:00:00', '31/12/2014'),
(2, 'Developer', '2015-01-01 01:00:00', '31/12/2016'),
(2, 'Architect', '2017-01-01 01:00:00', NULL)
;
Query 1:
SELECT
ID,
(SELECT
`Designation`
FROM
table1
WHERE
`EffectiveFrom` = (SELECT
MAX(`EffectiveFrom`)
FROM
table1
WHERE
ID = t1.ID)) AS `Current Designation`
,DATE(MIN(`EffectiveFrom`)) AS `Date First Employed`
FROM
table1 t1
GROUP BY ID
Results:
| ID | Current Designation | Date First Employed |
|----|---------------------|---------------------|
| 1 | Trainee | 2000-01-01 |
| 2 | Architect | 2014-01-01 |

It's actually a rather simple self-join assuming that the EffectiveFrom and EffectiveTo columns are always filled in appropriately (i.e. there's always only one NULL value for EffectiveTo of a given ID). Since it's possible for someone to be a Trainee twice, you also need to use a window function like ROW_NUMBER() to filter out only the earliest Traininee EffectiveFrom date:
WITH CTE_Designations AS
(
SELECT T1.ID, T1.Designation AS CurrentDesignation, ISNULL(T2.EffectiveFrom, T1.EffectiveFrom) AS DateFirstEmployed -- If the join fails below then that means the earliest Designation is in T1 (e.g. that is the 'Trainee' record)
FROM DesignationsTable AS T1
LEFT JOIN DesignationsTable AS T2
ON T1.ID = T2.ID
AND T1.Designation <> T2.Designation
AND T2.Designation = 'Trainee'
WHERE T1.EffectiveTo IS NULL
),
CTE_Designations_FirstEmployedOnly AS
(
SELECT ID, CurrentDesignation, DateFirstEmployed, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DateFirstEmployed) AS SortId -- Generates a unique ID per DateFirstEmployed row for each Designation.ID sorted by DateFirstEmployed
FROM CTE_Designations
)
SELECT ID, CurrentDesignation, DateFirstEmployed
FROM CTE_Designations_FirstEmployedOnly
WHERE SortId = 1
Results per your example data:
Results if you had an additional person who was still a Trainee:

This returns the result shown for the given data
Solves for "current Designation(whose effectiveto is null) and the date where they FIRST joined". Also handles terminated employees where EffectiveTo is not NULL as well as new employees with a single row. Replace "#t" with your table name.
SELECT a.Id, d.Designation, a.EffectiveFrom
FROM
(SELECT *, ROW_NUMBER() OVER ( PARTITION BY Id ORDER BY EffectiveFrom ASC ) r FROM #t) a
INNER JOIN
(SELECT *, ROW_NUMBER() OVER ( PARTITION BY Id ORDER BY EffectiveFrom DESC ) r FROM #t) d
ON a.Id = d.Id
WHERE a.r = 1 AND d.r = 1
Result:
Id Designation EffectiveFrom
1 Manager 2000-01-01
2 Architect 2014-01-01

Related

Restore values in a time series table

I need to restore the most actual values from a time series table for the other values in specific time.
Let is say, that we have a table like that (I use SQL Server 2016), (this is pseudo-code, I did not check whether it works):
use sample
go
-- create time series table
drop table if exists dbo.PropertyHistory
go
create table dbo.PropertyHistory (
Id int
, Timestamp datetime
, Value int
)
go
-- fill dbo.PropertyHistory
insert into
dbo.PropertyHistory(Id, Timestamp, Value)
values
(1, '2019-01-01 12:00:00', 10)
, (1, '2019-01-01 13:00:00', 20)
, (2, '2019-01-01 13:00:00', 15)
, (3, '2019-01-01 14:00:00', 1)
, (4, '2019-01-01 15:00:00', 10)
, (1, '2019-01-01 16:00:00', 6)
, (4, '2019-01-01 17:00:00', 5)
, (2, '2019-01-01 17:00:00', 50)
, (2, '2019-01-01 19:00:00', 7)
, (1, '2019-01-01 19:00:00', 44)
go
I need to for example each row with the property id = 1 to have the last actual value (actual by datetime of course) of the property id = 2.
| Id | Timestamp | Value | Property2Value |
-------------------------------------------------------
| 1 | 2019-01-01 12:00:00 | 10 | NULL |
| 1 | 2019-01-01 13:00:00 | 20 | 15 |
| 1 | 2019-01-01 16:00:00 | 6 | 15 |
| 1 | 2019-01-01 19:00:00 | 44 | 7 |
-------------------------------------------------------
The ideas:
To create the function kinda of create function A (#propertyId int, #toDateTime datetime) which finds the latest row for the specified value restricted by the datetime. And then for each row with property id = 1 cross apply to this function. The performance is bad.
I think that it is possible to somehow use cumulative sum kinda of sum (case when PropertyId = 2 then Value else 0 end) over (order by Timestamp) but it will be cumulate more and more...
So, please help me to obtain the expected result.
If I understand correctly, this is a good use of apply:
select ph1.*, ph2.value as value2
from propertyhistory ph1 outer apply
(select top (1) ph2.*
from propertyhistory ph2
where ph2.id = 2 and ph2.timestamp <= ph1.timestamp
order by ph2.timestamp desc
) ph2
where ph1.id = 1;
Here is a db<>fiddle.
You can also do this with window functions, with the following logic:
For each row in the original data, get the most recent "2" timestamp.
Get the value for the "2" timestamp.
Filter down to just the "1"s
This looks like:
select ph.*
from (select ph.*,
max(case when ph.id = 2 then ph.value end) over (partition by timestamp_2) as value_2
from (select ph.*,
max(case when ph.id = 2 then ph.timestamp end) over (order by ph.timestamp) as timestamp_2
from propertyhistory ph
) ph
) ph
where id = 1;
We can handle this requirement by a judicious use of ROW_NUMBER, combined with some pivoting logic:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Timestamp DESC) rn
FROM dbo.PropertyHistory
)
SELECT
1 AS Id,
MAX(CASE WHEN Id = 1 THEN Timestamp END) AS Timestamp,
MAX(CASE WHEN Id = 1 THEN Value END) AS Value,
MAX(CASE WHEN Id = 2 THEN Value END) AS Property2Value
FROM cte
GROUP BY
rn
ORDER BY
MAX(CASE WHEN Id = 1 THEN Timestamp END);
Demo
The idea here is to compute a row number label for each record, numbered separately for each Id value. Then, we can aggregate by the row number, which brings the Id values from 1 and 2 into line, in a single record.

Display data for all date ranges including missing dates

I'm having a issue with dates. I have a table with given from and to dates for an employee. For an evaluation, I'd like to display each date of the month with corresponding values from the second sql table.
SQL Table:
EmpNr | datefrom | dateto | hours
0815 | 01.01.2019 | 03.01.2019 | 15
0815 | 05.01.2019 | 15.01.2019 | 15
0815 | 20.01.2019 | 31.12.9999 | 40
The given employee (0815) worked during 01.01.-15.01. 15 hours, and during 20.01.-31.01. 40 hours
I'd like to have the following result:
0815 | 01.01.2019 | 15
0815 | 02.01.2019 | 15
0815 | 03.01.2019 | 15
0815 | 04.01.2019 | NULL
0815 | 05.01.2019 | 15
...
0815 | 15.01.2019 | 15
0815 | 16.01.2019 | NULL
0815 | 17.01.2019 | NULL
0815 | 18.01.2019 | NULL
0815 | 19.01.2019 | NULL
0815 | 20.01.2019 | 40
0815 | 21.01.2019 | 40
...
0815 | 31.01.2019 | 40
as for the dates, we have:
declare #year int = 2019, #month int = 1;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value + 1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year,#month,1)))
)
SELECT b.empnr, b.hours, datefromparts(#year,#month,numbers.value) Datum FROM numbers left outer join
emptbl b on b.empnr = '0815' and (datefromparts(#year,#month,numbers.value) >= b.datefrom and datefromparts(#year,#month,numbers.value) <= case b.dateto )
which is working quite well, yet I have the odd issue, that this code is only shoes the dates between 01.01.2019 and 03.01.2019
thank you very much in advance!
Did you check, if datefrom and dateto is in correct range?
Minimum value of DateTime field is 1753-01-01 and maximum value is 9999-12-31.
Look at your source table to check initial values.
The recursive CTE needs to begin with MIN(datefrom) and MAX(dateto):
DECLARE #t TABLE (empnr INT, datefrom DATE, dateto DATE, hours INT);
INSERT INTO #t VALUES
(815, '2019-01-01', '2019-01-03', 15),
(815, '2019-01-05', '2019-01-15', 15),
(815, '2019-01-20', '9999-01-01', 40),
-- another employee
(999, '2018-01-01', '2018-01-31', 15),
(999, '2018-03-01', '2018-03-31', 15),
(999, '2018-12-01', '9999-01-01', 40);
WITH rcte AS (
SELECT empnr
, MIN(datefrom) AS refdate
, ISNULL(NULLIF(MAX(dateto), '9999-01-01'), CURRENT_TIMESTAMP) AS maxdate -- clamp year 9999 to today
FROM #t
GROUP BY empnr
UNION ALL
SELECT empnr
, DATEADD(DAY, 1, refdate)
, maxdate
FROM rcte
WHERE refdate < maxdate
)
SELECT rcte.empnr
, rcte.refdate
, t.hours
FROM rcte
LEFT JOIN #t AS t ON rcte.empnr = t.empnr AND rcte.refdate BETWEEN t.datefrom AND t.dateto
ORDER BY rcte.empnr, rcte.refdate
OPTION (MAXRECURSION 1000) -- approx 3 years
Demo on db<>fiddle
It could be in your select, try:
SELECT b.empnr, b.hours, datefromparts(#year,#month,numbers.value) Datum
FROM numbers
LEFT OUTER JOIN emptbl b ON b.empnr = '0815' AND
datefromparts(#year,#month,numbers.value) BETWEEN b.datefrom AND b.dateto
Your CTE produces only 31 number and therefore it is showing only January dates.
declare #year int = 2019, #month int = 1;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value + 1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year,#month,1)))
)
SELECT *
FROM numbers
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=a24e58ef4ce522d3ec914f90907a0a9e
You can try below code,
with t0 (i) as (select 0 union all select 0 union all select 0),
t1 (i) as (select a.i from t0 a ,t0 b ),
t2 (i) as (select a.i from t1 a ,t1 b ),
t3 (srno) as (select row_number()over(order by a.i) from t2 a ,t2 b ),
tbldt(dt) as (select dateadd(day,t3.srno-1,'01/01/2019') from t3)
select tbldt.dt
from tbldt
where tbldt.dt <= b.dateto -- put your condition here
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b16469908b323b8d1b98d77dd09bab3d

Date range with minimum and maximum dates from dataset having records with continuous date range

I have a dataset with id ,Status and date range of employees.
The input dataset given below are the details of one employee.
The date ranges in the records are continuous(in exact order) such that startdate of second row will be the next date of enddate of first row.
If an employee takes leave continuously for different months, then the table is storing the info with date range as separated for different months.
For example: In the input set, the employee has taken Sick leave from '16-10-2016' to '31-12-2016' and joined back on '1-1-2017'.
So there are 3 records for this item but the dates are continuous.
In the output I need this as one record as shown in the expected output dataset.
INPUT
Id Status StartDate EndDate
1 Active 1-9-2007 15-10-2016
1 Sick 16-10-2016 31-10-2016
1 Sick 1-11-2016 30-11-2016
1 Sick 1-12-2016 31-12-2016
1 Active 1-1-2017 4-2-2017
1 Unpaid 5-2-2017 9-2-2017
1 Active 10-2-2017 11-2-2017
1 Unpaid 12-2-2017 28-2-2017
1 Unpaid 1-3-2017 31-3-2017
1 Unpaid 1-4-2017 30-4-2017
1 Active 1-5-2017 13-10-2017
1 Sick 14-10-2017 11-11-2017
1 Active 12-11-2017 NULL
EXPECTED OUTPUT
Id Status StartDate EndDate
1 Active 1-9-2007 15-10-2016
1 Sick 16-10-2016 31-12-2016
1 Active 1-1-2017 4-2-2017
1 Unpaid 5-2-2017 9-2-2017
1 Active 10-2-2017 11-2-2017
1 Unpaid 12-2-2017 30-4-2017
1 Active 1-5-2017 13-10-2017
1 Sick 14-10-2017 11-11-2017
1 Active 12-11-2017 NULL
I can't take min(startdate) and max(EndDate) group by id,status because if the same employee has taken another Sick leave then that end date ('11-11-2017' in the example) will come as the End date.
can anyone help me with the query in SQL server 2014?
It suddenly hit me that this is basically a gaps and islands problem - so I've completely changed my solution.
For this solution to work, the dates does not have to be consecutive.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
Id int,
Status varchar(10),
StartDate date,
EndDate date
);
SET DATEFORMAT DMY; -- This is needed because how you specified your dates.
INSERT INTO #T (Id, Status, StartDate, EndDate) VALUES
(1, 'Active', '1-9-2007', '15-10-2016'),
(1, 'Sick', '16-10-2016', '31-10-2016'),
(1, 'Sick', '1-11-2016', '30-11-2016'),
(1, 'Sick', '1-12-2016', '31-12-2016'),
(1, 'Active', '1-1-2017', '4-2-2017'),
(1, 'Unpaid', '5-2-2017', '9-2-2017'),
(1, 'Active', '10-2-2017', '11-2-2017'),
(1, 'Unpaid', '12-2-2017', '28-2-2017'),
(1, 'Unpaid', '1-3-2017', '31-3-2017'),
(1, 'Unpaid', '1-4-2017', '30-4-2017'),
(1, 'Active', '1-5-2017', '13-10-2017'),
(1, 'Sick', '14-10-2017', '11-11-2017'),
(1, 'Active', '12-11-2017', NULL);
The (new) common table expression:
;WITH CTE AS
(
SELECT Id,
Status,
StartDate,
EndDate,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY StartDate)
- ROW_NUMBER() OVER(PARTITION BY Id, Status ORDER BY StartDate) As IslandId,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY StartDate DESC)
- ROW_NUMBER() OVER(PARTITION BY Id, Status ORDER BY StartDate DESC) As ReverseIslandId
FROM #T
)
The (new) query:
SELECT DISTINCT Id,
Status,
MIN(StartDate) OVER(PARTITION BY IslandId, ReverseIslandId) As StartDate,
NULLIF(MAX(ISNULL(EndDate, '9999-12-31')) OVER(PARTITION BY IslandId, ReverseIslandId), '9999-12-31') As EndDate
FROM CTE
ORDER BY StartDate
(new) Results:
Id Status StartDate EndDate
1 Active 01.09.2007 15.10.2016
1 Sick 16.10.2016 31.12.2016
1 Active 01.01.2017 04.02.2017
1 Unpaid 05.02.2017 09.02.2017
1 Active 10.02.2017 11.02.2017
1 Unpaid 12.02.2017 30.04.2017
1 Active 01.05.2017 13.10.2017
1 Sick 14.10.2017 11.11.2017
1 Active 12.11.2017 NULL
You can see a live demo on rextester.
Please note that string representation of dates in SQL should be acccording to ISO 8601 - meaning either yyyy-MM-dd or yyyyMMdd as it's unambiguous and will always be interpreted correctly by SQL Server.
It's an example of GROUPING AND WINDOW.
First you set a reset point for each Status
Sum to set a group
Then get max/min dates of each group.
;with x as
(
select Id, Status, StartDate, EndDate,
iif (lag(Status) over (order by Id, StartDate) = Status, null, 1) rst
from emp
), y as
(
select Id, Status, StartDate, EndDate,
sum(rst) over (order by Id, StartDate) grp
from x
)
select Id,
MIN(Status) as Status,
MIN(StartDate) StartDate,
MAX(EndDate) EndDate
from y
group by Id, grp
order by Id, grp
GO
Id | Status | StartDate | EndDate
-: | :----- | :------------------ | :------------------
1 | Active | 01/09/2007 00:00:00 | 15/10/2016 00:00:00
1 | Sick | 16/10/2016 00:00:00 | 31/12/2016 00:00:00
1 | Active | 01/01/2017 00:00:00 | 04/02/2017 00:00:00
1 | Unpaid | 05/02/2017 00:00:00 | 09/02/2017 00:00:00
1 | Active | 10/02/2017 00:00:00 | 11/02/2017 00:00:00
1 | Unpaid | 12/02/2017 00:00:00 | 30/04/2017 00:00:00
1 | Active | 01/05/2017 00:00:00 | 13/10/2017 00:00:00
1 | Sick | 14/10/2017 00:00:00 | 11/11/2017 00:00:00
1 | Active | 12/11/2017 00:00:00 | null
dbfiddle here
Here's an alternative answer that doesn't use LAG.
First I need to take a copy of your test data:
DECLARE #table TABLE (Id INT, [Status] VARCHAR(50), StartDate DATE, EndDate DATE);
INSERT INTO #table SELECT 1, 'Active', '20070901', '20161015';
INSERT INTO #table SELECT 1, 'Sick', '20161016', '20161031';
INSERT INTO #table SELECT 1, 'Sick', '20161101', '20161130';
INSERT INTO #table SELECT 1, 'Sick', '20161201', '20161231';
INSERT INTO #table SELECT 1, 'Active', '20170101', '20170204';
INSERT INTO #table SELECT 1, 'Unpaid', '20170205', '20170209';
INSERT INTO #table SELECT 1, 'Active', '20170210', '20170211';
INSERT INTO #table SELECT 1, 'Unpaid', '20170212', '20170228';
INSERT INTO #table SELECT 1, 'Unpaid', '20170301', '20170331';
INSERT INTO #table SELECT 1, 'Unpaid', '20170401', '20170430';
INSERT INTO #table SELECT 1, 'Active', '20170501', '20171013';
INSERT INTO #table SELECT 1, 'Sick', '20171014', '20171111';
INSERT INTO #table SELECT 1, 'Active', '20171112', NULL;
Then the query is:
WITH add_order AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY StartDate) AS order_id
FROM
#table),
links AS (
SELECT
a1.Id,
a1.[Status],
a1.order_id,
MIN(a1.order_id) AS start_order_id,
MAX(ISNULL(a2.order_id, a1.order_id)) AS end_order_id,
MIN(a1.StartDate) AS StartDate,
MAX(ISNULL(a2.EndDate, a1.EndDate)) AS EndDate
FROM
add_order a1
LEFT JOIN add_order a2 ON a2.Id = a1.Id AND a2.[Status] = a1.[Status] AND a2.order_id = a1.order_id + 1 AND a2.StartDate = DATEADD(DAY, 1, a1.EndDate)
GROUP BY
a1.Id,
a1.[Status],
a1.order_id),
merged AS (
SELECT
l1.Id,
l1.[Status],
l1.[StartDate],
ISNULL(l2.EndDate, l1.EndDate) AS EndDate,
ROW_NUMBER() OVER (PARTITION BY l1.Id, l1.[Status], ISNULL(l2.EndDate, l1.EndDate) ORDER BY l1.order_id) AS link_id
FROM
links l1
LEFT JOIN links l2 ON l2.order_id = l1.end_order_id)
SELECT
Id,
[Status],
StartDate,
EndDate
FROM
merged
WHERE
link_id = 1
ORDER BY
StartDate;
Results are:
Id Status StartDate EndDate
1 Active 2007-09-01 2016-10-15
1 Sick 2016-10-16 2016-12-31
1 Active 2017-01-01 2017-02-04
1 Unpaid 2017-02-05 2017-02-09
1 Active 2017-02-10 2017-02-11
1 Unpaid 2017-02-12 2017-04-30
1 Active 2017-05-01 2017-10-13
1 Sick 2017-10-14 2017-11-11
1 Active 2017-11-12 NULL
How does it work? First I add a sequence number, to assist with merging contiguous rows together. Then I determine the rows that can be merged together, add a number to identify the first row in each set that can be merged, and finally pick the first rows out of the final CTE. Note that I also have to handle rows that can't be merged, hence the LEFT JOINs and ISNULL statements.
Just for interest, this is what the output from the final CTE looks like, before I filter out all but the rows with a link_id of 1:
Id Status StartDate EndDate link_id
1 Active 2007-09-01 2016-10-15 1
1 Sick 2016-10-16 2016-12-31 1
1 Sick 2016-11-01 2016-12-31 2
1 Sick 2016-12-01 2016-12-31 3
1 Active 2017-01-01 2017-02-04 1
1 Unpaid 2017-02-05 2017-02-09 1
1 Active 2017-02-10 2017-02-11 1
1 Unpaid 2017-02-12 2017-04-30 1
1 Unpaid 2017-03-01 2017-04-30 2
1 Unpaid 2017-04-01 2017-04-30 3
1 Active 2017-05-01 2017-10-13 1
1 Sick 2017-10-14 2017-11-11 1
1 Active 2017-11-12 NULL 1
You could use lag() and lead() function together to check the previous and next status
WITH CTE AS
(
select *,
COALESCE(LEAD(status) OVER(ORDER BY (select 1)), '0') Nstatus,
COALESCE(LAG(status) OVER(ORDER BY (select 1)), '0') Pstatus
from table
)
SELECT * FROM CTE
WHERE (status <> Nstatus AND status <> Pstatus) OR
(status <> Pstatus)

Selecting the second (middle) row between MIN & MAX values in SQL Server

I have the following table:
TicketNumber CallDate
--------------------------------------------
101 10/09/2015 3:15:43 PM
101 10/09/2015 3:45:43 PM
101 11/19/2015 2:23:09 PM
I want to select the min date, the middle date and the max date. It is easy to get the first and last dates using MIN and MAX. But how to SELECT (get) the second date?
SELECT
TicketNumber
, MIN(CallDate) CallDate1
, MAX(CallDate) CallDate3
, COUNT(TicketNumber) [Count]
FROM Table1
WHERE -(conditions)-
GROUP BY TicketNumber
HAVING COUNT(TicketNumber)=3
Between MIN & MAX dates in the SELECT statement I want the second row date.
The expected output should be:
TicketNumber CallDate1 CallDate2 CallDate3 Count
------------------------------------------------------------------------------------------
101 10/9/2015 3:15:43 PM 10/9/2015 3:45:43 PM 11/19/2015 2:23:09 PM 3
Here is one possible variant. At first number and count all rows, then filter only those TicketNumbers that have three tickets and PIVOT result.
SQL Fiddle
Sample data
DECLARE #Tickets TABLE (TicketNumber int, CallDate datetime2(0));
INSERT INTO #Tickets (TicketNumber, CallDate) VALUES
(101, '2015-10-09 03:15:43'),
(101, '2015-10-09 03:45:43'),
(101, '2015-11-19 02:23:09'),
(102, '2015-11-20 02:23:09'),
(102, '2015-11-19 02:23:09'),
(102, '2015-11-21 02:23:09'),
(103, '2015-11-10 02:23:09'),
(103, '2015-11-19 02:23:09'),
(104, '2015-11-11 02:23:09'),
(104, '2015-11-01 02:23:09'),
(104, '2015-11-21 02:23:09'),
(104, '2015-11-30 02:23:09');
Query
WITH
CTE
AS
(
SELECT
TicketNumber
,CallDate
,ROW_NUMBER() OVER (PARTITION BY TicketNumber ORDER BY CallDate) AS rn
,COUNT(*) OVER (PARTITION BY TicketNumber) AS cnt
FROM
#Tickets AS T
)
SELECT
P.TicketNumber
,[1] AS CallDate1
,[2] AS CallDate2
,[3] AS CallDate3
,cnt
FROM
CTE
PIVOT (MIN(CTE.CallDate) FOR rn IN ([1], [2], [3])) AS P
WHERE cnt = 3
ORDER BY P.TicketNumber;
Result
+--------------+---------------------+---------------------+---------------------+-----+
| TicketNumber | CallDate1 | CallDate2 | CallDate3 | cnt |
+--------------+---------------------+---------------------+---------------------+-----+
| 101 | 2015-10-09 03:15:43 | 2015-10-09 03:45:43 | 2015-11-19 02:23:09 | 3 |
| 102 | 2015-11-19 02:23:09 | 2015-11-20 02:23:09 | 2015-11-21 02:23:09 | 3 |
+--------------+---------------------+---------------------+---------------------+-----+
This can be achieved using table JOINS.
SELECT t1.TicketNumber, t2.CallDate1, t1.CallDate AS CallDate2, t2.CallDate3, t2.Count
FROM tickets AS t1
JOIN (
SELECT TicketNumber, MIN(CallDate) AS CallDate1, MAX(CallDate) AS CallDate3,
COUNT(TicketNumber) AS Count
FROM tickets
GROUP BY TicketNumber
HAVING COUNT(TicketNumber)=3
) AS t2
ON t1.TicketNumber = t2.TicketNumber
WHERE t1.CallDate > t2.CallDate1
AND t1.CallDate < t2.CallDate3
Working Fiddle

How To get the First Row Form SQL Group Query?

I have a problem in writing a query.
I'd like to select the first row of each set of rows grouped
My table is Transactions:
userID | Date | StoreID
---------------------------
1 | 8-9-2013 | 10
1 | 9-9-2013 | 10
1 | 10-9-2013| 20
2 | 7-9-2013 | 30
2 | 8-9-2013 | 10
2 | 9-9-2013 | 20
1 | 11-9-2013| 10
2 | 10-9-2013| 20
and I try to this SQL statement:
Select
tr.userID , Min(tr.TransactionDate) FirstDate
From
Transactions tr
Group By
tr.userID
I get this output:
userID | Date
------------------
1 | 8-9-2013
2 | 7-9-2013
But I need the Store ID in every first transaction.
I need it to be like that
userID | Date | StoreID
-------------------------
1 | 8-9-2013 | 10
2 | 7-9-2013 | 30
Please any one can help me
You could use Row_Number().
select UserId, Date, StoreId from (select row_number() over(partition
by UserId order by date) as RowNumber, UserId, Date, StoreId from
Transactions ) as View1 where RowNumber = 1
http://sqlfiddle.com/#!6/e536a/7
You could use a sub-query
SELECT TR1.userID
,TR1.TransactionDate
,TR1.StoreID
FROM Transactions tr1
INNER JOIN
(
Select
tr.userID
,Min(tr.TransactionDate) AS FirstDate
From
Transactions tr
Group By
tr.userID
) SQ
ON TR1.userID = SQ.userID
AND TR1.TransactionDate = SQ.FirstDate
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE Transactions
([userID] int, [Date] datetime, [StoreID] int)
;
INSERT INTO Transactions
([userID], [Date], [StoreID])
VALUES
(1, '2013-08-09 00:00:00', 10),
(1, '2013-09-09 00:00:00', 10),
(1, '2013-10-09 00:00:00', 20),
(2, '2013-07-09 00:00:00', 30),
(2, '2013-08-09 00:00:00', 10),
(2, '2013-09-09 00:00:00', 20),
(1, '2013-11-09 00:00:00', 10),
(2, '2013-10-09 00:00:00', 20)
;
Query 1:
SELECT
tr.userID , Min(tr.Date) FirstDate , tr2.storeid
FROM
Transactions tr
inner join Transactions tr2 on tr.userid = tr2.userid and
tr2.date = (select top 1 date
from transactions t
where t.userid = tr2.userid
order by date asc)
GROUP BY
tr.userID, tr2.storeid
Results:
| USERID | FIRSTDATE | STOREID |
|--------|-------------------------------|---------|
| 1 | August, 09 2013 00:00:00+0000 | 10 |
| 2 | July, 09 2013 00:00:00+0000 | 30 |
with user_cte (userid,date)
as(Select tr.userID , Min(tr.TransactionDate) FirstDate
From Transactions tr
Group By tr.userID
)
select b.userid,b.date,a.storeId from Transactions a join user_cte b on a.userID=b.userId and a.Date=b.Date
You can do this with a subquery. The basic principle is that the subquery identifies the min date for each one, and the wrapping query picks the row that matches the user and min date, also being able to return the store id.
It would look something like this:
SELECT
t.UserID,
t.Date,
t.StoreId
FROM
Transactions t JOIN
(
SELECT
tr.userID , Min(tr.Date) AS FirstDate
FROM
Transactions tr
GROUP BY
tr.userID
) u ON t.UserId = u.UserId AND t.Date = u.FirstDate
You can check this out yourself in SqlFiddle here.