Retrieve value from the closest available date - sql

I have a system which stores the history of a balance in a history table.
The table has the account number, sort code, balance, balance start date, balance end date.
When a balance is updated an entry is created in the history table which shows what the balance, what the date was when that balance first started and the date which shows when the balances changed. So for example the table will show a balance of $100.00 and this balance ran from 07/10/2013 to 07/15/2013.
What I'm trying to do is get the sum of the balances for all sort codes on a specific day however the balance may not have changed on this date so I would need to return the closest prior date but I'm failing.
This is what I tried so far.
declare #sdate datetime
set #sdate = '06/08/2012' --mm/dd/yyyy
SELECT CONVERT(varchar(20),MAX(StartDate),103) as "Closest Start Date", Sort, SUM(Balance) AS "Sum of balances"
FROM BalanceHistory
WHERE StartDate <= convert(smalldatetime ,#sdate) AND SortCode <> 'ABC123456'
GROUP BY SortCode
SELECT FROM BalanceHistory would produce something like
AccountNumber, SortCode, Balance, StartDate, EndDate, RECID
00000001, srt010203, 100.00, 06/01/2013, 06/02/2013, RECID
00000001, srt010203, 110.00, 06/02/2013, 06/03/2013, RECID
00000001, srt010203, 120.00, 06/03/2013, 06/04/2013, RECID
00000002, srt010204, 200.00, 06/01/2013, 06/02/2013, RECID
00000003, srt010204, 300.00, 06/01/2013, 06/02/2013, RECID
00000004, srt010205, 400.00, 06/01/2013, 06/02/2013, RECID
00000005, srt010205, 500.00, 06/01/2013, 06/02/2013, RECID

You can do this without a JOIN by using the ROW_NUMBER() function (assuming SQL Server 2005 or newer):
DECLARE #sdate DATE
SET #sdate = '2012-06-08'
SELECT SortCode, SUM(Balance)'Sum of Balances'
FROM (SELECT AccountNumber,SortCode, Balance,ROW_NUMBER() OVER (PARTITION BY AccountNumber ORDER BY StartDate DESC)'RowRank'
FROM BalanceHistory
WHERE StartDate <= #sdate AND SortCode <> 'ABC123456'
)sub
WHERE RowRank = 1
GROUP BY SortCode
Demo: SQL Fiddle
The ROW_NUMBER() function in the subquery assigns a 'RowRank' to the balance for each accountnumber, we order by StartDate DESC to get rank of '1' for the most recent balance for each accountnumber, the WHERE criteria limits it to most recent balance from the date you set in your variable. Then you use that rank in the outer query to limit only to that one balance.

This should work
Declare #table as table
(AccountNo int,
Balance money,
DateEntered datetime)
Declare #dt datetime
set #dt = '2013-07-01'
Insert into #table values(1, 100, '2013-04-01')
Insert into #table values(2, 200, '2013-04-01')
Insert into #table values(2, 300, '2013-05-01')
Insert into #table values(2, 400, '2013-06-01')
--select AccountNo, Max(Balance), Max(DateEntered) MaxDateEntered From #table where DateEntered <= #dt group by AccountNo
Select Sum(t.Balance) From #table t
inner join (select AccountNo, Max(Balance) Balance, Max(DateEntered) MaxDateEntered From #table where DateEntered <= #dt group by AccountNo) tm
on t.AccountNo = tm.AccountNo and t.DateEntered = tm.MaxDateEntered
enter code here

Related

SQL Query > sysdate <Sysdate + 1 year

I am trying to write a query that will find the max expiration date but what i noticed is when I am doing this I get no results if I have a expiration date lets say 30-Dec-16 and for the same part I also have an expiration date of 01-Jan-2099 (which is the default date if nothing is filled in) below is my query how could I rewrite the expiration_date query to get the correct date.
SELECT
Part,
price,
effective_date,
expiration_date
FROM a.Table
WHERE Part IN ('&Part')
AND PRICE IN ('somewere')
AND expiration_date IN (SELECT
MAX(expiration_date)
FROM table
WHERE expiration_date > SYSDATE
AND part IN ('&Part)
AND PRICE IN (Somewere))
AND to_date(effective_date) IN (SELECT
MAX(EFFECTIVE_DATE) FROM b.table
WHERE expiration_date > SYSDATE
AND Part IN ('&Part)
AND price IN (somewere)
AND EFFECTIVE_DATE < SYSDATE + 1)
I would use ROW_NUMBER. https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions137.htm
Here is the query:
SELECT
part
,price
,effective_date
,expieration_date
FROM (
SELECT
part
,price
,effective_date
,expieration_date
,ROW_NUMBER() OVER (PARTITION BY part ORDER BY expieration_date DESC) AS "row"
FROM #tbl
WHERE effective_date < SYSDATE + 1
) tbl
WHERE "row" = 1
Here is what I used to populate #tbl.
DECLARE #tbl TABLE (
part NVARCHAR(MAX)
,price FLOAT
,effective_date DATETIME2(3)
,expieration_date DATETIME2(3)
)
INSERT #tbl (part, PRICE, EFFECTIVE_DATE, EXPIERATION_DATE)
VALUES ('Apples',7.95,'2016-12-01','2016-12-30')
,('Apples',7.95,'2016-11-01','2016-11-30')
,('Apples',7.95,'2016-12-30','2099-01-01')

How to get the alternate dates between two dates in the same table and insert those dates into a temporary table?

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).

Find maximum and minimum days between several dates in SQL

I want to find max. and min. days between several records in my table. For example, in the following table I would like to have max. and min. days due to DATE field for each ID.
I'm using MS-SQL 2013
I know that there is dateiff to finding days between two dates but now, I want to find maximum and minimum days between several dates.
ID DATE
10 2016/01/13
10 2016/01/10
10 2016/11/01
10 2015/12/28
11 2015/12/11
11 2016/02/01
11 2015/01/01
Now, how can I find max. and min. days between DATEs for each ID?
Can you please help me to have the query in SQL?
This solution is a bit ugly (using two subqueries) but should get you started:
CREATE TABLE #DataTable (id INT, [date] DATETIME)
INSERT INTO #DataTable (id, [date])
VALUES (10, '20160113')
,(10, '20160110')
,(10, '20161101')
,(10, '20151211')
,(11, '20151211')
,(11, '20160201')
,(11, '20150101')
SELECT
id
, MIN([days]) AS mindays
, MAX([days]) AS maxdays
FROM (
SELECT
id
, DATEDIFF(DAY, [date], (SELECT MIN([date]) FROM #DataTable AS D1 WHERE D1.id = #DataTable.id AND D1.[date] > #DataTable.[date])) AS [days]
FROM #DataTable
) AS t
GROUP BY id
ORDER BY id
Easiest way to understand is by starting at the middle query (which can be run on its own). It delivers for each row the id, and the number of days between the date in the row and the next higher one from the same id.
The outer query is then a simple MIN MAX GROUP BY.
Ok, I've re-read your answer, are you looking for something like below;
Creating temp table and inserting data;
CREATE TABLE #DataTable (ID int, DATE DateTime)
INSERT INTO #DataTable (ID, DATE)
VALUES
(10, '2016-01-13')
,(10, '2016-01-10')
,(10, '2016-11-01')
,(10, '2015-12-11')
,(11, '2015-12-11')
,(11, '2016-02-01')
,(11, '2015-01-01')
Select statement to retrieve data;
DECLARE #StartDate DateTime; SET #StartDate = '2015-12-01'
DECLARE #EndDate DateTime; SET #EndDate = '2016-12-01'
SELECT
a.ID
,MIN(DATE) FirstDate
,MAX(DATE) LastDate
,DATEDIFF(day, MIN(DATE), MAX(DATE)) DayDiff
FROM #DataTable a
WHERE a.DATE BETWEEN #StartDate AND #EndDate
GROUP BY a.ID
You can remove the fields FirstDate and LastDate, this is just to show the dates that are being compared. You'll return a 0 value if there is only one date between your variable dates so you may have to account for this. Also probably a good idea to put something to check for a NULL.
Have you tried this:
Select ID, Min(Date), Max(Date) From MyTable Group By ID

SQL Delete specific records with condition

I have a table like below:
And the delete condition is:
delete from Employee only if the date is smaller than a specific date and no record is larger than that date
e.g.
if the date is 3/8/2014, then only record with EmployeeID 3 will be removed as the record with EmployeeID 4 has date larger than 3/8/2014, and EmployeeID 5 won't be removed as the date is 3/9/2014
if the date is 3/9/2014, then record with EmployeeID 3 and 5 will be removed, as the record with EmployeeID 4 has date larger than 3/9/2014
At first, I tried
delete from Employee where Date > #Date
But the above SQL would delete all records wherever the date is smaller than the #Date
What amendments should be made to the above SQL?
Try this:
DELETE FROM TableName
WHERE EmployeeID IN
(SELECT EmployeeID FROM TableName
GROUP BY EmployeeID
HAVING MAX(DATE)<=#Date)
Tested and verified.
See an example in SQL Fiddle.
Try this:
delete from Employee
where EmployeeID in
(select EmployeeID
from Employee
group by Employeeid
having max(Date) < #Date)
Here it is,
Declare #d date ='3/8/2014'
delete from myEmp where empID in
(
select empID from myEmp
group by empID
having MAX(empDate) <=#d
)
Link for Demo,
DEMO
create table #t (EmployeeID int, Date datetime)
insert #t values
(3, '20140304'),
(3, '20140305'),
(3, '20140306'),
(4, '20140307'),
(4, '20140308'),
(4, '20140310'),
(5, '20140309')
declare #date date = '20140308'
;with x as (
select t.*
from #t t
where Date <= #date and not exists (
select * from #t t2 where t.EmployeeId = t2.EmployeeID and Date > #date)
)
delete x;

First and Last Time of Day

I need to run a query to sort out records for the first time an event occurs during the day, and the last time an event happens during the day, and run the report to include a week of recorded history on the system. This is in a SQL2005 database, but I haven't found anything to help me narrow things down to just a first occurrance and a last occurance.
-- Test data in table #T
declare #T table(id int, dt datetime)
insert into #T values (1, '2011-01-01T10:00:00')
insert into #T values (2, '2011-01-01T11:00:00')
insert into #T values (3, '2011-01-01T12:00:00')
insert into #T values (4, '2011-01-02T20:00:00')
insert into #T values (5, '2011-01-02T21:00:00')
insert into #T values (6, '2011-01-02T22:00:00')
-- First day of interval to query
declare #FromDate datetime = '2011-01-01'
-- Add 7 days to get #ToDate
declare #ToDate datetime = dateadd(d, 7, #FromDate)
;with cte as
(
select *,
row_number() over(partition by datediff(d, T.dt, 0) order by T.dt) as rnMin,
row_number() over(partition by datediff(d, T.dt, 0) order by T.dt desc) as rnMax
from #T as T
where T.dt >= #FromDate and T.dt < #ToDate
)
select C.id, C.dt
from cte as C
where C.rnMax = 1 or C.rnMin = 1
order by C.dt