How to make a status by comparing the dates - sql

Using SQL Server 2005
Leave Table
ID StartDate EndDate
001 04/01/2010 04/02/2010
002 04/02/2010 04/03/2010
…
Event Table
ID Date PresentDate Status
001 03/30/2010 03/30/2010 Present
001 03/31/2010 null absent
001 04/01/2010 null Leave
001 04/02/2010 null Leave
001 04/03/2010 null absent
001 04/04/2010 04/04/2010 Present
….
All the Datecolumn datatype is datetime
In the Status Column, if Present Date is null then it will display as “absent”, if not null then it will display as “present”. Now if we apply a leave for the date then it will display as “Leave” in status column.
Query
Select
id, date, present date
, CASE WHEN t2.id IS NULL THEN t1.Status ELSE ‘Leave’ END AS status
from event table as t1
left outer join leave table as t2 on
t1.id = t2.id and t1.date between t2.startdate and t2.enddate
The above method is working, but I need to add one more condition.
Once if we applied the leave for the particular employee in the Leave Table then it should compare the Present Date column, if Present Date Column is empty then it should display as “leave”
Expected Output
ID Date PresentDate Status
001 03/30/2010 03/30/2010 Present
001 03/31/2010 null absent
001 04/01/2010 null Leave
001 04/02/2010 null Leave
001 04/03/2010 null Leave (Expect this value)
001 04/04/2010 04/04/2010 Present
….
From the above output Leave is starting from 04/01/2010 to 04/02/2010, then next column of present date is null then status should display as a “Leave”, once present date is not null then it should display as “Present.
Method
We can display as "Leave" in status column from Start Date to end date of leave table, after that leave date end then we can compare with PresentDate column, if PresentDate column is null then it should display as "Leave", once data is available in present column then status should display with normal condition.
How to make a query for the above condition.
Need Query Help

select E.id, E.date, E.presentdate, *,
case
when E.presentdate is not null then 'Present'
when E2.presentdate is not null then 'Absent'
when L.ID is not null then 'Leave'
else 'Absent'
end
from Event E
outer apply (
select top 1 *
from Leave L
where E.presentdate is null and E.date >= L.startdate
AND e.ID = L.ID
order by L.startDate desc) L
outer apply (
select top 1 *
from Event E2
where E.presentdate is null
and E2.presentdate is not null
and E.date >= E2.date and E2.date > L.startdate
AND e2.ID = e.ID
order by E2.presentdate desc) E2
order by E.date
Leave table
ID StartDate EndDate
----------- ----------------------- -----------------------
1 2010-04-01 00:00:00.000 2010-04-02 00:00:00.000
1 2010-04-02 00:00:00.000 2010-04-03 00:00:00.000
1 2010-04-05 00:00:00.000 2010-04-05 00:00:00.000
Output
id date presentdate
----------- ----------------------- ----------------------- -------
1 2010-03-30 00:00:00.000 2010-03-30 00:00:00.000 Present
1 2010-03-31 00:00:00.000 NULL Absent
1 2010-04-01 00:00:00.000 NULL Leave
1 2010-04-02 00:00:00.000 NULL Leave
1 2010-04-03 00:00:00.000 NULL Leave -**
1 2010-04-04 00:00:00.000 2010-04-04 00:00:00.000 Present
1 2010-04-05 00:00:00.000 NULL Leave
1 2010-04-06 00:00:00.000 NULL Leave -**
1 2010-04-07 00:00:00.000 NULL Leave -**
1 2010-04-08 00:00:00.000 2010-04-08 00:00:00.000 Present
1 2010-04-09 00:00:00.000 NULL Absent
1 2010-04-10 00:00:00.000 NULL Absent
1 2010-04-11 00:00:00.000 2010-04-11 00:00:00.000 Present
The ones marked -** are not covered by Leave records, but they show leave because they follow a Leave period, correct? 2010-04-09 for example remains "Absent" because it follows a Present record (without actually being present).

Related

SQL query to check if the next row value is same or different

