sql query get max date from different table - sql

ok, I have a TankID and a UnitNumber in one table and an ExpiredDate in another table, but that table has multiple expired dates for the same tankID, I just want the max(ExpiredDate).
TankID UnitNumber ExpiredDate
20666 107 2009-08-31 00:00:00
20666 107 2010-08-31 00:00:00
20666 107 2011-08-31 00:00:00
20666 107 2012-08-31 00:00:00
20666 107 NULL
Now I just want to return
20666 107 2012-08-31
I tried to do this:
select tanks.TankID, tanks.UnitNumber, MAX(Table2.ExpireDate)
from Tanks
join Table2 on Tanks.TankID = Table2.TankID
where CompanyID = '1111'
and Tanks.TankID = '22222'
order by Tanks.TankID
That obviously doesn't work, does anybody know how to do this? Thanks

try
SELECT tanks.tankId, tanks.unitNumber, (SELECT MAX(table2.ExpiraDate) FROM table2 WHERE table2.tankID = tanks.tankID) AS max_expire_date
FROM tanks
where CompanyID = '1111'
and Tanks.TankID = '22222'
order by Tanks.TankID

select tanks.TankID, tanks.UnitNumber,
(select MAX(Table2.ExpireDate) from Table2 where table2.TankID = Tanks.TankID) ExpireDate
from Tanks
where CompanyID = '1111'
and Tanks.TankID = '22222'
order by Tanks.TankID
or
select tanks.TankID, tanks.UnitNumber, maxExpireDate.ExpireDate
from Tanks join
(
select TankID, MAX(Table2.ExpireDate) ExpireDate
from Table2
group by TankID
) maxExpireDate
on Tanks.TankID = maxExpireDate.TankID
where CompanyID = '1111'
and Tanks.TankID = '22222'
order by Tanks.TankID

I'm pasting two ways to do this. Both queries do the same thing in a different way. I set up a table variable with mock data so you can test this on a local database (or anywhere) and see that it works.
DECLARE #Tanks TABLE
(
CompanyId INT NOT NULL,
TankId INT NOT NULL,
UnitNumber INT NOT NULL
)
DECLARE #Expirations TABLE
(
TankId INT NOT NULL,
ExpiredDate DATETIME NULL
)
INSERT #Tanks
(
CompanyId
, TankId
, UnitNumber
)
VALUES
(1111, 22222, 107)
INSERT #Expirations
(
TankId
, ExpiredDate
)
VALUES
(22222, '2009-08-31 00:00:00')
, (22222, '2010-08-31 00:00:00')
, (22222, '2011-08-31 00:00:00')
, (22222, '2012-08-31 00:00:00')
, (22222, NULL)
SELECT
Tanks.TankId
, Tanks.UnitNumber
, Expiration.MaxExpiredDate
FROM #Tanks Tanks
CROSS APPLY
(
SELECT MAX(E.ExpiredDate) MaxExpiredDate FROM #Expirations E WHERE E.TankId = Tanks.TankId
) Expiration
WHERE Tanks.CompanyId = 1111 AND Tanks.TankId = 22222
SELECT
Tanks.TankId
, Tanks.UnitNumber
, MAX(Expirations.ExpiredDate) ExpiredDate
FROM #Tanks Tanks
LEFT JOIN #Expirations Expirations
ON Expirations.TankId = Tanks.TankId
WHERE Tanks.CompanyId = 1111 AND Tanks.TankId = 22222
GROUP BY Tanks.TankId, Tanks.UnitNumber

Related

Select hours from one table, but subtract hours from another where conditions exist?