I am joining two tables based on a common column date. However, the column I am trying to get from one the table (cmg) in this case, should get next row value only if it is different from its previous row's value
Table A
Date comp.no
-----------------------
2019-03-08 5
2019-02-26 5
2019-01-17 5
2019-01-10 5
2018-12-27 5
Table B
Date cmg
-----------------
2019-07-17 NULL
2019-04-20 NULL
2019-02-26 RHB
2019-01-19 NULL
2019-01-17 RHB
2019-01-10 RMB
2018-12-28 NULL
2018-12-27 RHB
2018-12-12 RUB
2018-11-28 RUB
2018-10-20 NULL
2018-07-21 NULL
2018-04-21 NULL
2018-01-20 NULL
2017-10-21 NULL
2017-07-29 NULL
2017-05-07 NULL
2017-02-13 NULL
2016-11-22 NULL
2016-08-29 NULL
2016-06-07 NULL
2016-04-06 RUB
2016-03-21 RUB
2016-03-07 RUB
You can use lag function to compare with previous value. And for the first row you'll need an isnull() check since the first row won't have a previous value.
;with cte as(
select case
when isnull(lag(t2.cmg)over (order by t2.cmg desc),'') <>t2.cmg then 1 else 0 end as isresult
,t2.date,t2.cmg
from TableA t1
inner join TableB t2
on t1.date=t2.date
)
select date,cmg from cte where isresult=1
Use lag():
select date, cmg
from (select b.date, b.cmg, lag(b.cmg) over (order by b.date) as prev_cmg
from a join
b
on a.date = b.date
) b
where prev_cmg is null or prev_cmg <> cmg
order by date;

Select next subsequent change of certain column in a new column

I have a table with a unique index on Contracts of Customers that live in Houses. I want to know the days per house how long it takes when someone moves out (Contract end date) and a new contracts starts. For that I want to know what the first next contract will be in that house, but on the same row as the old contract for a (potentially different) customer.
This how the table currently looks like, I select the top 10 here:
SELECT TOP 10
PMCCONTRACT.ACCOUNTNUM --Customer
,PMCCONTRACT.RENTALOBJECTID --House
,PMCCONTRACT.CONTRACTID --Contract & Unique index of the table
,PMCCONTRACT.VALIDFROM --Contract Start Date
,PMCCONTRACT.VALIDTO --Contract End Date
FROM PMCCONTRACT
Then this rolls out:
ACCOUNTNUM RENTALOBJECTID CONTRACTID VALIDFROM VALIDTO
101852 2488 HC000001 1994-03-01 00:00:00.000 NULL
101136 2489 HC000002 1920-01-01 00:00:00.000 NULL
101352 2491 HC000003 1996-09-16 00:00:00.000 NULL
100687 2492 HC000004 1984-11-01 00:00:00.000 NULL
105160 2499 HC000005 1975-05-02 00:00:00.000 2018-01-31 00:00:00.000
102821 2501 HC000006 1997-09-16 00:00:00.000 NULL
100731 2506 HC000007 1920-01-01 00:00:00.000 2018-11-15 00:00:00.000
102797 2508 HC000008 1998-02-01 00:00:00.000 NULL
102155 2512 HC000009 1981-09-01 00:00:00.000 NULL
102563 2515 HC000010 1965-10-17 00:00:00.000 2017-06-30 00:00:00.000
And what I want is that based on the RENTALOBJECTID it will show what the First Next contract on that house was (so it is important that the CONTRACTID remains unique in this table).
Below is the code I use to get it, however, it shows all the following contract changes for that specific RENTALOBJECTID (House).
SELECT --TOP 1000
PMCCONTRACT.CONTRACTID
,PMCCONTRACT.RENTALOBJECTID
,PMCCONTRACT.VALIDFROM
,PMCCONTRACT.VALIDTO
,P2.CONTRACTID AS 'FirstNextContractId'
,P2.VALIDFROM
,P2.VALIDTO
FROM PMCCONTRACT
LEFT JOIN PMCCONTRACT P2
ON PMCCONTRACT.RENTALOBJECTID = P2.RENTALOBJECTID
LEFT JOIN
(SELECT
RENTALOBJECTID,
MAX(CONTRACTID) AS CONTRACTID
FROM PMCCONTRACT
GROUP BY RENTALOBJECTID) X ON X.CONTRACTID = P2.CONTRACTID
WHERE P2.VALIDFROM > PMCCONTRACT.VALIDTO
This is what I get when I select only ContractID HC000028, it shows 2 rows, while I want it to show only the first row.
CONTRACTID RENTALOBJECTID VALIDFROM VALIDTO FirstNextContractId VALIDFROM2 VALIDTO2
HC000028 75 1995-01-01 00:00:00.000 2016-04-30 00:00:00.000 HC009990 2016-05-01 00:00:00.000 2018-11-25 00:00:00.000 --<< Only row I want to show
HC000028 75 1995-01-01 00:00:00.000 2016-04-30 00:00:00.000 HC025218 2018-11-26 00:00:00.000 1900-01-01 00:00:00.000 --Too far in the future
Kind regards,
Igor
It looks like a simple LEAD window function is enough. It returns the next row, as defined by partitioning and ordering clauses.
SELECT TOP 10
PMCCONTRACT.ACCOUNTNUM --Customer
,PMCCONTRACT.RENTALOBJECTID --House
,PMCCONTRACT.CONTRACTID --Contract & Unique index of the table
,PMCCONTRACT.VALIDFROM --Contract Start Date
,PMCCONTRACT.VALIDTO --Contract End Date
,LEAD(CONTRACTID) OVER (PARTITION BY RENTALOBJECTID ORDER BY VALIDFROM) AS NextContractID
,LEAD(VALIDFROM) OVER (PARTITION BY RENTALOBJECTID ORDER BY VALIDFROM) AS NextVALIDFROM
,LEAD(VALIDTO) OVER (PARTITION BY RENTALOBJECTID ORDER BY VALIDFROM) AS NextVALIDTO
FROM PMCCONTRACT
;

Subtracting from different rows

I am trying to subtract the startdate from the enddate on different rows, but only for the same code.
For example:
I want to do startdate in row 2 for C002 (2012-07-01) minus enddate in row 1 for C002 (2012-06-30).
The result should be 1 (day) for row 2. No data should be in row 1.
Row 4 should show 1 (day) as well.
How can I go about doing this?
row code startdate enddate
1 C002 2011-07-01 00:00:00.000 2012-06-30 00:00:00.000
2 C002 2012-07-01 00:00:00.000 2013-06-30 00:00:00.000
3 C003 2011-07-01 00:00:00.000 2012-06-30 00:00:00.000
4 C003 2012-07-01 00:00:00.000 2013-06-30 00:00:00.000
select max(row),code,datediff(day,max(startdate),min(enddate)) as ouputtt
from table
group by
code
Try this-
Select x.code, y.startdate-x.enddate
From table1 x left outer join table1 y on
X.code=Y.code
Where
X.enddate<y.startdate
You could use this query, which adds the requested value as an additional column:
select row, code, startdate, enddate,
datediff('d', lag(enddate) over (partition by code order by row1), startdate) df
from mytable

Select distinct records based on max(date) or NULL date

I am trying to get a list of employees based on their employee status or their most recent termination date. If the employee is active, the termination date will be NULL. There are also employees that have worked in multiple companies within our organization, I only want the record from the most recent company, whether active or terminated. An employee may also have different Employee numbers in the different companies, so the selection will have to be based on the SSN (Fica) number.
Here is an original data set:
company employee Fica First_name emp_status Term_date
5 7026 Jason T1 2013-09-16 00:00:00.000
500 7026 Jason T1 2010-11-30 00:00:00.000
7 7026 Jason T1 2009-07-31 00:00:00.000
2 90908 Jason A1 NULL
505 293866 William T1 2008-05-23 00:00:00.000
7 7243 Ashley T1 2010-07-11 00:00:00.000
2 90478 Michael T1 2013-01-11 00:00:00.000
500 90478 Michael T1 2011-09-26 00:00:00.000
500 311002 Andreas A1 NULL
3 365463 Matthew A1 NULL
500 248766 Chris T1 2007-04-23 00:00:00.000
500 90692 Kaitlyn T1 2012-03-13 00:00:00.000
2 90692 Kaitlyn A5 NULL
500 90236 Jeff T1 2011-09-26 00:00:00.000
2 90236 Jeff A1 NULL
2 90433 Nathan T1 2012-03-26 00:00:00.000
500 90433 Nathan T1 2011-09-26 00:00:00.000
Here are the results I am trying to get:
company employee Fica First_name emp_status Term_date
2 90908 Jason A1 NULL
505 293866 William T1 2008-05-23 00:00:00.000
7 7243 Ashley T1 2010-07-11 00:00:00.000
2 90478 Michael T1 2013-01-11 00:00:00.000
500 311002 Andreas A1 NULL
3 365463 Matthew A1 NULL
500 248766 Chris T1 2007-04-23 00:00:00.000
2 90692 Kaitlyn A5 NULL
2 90236 Jeff A1 NULL
2 90433 Nathan T1 2012-03-26 00:00:00.000
Thanks for any help you are able to give. I need to run this on a SQL2005 server which will be connecting to an Oracle server via ODBC.
If the dates were all populated, you could do this with a "standard" not exists query. The NULLs introduce a problem, but that problem can be solved using coalesce():
select t.*
from table t
where not exists (select 1
from table t2
where t2.employee = t.employee and
coalesce(t2.term_date, '9999-01-01') > coalesce(t.term_date, '9999-01-01)
);
NOTE: If you need for this to work on Oracle, then you need a different format for the date constant.
EDIT:
Another way to solve this uses row_number():
select t.*
from (select t.*,
row_number() over (partition by employee
order by (case when term_date is null then 0 else 1 end),
term_date desc
) as seqnum
from table t
) t
where seqnum = 1;
The rule for choosing the "last" row are embedded in the order by clause. Put the NULL value first, followed by the term_date in descending order.

Subtition of cursor for combining tables with time periods

I have to combine two tables into one but I have to take validation dates into consideriation. For instance having two tables:
Address
ID AddressValue ValidFrom ValidTo
----------- --------------- ----------------------- -----------------------
1 Pink Street 2010-01-01 00:00:00.000 2010-01-20 00:00:00.000
2 Yellow Street 2010-01-20 00:00:00.000 2010-02-28 00:00:00.000
Phone
ID PhoneValue ValidFrom ValidTo
----------- ------------ ----------------------- -----------------------
1 123456789 2010-01-01 00:00:00.000 2010-01-15 00:00:00.000
2 987654321 2010-01-16 00:00:00.000 2010-01-31 00:00:00.000
I need to do combine them into new one:
NewSystem
ID NewPhone NewAddress ValidFrom ValidTo Version
----------- ----------- --------------- ----------------------- ----------------------- -------
1 123456789 Pink Street 2010-01-01 00:00:00.000 2010-01-15 00:00:00.000 4
2 NULL Pink Street 2010-01-15 00:00:00.000 2010-01-16 00:00:00.000 3
3 987654321 Pink Street 2010-01-16 00:00:00.000 2010-01-20 00:00:00.000 2
4 987654321 Yellow Street 2010-01-20 00:00:00.000 2010-01-31 00:00:00.000 1
5 NULL Yellow Street 2010-01-31 00:00:00.000 2010-02-28 00:00:00.000 0
The idea is quite simple. I create periods based on dates and then query each table in subqueries. I pasted my solution here: http://pastebin.com/cdKePA9X.
Right now I am trying to get rid of the cursor but I failed. I tried to use CTE but without success. Maybe someone of you faced similar problem or know how to combine these tables into one without using cursor. I pasted the 'create table' scripts here: http://pastebin.com/BeRspb6K.
Thank you in advanced.
First, construct new date ranges by merging the date ranges from the source tables. Second, for each new date range, lookup the valid data in the source tables.
WITH
old_ranges(d1,d2) AS (
SELECT ValidFrom,ValidTo FROM #Address UNION
SELECT ValidFrom,ValidTo FROM #Phone
),
new_ranges(d1,d2) AS (
SELECT d,LEAD(d) OVER(ORDER BY d)
FROM (
SELECT DISTINCT d
FROM old_ranges
UNPIVOT(d FOR dx IN (d1,d2)) p
) t
)
SELECT
ROW_NUMBER() OVER (ORDER BY d1) AS ID,
NewPhone,
NewAddress,
d1 AS ValidFrom,
d2 AS ValidTo
FROM new_ranges
OUTER APPLY (
SELECT PhoneValue AS NewPhone
FROM #Phone
WHERE ValidFrom <= d1 AND ValidTo >= d2
) x1
OUTER APPLY (
SELECT AddressValue AS NewAddress
FROM #Address
WHERE ValidFrom <= d1 AND ValidTo >= d2
) x2
WHERE d2 IS NOT NULL