using SQL Server and not sure if blanking or trying to do something I've never done before!
So to simplify, I'm doing a Select query for rostering. I have 2 tables:
[dbo].[LeaveBalances]
[PrimaryKey] [Employee] [LeaveType] [AsAtDate] [Balance]
1 100000 ANN 2017-10-23 10
2 100000 SIC 2017-10-23 16
[dbo].[P_R]
[PrimaryKey] [Employee] [ShiftDate] [LeaveType] [Hrs]
1 100000 2017-10-21 ANN 5
2 100000 2017-10-24 ANN 2
3 100000 2017-10-25 SIC 7
4 123456 2017-10-25 ANN 8
The result I want for the query to get the leave balances, net of anything taken after the [AsAtDate], is:
[Employee] [LeaveType] [AsAtDate] [Balance]
100000 ANN 2017-10-23 8
100000 SIC 2017-10-23 9
So the 2017-10-21 leave taken will be ignored because it's before the date, and the employee 123456 leave will be ignored because it's a different employee, but the other two will be subtracted from the corresponding leave type balances.
The query I've got so far is:
SELECT [Employee]
,[LeaveType]
,[AsAtDate]
,[Balance] -
(SELECT SUM([Hrs]) FROM [dbo].[P_R] WHERE [Employee] = 100000)
AS [Balance]
FROM [dbo].[LeaveBalances]
WHERE [Employee] = 100000
But obviously that's going so subtract all the [Hrs] values for employee 100000 from every leave type, regardless of [P_R] leave type or date. How do I incorporate in those other conditions?
Basically you want to select all LeaveBalances records and sum hours of P_R. You can do this in a subquery in the SELECT clause:
select
employee,
leavetype,
asatdate,
balance - (select sum(hrs) from dbo.p_r p where p.employee = b.employee
and p.leavetype = b.leavetype
and p.shiftdate > b.asatdate
) as balance
from dbo.leavebalances b
order by employee, leavetype;
Or move the subquery to your FROM clause with OUTER APPLY:
select
employee,
leavetype,
asatdate,
balance - coalesce(sums.total, 0) as balance
from leavebalances b
outer apply
(
select sum(hrs) as total
from p_r p
where p.employee = b.employee
and p.leavetype = b.leavetype
and p.shiftdate > b.asatdate
) sums
order by employee, leavetype;
This Can be achive by Using CTE
BEGIN TRAN
CREATE TABLE [dbo].[LeaveBalances]([ID] INT, [Employee] NVARCHAR(255), [LeaveType] NVARCHAR(6) , [AsAtDate] DATETIME, [Balance] INT)
CREATE TABLE[dbo].[P_R](ID INT,[Employee]NVARCHAR(255), [ShiftDate] DATETIME, [LeaveType] NVARCHAR(6) , [Hrs] INT)
INSERT INTO LeaveBalances
SELECT 1,'100000','ANN','2017-10-23',10 UNION ALL
SELECT 2,'100000','SIC','2017-10-23',16
INSERT INTO P_R
SELECT 1,'100000','2017-10-21','ANN',5 UNION ALL
SELECT 2,'100000','2017-10-24','ANN', 2 UNION ALL
SELECT 3 ,'100000','2017-10-25','SIC', 7 UNION ALL
SELECT 4,'123456','2017-10-25','ANN',8
;WITH CTE AS
(
SELECT DISTINCT a.Employee , a.LeaveType , a.AsAtDate , a.Balance
FROM LeaveBalances a INNER JOIN P_R P ON a.Employee=P.Employee
WHERE p.ShiftDate>a.AsAtDate
GROUP BY a.Employee , a.LeaveType , a.AsAtDate , a.Balance,p.Hrs
)
SELECT a.Employee , a.LeaveType , a.AsAtDate , a.Balance-b.Hrs Balance
FROM CTE a
INNER JOIN P_R b ON a.Employee=b.Employee AND a.LeaveType=b.LeaveType
WHERE b.ShiftDate > a.AsAtDate
ROLLBACK TRAN
I actually managed to come up with a solution that seems to work, no idea which method would be most efficient, though, but this one seems elegant enough:
SELECT [LB].[Employee]
,[LB].[LeaveType]
,[LB].[AsAtDate]
,[LB].[Balance] -
SUM(CASE WHEN [R].[LeaveType] = [LB].[LeaveType]
AND [R].[ShiftDate] > [LB].[AsAtDate]
THEN [R].[Hrs] ELSE 0 END) [Balance]
FROM [dbo].[LeaveBalances] [LB]
LEFT JOIN [dbo].[P_R] [R] ON [LB].[Employee] = [R].[Employee]
WHERE [LB].[Employee] = 100000 AND NOT [LB].[Entitlement] = 0
GROUP BY [LB].[LeaveType]
,[LB].[Employee]
,[LB].[AsAtDate]
GO
I managed to work out the following query. In the CTE below, I aggregate the number of sick and annual days spent by each employees after the AsAtDate specified in the LeaveBalances table. Note that I had to do a distinct select on LeaveBalances to isolate this cutoff date for each employee, implying that your data is not normalized.
WITH cte AS (
SELECT t1.[Employee], t1.[LeaveType], SUM(t1.[Hrs]) AS [Hrs]
FROM [dbo].[P_R] t1
INNER JOIN
(
SELECT DISTINCT [Employee], [AsAtDate]
FROM [dbo].[LeaveBalances]
) t2
ON t1.[Employee] = t2.[Employee]
WHERE t1.[ShiftDate] > t2.[AsAtDate]
GROUP BY t1.[Employee], t1.[LeaveType]
)
SELECT
t1.[Employee], t1.[LeaveType], t1.[AsAtDate],
t1.[Balance] - COALESCE(t2.[Hrs], 0) AS [Balance]
FROM [dbo].[LeaveBalances] t1
LEFT JOIN cte t2
ON t1.[Employee] = t2.[Employee] AND
t1.[LeaveType] = t2.[LeaveType];
Output:
Demo here:
Rextester
CREATE TABLE #LeaveBalances([ID] INT, [Employee] NVARCHAR(255), [LeaveType] NVARCHAR(6) , [AsAtDate] DATETIME, [Balance] INT)
CREATE TABLE #P_R(ID INT,[Employee]NVARCHAR(255), [ShiftDate] DATETIME, [LeaveType] NVARCHAR(6) , [Hrs] INT)
INSERT INTO #LeaveBalances
SELECT 1,'100000','ANN','2017-10-23',10 UNION ALL
SELECT 2,'100000','SIC','2017-10-23',16
INSERT INTO #P_R
SELECT 1,'100000','2017-10-21','ANN',5 UNION ALL
SELECT 2,'100000','2017-10-24','ANN', 2 UNION ALL
SELECT 3 ,'100000','2017-10-25','SIC', 7 UNION ALL
SELECT 4,'123456','2017-10-25','ANN',8;
;WITH CTE AS (
SELECT LBS.Employee,LBS.LeaveType,LBS.AsAtDate,LBS.Balance,#P_R.Hrs,
ROW_NUMBER()OVER(
PARTITION BY LBS.Employee,LBS.LeaveType
ORDER BY LBS.LeaveType,#P_R.ShiftDate DESC
) AS RN
FROM #LeaveBalances AS LBS INNER JOIN #P_R
ON (LBS.Employee=#P_R.Employee)
AND
(LBS.LeaveType=#P_R.LeaveType)
)
SELECT Employee,LeaveType,CAST(AsAtDate AS DATE) AS AsAtDate,
SUM(Balance-Hrs)over(PARTITION BY Employee,LeaveType ORDER BY LeaveType) AS Balance
from CTE WHERE RN=1;
------------------------------------------
Employee LeaveType AsAtDate Balance
---------------------------------------------
100000 ANN 2017-10-23 8
100000 SIC 2017-10-23 9

Find Overlapping Dates and Return Overlapping Records

What I need is to return all records that may overlap each other.
-- Create Temp Table
CREATE TABLE #Overlap
(SubType varchar(50),
Cause varchar(9),
CircuitID varchar(100),
BegDate date,
EndDate date,
AmtSought decimal(11,2),
Remarks varchar(max))
--Insert records
INSERT INTO #Overlap VALUES('Original','201500018','36/KQ--/831670/IP /NUVX/','2016-12-01','2016-12-31',354.41,'Rec 1')
INSERT INTO #Overlap VALUES('Original','201500009','36/VCID/826061/IP/NUVX','2016-08-11','2016-08-12',200.50,'Rec 2')
INSERT INTO #Overlap VALUES('Original','201500018','36/KQ--/831670/IP /NUVX/','2016-11-15','2016-12-14',100.25,'Rec 3')
INSERT INTO #Overlap VALUES('Original','201500018','36/KQ--/831670/IP /NUVX/','2016-12-16','2017-01-15',300.75,'Rec 4')
INSERT INTO #Overlap VALUES('Original','201500018','36/KQ--/831670/IP /NUVX/','2017-01-01','2017-01-01',500.00,'Rec 5')
INSERT INTO #Overlap VALUES('Original','201500009','36/VCID/826061/IP/NUVX','2016-07-01','2016-07-31',100.50,'Rec 6')
My result would look like:
SubType Cause CircuitID BegDate EndDate AmtSought Remarks
------------------- -------------------------- ---------- ---------- --------- --------
Original 201500018 36/KQ--/831670/IP /NUVX/ 2016-11-15 2016-12-14 100.25 Rec 3
Original 201500018 36/KQ--/831670/IP /NUVX/ 2016-12-01 2016-12-31 354.41 Rec 1
Original 201500018 36/KQ--/831670/IP /NUVX/ 2016-12-16 2017-01-15 300.75 Rec 4
Original 201500018 36/KQ--/831670/IP /NUVX/ 2017-01-01 2017-01-01 500.00 Rec 5
I have tried this from a sample code I saw but it is not returning the desired result.
Select * from #Overlap a
Inner Join #Overlap b
on a.SubType = b.SubType
And a.Cause = b.Cause
And a.CircuitID = b.CircuitID
And b.BegDate between a.BegDate and a.endDate
And b.BegDate < a.endDate
Assuming that your #Overlap table has a primary key (or unique key) TableId
You could use this following query
SELECT * FROM #Overlap o
WHERE EXISTS
(
SELECT 1 FROM #Overlap o2
WHERE o2.SubType = o.SubType
AND o2.Cause = o.Cause
AND o2.CircuitID = o.CircuitID
AND (
o2.BegDate BETWEEN o.BegDate AND o.EndDate
OR o2.EndDate BETWEEN o.BegDate AND o.EndDate
OR o.BegDate BETWEEN o2.BegDate AND o2.EndDate
OR o.EndDate BETWEEN o2.BegDate AND o2.EndDate
)
AND o2.TableId != o.TableId
)
ORDER BY o.SubType, o.Cause, o.BegDate
Demo link: http://rextester.com/KBFX30109
As #HABO suggestion, you also can use to check overlapping
AND o2.BegDate <= o.EndDate
AND o.BegDate <= o2.EndDate
You need to check start overlap and end overlap. In case, startdate is always less than endDate, following query can be used. The CTE is not required if you have some primary key:
; WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER(
PARTITION BY (SELECT NULL)
ORDER BY (SELECT NULL)
) AS pk FROM #Overlap
)
SELECT a.*, b.pk, b.BegDate, b.endDate FROM CTE a
CROSS JOIN CTE b
WHERE -- Simplify by adding PK
(a.pk <> b.pk)
AND (CASE WHEN (a.BegDate > b.BegDate) THEN a.BegDate ELSE b.BegDate END)
<= (CASE WHEN (a.endDate < b.endDate) THEN a.endDate ELSE b.endDate END)

Any one help me to solve this i try my best but did not solve this?

ItemName Price CreatedDateTime
New Card 50.00 2014-05-26 19:17:09.987
Recharge 110.00 2014-05-26 19:17:12.427
Promo 90.00 2014-05-27 16:17:12.427
Membership 70.00 2014-05-27 16:17:12.427
New Card 50.00 2014-05-26 19:20:09.987
Out Put : Need a query which Sum the sale of Current hour and
sale of item which have maximum sale in that hour in breakdownofSale
Column.
Hour SaleAmount BreakDownOfSale
19 210 Recharge
16 160 Promo
This should do it
create table #t
(
ItemName varchar(50),
Price decimal(18,2),
CreatedDateTime datetime
);
set dateformat ymd;
insert into #t values('New Card', 50.00, '2014-05-26 19:17:09.987');
insert into #t values('Recharge', 110.00, '2014-05-26 19:17:12.427');
insert into #t values('Promo', 90.00, '2014-05-27 16:17:12.427');
insert into #t values('Membership', 70.00, '2014-05-27 16:17:12.427');
insert into #t values('New Card', 50.00, '2014-05-26 19:20:09.987');
with cte as
(
select datepart(hh, CreatedDateTime) as [Hour],
ItemName,
Price,
sum(Price) over (partition by datepart(hh, CreatedDateTime)) SaleAmount,
ROW_NUMBER() over (partition by datepart(hh, CreatedDateTime) order by Price desc) rn
from #t
)
select Hour,
SaleAmount,
ItemName
from cte
where rn = 1
Though i am not clear with the question, based on your desired output, you may use the query as below.
SELECT DATEPART(HOUR,CreatedDateTime) AS Hour, sum(Price) AS Price, ItemName AS BreakDownOfSale from TableName WHERE BY ItemName,DATEPART(HOUR,CreatedDateTime)
Replace table name and column name with the actual one.
Hope this helps!
Here is the sample query.
You can use SQL Server Windows functions to get the result you need.
DECLARE #Table TABLE
(
ItemName NVARCHAR(40),
Price DECIMAL(10,2),
CreatedDatetime DATETIME
)
-- Fill table.
INSERT INTO #Table
( ItemName, Price, CreatedDatetime )
VALUES
( N'New Card' , 50.00 , '2014-05-26 19:17:09.987' ),
( N'Recharge' , 110.00 , '2014-05-26 19:17:12.427' ) ,
( N'Promo' , 90.00 , '2014-05-27 16:17:12.427' ) ,
( N'Membership' , 70.00 , '2014-05-27 16:17:12.427' ) ,
( N'New Card' , 50.00 , '2014-05-26 19:20:09.987' )
-- Check record(s).
SELECT * FROM #Table
-- Get record(s) in required way.
;WITH T1 AS
(
SELECT
DATEPART(HOUR, T.CreatedDatetime) AS Hour,
CONVERT(DATE, T.CreatedDatetime) AS Date,
T.ItemName AS BreakDownOfSales,
-- Date and hour both will give unique record(s)
SUM(Price) OVER (PARTITION BY CONVERT(DATE, T.CreatedDatetime), DATEPART(HOUR, CreatedDateTime)) AS SaleAmount,
ROW_NUMBER() OVER(PARTITION BY CONVERT(DATE, T.CreatedDatetime), DATEPART(HOUR, T.CreatedDatetime) ORDER BY T.Price DESC) AS RN
FROM
#Table T
)
SELECT
T1.Date ,
T1.Hour ,
T1.SaleAmount,
T1.BreakDownOfSales
FROM
T1
WHERE T1. RN = 1
ORDER BY
T1.Hour
Check this simple solution, Please convert it to SQL Server Query.
This will give you perfect result even if you have multiple date data.
SELECT HOUR(CreatedDateTime), SUM(Price),
(SELECT itemname FROM t it WHERE HOUR(ot.CreatedDateTime) = HOUR(it.CreatedDateTime) AND
DATE(ot.CreatedDateTime) = DATE(it.CreatedDateTime)
GROUP BY itemname
ORDER BY price DESC
LIMIT 1
) g
FROM t ot
GROUP BY HOUR(CreatedDateTime);

Dynamic PIVOT over dynamic and multiple columns

i'm trying to pivot dynamic pivot on multiple columns in SQL Server 2012
My table is as below
customer UnitNo invoiceNo invDate heads Amt periodFrmDT periodToDT
abc GF-3 C0000001 2015-11-01 Charge1 100 2015-11-01 2015-11-30
abc GF-3 C0000001 2015-11-01 Charge2 500 2015-11-10 2015-12-10
abc GF-3 C0000001 2015-11-01 charge3 600 2015-10-01 2015-10-30
and i want result like below
customer unitNo invoiceNo invDate Charge1 PeriodFrmDT periodToDT Charge2 PeriodFrmDT periodToDT Charge3 PeriodFrmDT periodToDT
abc GF-3 C0000001 2015-11-01 100 2015-11-01 2015-11-30 500 2015-11-10 2015-12-10 600 2015-10-01 2015-10-30
i have tried yet
select * from (
select c.Customer,unitNo, ih.invoiceNo
,bh.heads,it.Amt,periodFrmDT,PeriodToDT
from invHeader ih
inner join customer c on c.custID =ih.custID
inner join mstUnit mu on mu.unitID=ih.unitID
inner join invTran it on it.invID=ih.invHeaderID
inner join billingHeads bh on bh.BillHeadID=it.headID
)P
Pivot
(
max(amt)
for Head in([charge1],[charge2],[charge3])
) as pvt
Please help..
This will give you the table you have specified. The key thing here is that you must unpivot the amount, from date and to date for each charge first. This then allows you to repivot and see the values for all 3.
First I faked your data as follows:
declare #cte table (customer nvarchar(4)
, UnitNo nvarchar(4)
, invoiceNo nvarchar(8)
, invDate datetime
, heads nvarchar(8)
, Amt int
, periodFrmDT datetime
, periodToDT datetime)
insert into #cte ( customer, UnitNo, invoiceNo, invDate, heads, Amt, periodFrmDT, periodToDT)
values
('abc', 'GF-3', 'C0000001', '2015-11-01', 'Charge1', 100, '2015-11-01', '2015-11-30'),
('abc', 'GF-3', 'C0000001', '2015-11-01', 'Charge2', 500, '2015-11-10', '2015-12-10'),
('abc', 'GF-3', 'C0000001', '2015-11-01', 'charge3', 600, '2015-10-01', '2015-10-30');
Then I used the following query to get out the data as you requested:
with mycte as (select * from #cte
)
select * from
(
select customer, UnitNo ,invoiceNo ,invDate, heads + '_' + columnname as heads, value
from (select customer, UnitNo, invoiceNo, invDate, heads
, cast(Amt as nvarchar(10)) as Amt
, left(convert(nvarchar(10), periodFrmDT, 120), 10) as periodFrm
, left(convert(nvarchar(10), periodToDT, 120), 10) as periodTo
from mycte
) as t1
unpivot(value for columnname in (Amt, periodFrm ,periodTo)) as t2
) as t3 -- this table gives you your initial columns, then 2 more with the column headings for the next stage, and the values
-- columnname contains [Charge1_Amt], [Charge1_periodFrm], [Charge1_periodTo] etc
pivot (min(value) for heads in ( [Charge1_Amt], [Charge1_periodFrm], [Charge1_periodTo]
, [Charge2_Amt], [Charge2_periodFrm], [Charge2_periodTo]
, [charge3_Amt] , [charge3_periodFrm], [charge3_periodTo]
)
) as t4 -- And then we can re-pivot it all back up again.
To use the second query directly from you data, replace
with mycte as (select * from #cte
)
with
with mycte as (
-- This is the query with your base data
select c.Customer,unitNo, ih.invoiceNo, invDate ,bh.heads,it.Amt,periodFrmDT,PeriodToDT
from invHeader ih
inner join customer c on c.custID =ih.custID
inner join mstUnit mu on mu.unitID=ih.unitID
inner join invTran it on it.invID=ih.invHeaderID
inner join billingHeads bh on bh.BillHeadID=it.headID
)

SQL query for getting active employees in specific period

Having the following table:
ID EmployeeID Status EffectiveDate
------------------------------------------------------
1 110545 Active 01AUG2011
2 110700 Active 05JAN2012
3 110060 Active 05JAN2012
4 110222 Active 30JUN2012
5 110545 Resigned 01JUL2012
6 110545 Active 12FEB2013
How do I get the number of active (or partially active) in a specific period?
For example, if I want to know all active (or partially active) employees from 01JAN2011 to 01AUG2012 I should get 4 (according to the table above). If I want to know all active employees from 01AUG2012 to 01JAN2013 it should be 3 only (because employee 110454 is resigned).
How will I do that?
Sample data:
CREATE TABLE #Employee
(
ID integer NOT NULL,
EmployeeID integer NOT NULL,
[Status] varchar(8) NOT NULL,
EffectiveDate date NOT NULL,
CONSTRAINT [PK #Employee ID]
PRIMARY KEY CLUSTERED (ID)
);
INSERT #Employee
(ID, EmployeeID, [Status], EffectiveDate)
VALUES
(1, 110545, 'Active', '20110801'),
(2, 110700, 'Active', '20120105'),
(3, 110060, 'Active', '20120105'),
(4, 110222, 'Active', '20120630'),
(5, 110545, 'Resigned', '20120701'),
(6, 110545, 'Active', '20130212');
Helpful indexes:
CREATE NONCLUSTERED INDEX Active
ON #Employee
(EffectiveDate)
INCLUDE
(EmployeeID)
WHERE
[Status] = 'Active';
CREATE NONCLUSTERED INDEX Resigned
ON #Employee
(EmployeeID, EffectiveDate)
WHERE
[Status] = 'Resigned';
Solution with comments in-line:
CREATE TABLE #Selected (EmployeeID integer NOT NULL);
DECLARE
#start date = '20110101',
#end date = '20120801';
INSERT #Selected (EmployeeID)
SELECT
E.EmployeeID
FROM #Employee AS E
WHERE
-- Employees active before the end of the range
E.[Status] = 'Active'
AND E.EffectiveDate <= #end
AND NOT EXISTS
(
SELECT *
FROM #Employee AS E2
WHERE
-- No record of the employee
-- resigning before the start of the range
-- and after the active date
E2.EmployeeID = E.EmployeeID
AND E2.[Status] = 'Resigned'
AND E2.EffectiveDate >= E.EffectiveDate
AND E2.EffectiveDate <= #start
)
OPTION (RECOMPILE);
-- Return a distinct list of employees
SELECT DISTINCT
S.EmployeeID
FROM #Selected AS S;
Execution plan:
SQLFiddle here
1. Turn your events into ranges:
ID EmployeeID Status EffectiveDate ID EmployeeID Status StartDate EndDate
-- ---------- -------- ------------- -- ---------- -------- --------- ---------
1 110545 Active 01AUG2011 1 110545 Active 01AUG2011 01JUL2012
2 110700 Active 05JAN2012 2 110700 Active 05JAN2012 31DEC9999
3 110060 Active 05JAN2012 => 3 110060 Active 05JAN2012 31DEC9999
4 110222 Active 30JUN2012 4 110222 Active 30JUN2012 31DEC9999
5 110545 Resigned 01JUL2012 5 110545 Resigned 01JUL2012 12FEB2013
6 110545 Active 12FEB2013 6 110545 Active 12FEB2013 31DEC9999
2. Get active employees based on this condition:
WHERE Status = 'Active'
AND StartDate < #EndDate
AND EndDate > #StartDate
3. Count distinct EmployeeID values.
This is how you could implement the above:
WITH ranked AS (
SELECT
*,
rn = ROW_NUMBER() OVER (PARTITION BY EmployeeID ORDER BY EffectiveDate)
FROM EmployeeActivity
),
ranges AS (
SELECT
s.EmployeeID,
s.Status,
StartDate = s.EffectiveDate,
EndDate = ISNULL(e.EffectiveDate, '31DEC9999')
FROM ranked s
LEFT JOIN ranked e ON s.EmployeeID = e.EmployeeID AND s.rn = e.rn - 1
)
SELECT
ActiveCount = COUNT(DISTINCT EmployeeID)
FROM ranges
WHERE Status = 'Active'
AND StartDate < '01JAN2013'
AND EndDate > '01AUG2012'
;
A SQL Fiddle demo for this query: http://sqlfiddle.com/#!3/c3716/3
This should work (not tested)
SELECT COUNT DISTINCT EmployeeID FROM TABLE
WHERE EffectiveDate > CONVERT(VARCHAR(11), '08-01-2012', 106) AS [DDMONYYYY]
and EffectiveDate < CONVERT(VARCHAR(11), '01-01-2013', 106) AS [DDMONYYYY]
AND Status = 'Active'
Another solution using the PIVOT operator
DECLARE #StartDate date = '20120801',
#EndDate date = '20130101'
SELECT COUNT(*)
FROM (
SELECT EffectiveDate, EmployeeID, [Status]
FROM EmployeeActivity
WHERE EffectiveDate < #EndDate
) x
PIVOT
(
MAX(EffectiveDate) FOR [Status] IN([Resigned], [Active])
) p
WHERE ISNULL(Resigned, '99991231') > #StartDate
See demo on SQLFiddle
This should work fine:
DECLARE #d1 date = '01AUG2012';
DECLARE #d2 date = '01JAN2014';
WITH CTE_Before AS
(
--Last status of each employee before period will be RN=1
SELECT *, ROW_NUMBER() OVER (PARTITION BY EmployeeID ORDER BY EffectiveDate DESC) RN
FROM dbo.Table1
WHERE EffectiveDate < #d1
)
, CTE_During AS
(
--Those who become active during period
SELECT * FROM dbo.Table1
WHERE [Status] = 'Active' AND EffectiveDate BETWEEN #d1 AND #d2
)
--Union of those who were active at the beginning of period and those who became active during period
SELECT EmployeeID FROM CTE_Before WHERE RN = 1 AND Status = 'Active'
UNION
SELECT EmployeeID FROM CTE_During
SQLFiddle DEMO
You can use this query to build a list of employees and their start/resignation dates:
select
start.*,
resignation.EffectiveDate as ResignationDate
from Employment start
outer apply (
select top 1
Id,
EmployeeId,
EffectiveDate
from Employment
where EmployeeId = start.EmployeeId
and Status = 'Resigned'
and Id > start.Id
order by Id
) resignation
where start.Status='Active'
The key here is the use of OUTER APPLY, which allows us to use a pretty "funky" join criterion.
Here's how it works: http://www.sqlfiddle.com/#!3/ec969/7
From here, it's just a matter of querying the records whose the employment interval overlaps the target interval.
There are many ways to write this, but I personally like using a CTE, because I find it a bit more readable:
;with EmploymentPeriods as (
select
start.EmployeeId,
start.EffectiveDate as StartDate,
isnull(resignation.EffectiveDate, '9999-01-01') as EndDate
from Employment start
outer apply (
select top 1
Id,
EmployeeId,
EffectiveDate
from Employment
where EmployeeId = start.EmployeeId
and Status = 'Resigned'
and Id > start.Id
order by Id
) resignation
where start.Status='Active'
)
select distinct EmployeeId
from EmploymentPeriods
where EndDate >= #QueryStartDate
and StartDate <= #QueryEndDate
SQLFiddles:
first interval: http://www.sqlfiddle.com/#!3/ec969/27
second interval: http://www.sqlfiddle.com/#!3/ec969/26