MS SQL - Aging Records - sql

Please help as I am stuck with this!! :/
I have 5 columns that I select in a query and last 2 are derived/calculated
Date | Account | Symbol | Type | User | AgeKEY | Age |
where KEY is concatenated (Account+Symbol+Type+User)
How do I look back 1 year into history and calculate the Age of the record? Age is the continuous # of business days that the AgeKey appears in history
Aging Logic Example -
11/3 KeyExists hence Age = 1
11/4 KeyExists hence Age = 2
11/7 KeyExists hence Age = 3 (note over weekend ages only by 1 day)
11/8 KeyDoesntExist
11/9 KeyExists hence Age = 1 (counter restarts from 1 if this happens)

with T-SQL loop (it reads data from tab table and inserts to tab_result):
create table tab
(dt date, id int);
insert into tab values(DATEADD(day,-12,GETDATE()),1);
insert into tab values(DATEADD(day,-10,GETDATE()),1);
insert into tab values(DATEADD(day,-9,GETDATE()),1);
insert into tab values(DATEADD(day,-8,GETDATE()),1);
insert into tab values(DATEADD(day,-7,GETDATE()),3);
insert into tab values(DATEADD(day,-6,GETDATE()),3);
insert into tab values(DATEADD(day,-5,GETDATE()),1);
insert into tab values(DATEADD(day,-4,GETDATE()),1);
insert into tab values(DATEADD(day,-3,GETDATE()),1);
create table tab_result
(dt date, id int, age int);
DECLARE #id INT, #dt date, #prevId INT=NULL, #prevDt date=NULL, #age int
DECLARE CurName CURSOR FAST_FORWARD READ_ONLY
FOR
SELECT id,dt
FROM tab
ORDER BY id,dt
OPEN CurName
FETCH NEXT FROM CurName INTO #id, #dt
set #age=0;
WHILE ##FETCH_STATUS = 0
BEGIN
if (#prevId<>#id or #prevDt <> DATEADD(day,-1, #dt))
set #age=1;
else
set #age=#age+1;
insert into tab_result values (#dt, #id, #age )
set #prevId=#id
set #prevDt=#dt
FETCH NEXT FROM CurName INTO #id, #dt
END
CLOSE CurName
DEALLOCATE CurName
select * from tab_result order by id, dt;
with plain sql it would be something like below (id in the example is your key):
create table tab
(dt date, id int);
insert into tab values(DATEADD(day,-12,GETDATE()),1);
insert into tab values(DATEADD(day,-10,GETDATE()),1);
insert into tab values(DATEADD(day,-9,GETDATE()),1);
insert into tab values(DATEADD(day,-8,GETDATE()),1);
insert into tab values(DATEADD(day,-7,GETDATE()),3);
insert into tab values(DATEADD(day,-6,GETDATE()),3);
insert into tab values(DATEADD(day,-5,GETDATE()),1);
insert into tab values(DATEADD(day,-4,GETDATE()),1);
insert into tab values(DATEADD(day,-3,GETDATE()),1);
with x as (
select tab.dt,
tab.id,
case when prev.dt is not null then 1 else 0 end as exists_on_prev_day
from
tab left outer join tab prev on (tab.id=prev.id and DATEADD(day,-1 , tab.dt)= prev.dt)
)
select id,dt,
(select
-- count all records with the same id and date less or equal date of the given record
count(*) from x x2 where x2.id=x.id and x2.dt<=x.dt
-- (tricky part) we want to count only records between current record and last record without "previous" record (that is with exists_on_prev_day flag = 0)
and not exists (select 1 from x x3 where x3.id=x2.id and x3.dt>x2.dt and x3.dt<=x.dt and x3.exists_on_prev_day=0 )) age
from x
order by id, dt;
result:
id dt age
1 1 19.11.2016 00:00:00 1
2 1 21.11.2016 00:00:00 1
3 1 22.11.2016 00:00:00 2
4 1 23.11.2016 00:00:00 3
5 1 26.11.2016 00:00:00 1
6 1 27.11.2016 00:00:00 2
7 1 28.11.2016 00:00:00 3
8 3 24.11.2016 00:00:00 1
9 3 25.11.2016 00:00:00 2

Related

Write a cursor to search and insert

I have tables like that:
Main
id name isedit
1 kyle 0
2 jhon 1
3 dave 0
EditHistory
id idmain name isedit Begin end
1 2 jhon 0 28.05.2020 18:30 28.05.2020 18:35
2 2 jhon 0 28.05.2020 18:35 NULL
3 1 kyle 0 27.05.2020 12:03 NULL
I currently use trigger:
(…) if update(isedit) and exists (
select 1
from Inserted I
where design = 0
) begin
Insert into dbo.HistoryEdit
([idmain][name][isedit][Begin][end]) SELECT id, name, iedit, GETDATE(), null
from Inserted
end;
I need to create cursor that will check through EditHistory for previous rows with same idmain and if there is such row edit its end date to GETDATE() and insert into HistoryEdit as in my current insert.
I know it can be easily done with IF's and thats how I would do it. But I have to use cursor for that, and I never used cursor before.
I never used cursor before.
Well don't start now. Just update the older rows before you insert the newer ones:
declare #d datetime = GetDate()
update EditHistory set end = #d
where id in (select id from inserted)
and end is null;
Insert into dbo.HistoryEdit
([idmain][name][isedit][Begin][end])
SELECT id, name, iedit, #d, null
from Inserted

When there is a 6 month Gap Grab everything after if there is no 6 month gap then grab everything sql

I am working on a query pull and I need some help.
I am trying to figure out a case when statement when there is a 6 month gap then grab the next number when There is no 6 month gap then grab all of them.
Example 1:
ID Gap
1 0
2 4
3 1
4 8
5 1
6 6
7 1
So in this example, there is a gap of 8 so if that was the only gap >= 6 then I would just grab that one but since there is another gap of 6 I just want to grab ID 7.
Example 2:
ID Gap
1 0
2 1
3 0
4 2
5 0
So in this example, there is no gap. so I want to grab all of those IDs.
This is all in sql
Well, you can use window functions:
select id, gap
from (select t.*,
max(case when gap >= 6 then id end) over (order by id) as id_6
from t
) t
where id > id_6 or id_6 is null;
With cursor, didn't test on SSMS. It's another way to do it, not the best but that was for my personal training.
IF EXISTS (SELECT * FROM table WHERE Gap >= 6)
BEGIN
DECLARE #id AS INT;
DECLARE #gap AS INT;
DECLARE #id2 AS INT;
DECLARE #gap2 AS INT;
DECLARE gap_cursor CURSOR FOR
SELECT ID, Gap
FROM table;
OPEN gap_cursor;
FETCH NEXT FROM gap_cursor INTO #id, #gap;
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#gap >= 6)
BEGIN
FETCH NEXT FROM gap_cursor
INTO #id, #gap;
SET #id2 = #id;
SET #gap2 = #gap;
END
ELSE
BEGIN
FETCH NEXT FROM gap_cursor INTO #id, #gap;
END
END
SELECT #id2, #gap2;
END
ELSE
BEGIN
SELECT *
FROM table;
END

Weekends between weekdays SQL

I have a list of weekday's dates and want to insert rows for weekends/public holidays dates and populate data from previous row in SQL. Pls help.
Your holidays depend on what country you are in. This is the logic used for inserting two days after finding a Friday, similar logic can be applied if you know how to identify holidays, and how long they last.
create table #temp (fechas date, value int)
insert into #temp values ('20160601',2)
insert into #temp values ('20160602',4)
insert into #temp values ('20160603',8)
insert into #temp values ('20160606',2)
insert into #temp values ('20160607',1)
--TABLE
select *, DATEPART(DW,fechas) as dayOfTheWk
into #temp2
from #temp
-- selecting Fridays
declare #Fridays TABLE (fechas date,value int, dayOfTheWk int, nRow int)
insert into #Fridays
select *, DATEPART(DW,fechas) as dayOfTheWk, ROW_NUMBER() over(order by fechas) from #temp
where DATEPART(DW,fechas) = 6
declare #i int = 1, #maxI int
select #maxI = count(1) from #Fridays
while(#i <= #maxI)
begin
declare #x int = 1
while (#x <= 2)
begin
insert into #temp2
select
DATEADD(day,#x,fechas) as fechas, value, DATEPART(DW,DATEADD(day,#x,fechas))
from #Fridays
where nRow = #i
select #x += 1
end
select #i += 1
end
select * from #temp2
fechas value dayOfTheWk
2016-06-01 2 4
2016-06-02 4 5
2016-06-03 8 6
2016-06-04 8 7
2016-06-05 8 1
2016-06-06 2 2
2016-06-07 1 3

get specific rows of table given a rule SQL Server 2008

I have a table like:
ID NAME VAL
----------------------
1 a1*a1 90052
2 a1*a2 236
3 a1*a3 56
4 a1*a4 6072
5 a1*a5 1004
6 a2*a2 4576
7 a2*a3 724
8 a2*a4 230
9 a2*a5 679
10 a3*a3 5
11 a3*a4 644
12 a3*a5 23423
13 a4*a4 42354
14 a4*a5 10199
15 a5*a5 10279
Given a number given S = 5, I want to query
the rows wth id: 1,6,10,13,15
they are a1*a1,a2*a2,a3*a3,a4*a4 and a5*a5
I would like something like:
INSERT #NEW_TABLE (ID,NAME,Value) (
SELECT ordinal, NAME, VAL FROM myTable where id = 1,6,10,13,15)
to get
ID NAME VAL
----------------------
1 a1*a1 90052
2 a2*a2 4576
3 a3*a3 5
4 a4*a4 42354
5 a5*a5 10279
Is there a way to do this for any given S, Maybe wth dynamic sql?
I was getting the formula and I got this:
S=5
ID formula
1 1
6 1+S
10 1+S+ (S-1)
13 1+S+ (S-1) + (S-2)
15 1+S+ (S-1) + (S-2) + (S-3)
Is there a way to do this inside a case or a while loop?
This worked in testing.
You can just inner join on #Tab to limit your results. You probably also want to add some traps for values below 3, which I haven't done.
The basic process is
Declare your #s value
Insert the first two rows since they will always be the same
In a loop, insert one row at a time with an incrementing difference
Loop exits once it has run #s-2 times
Try:
DECLARE #Tab Table (id INT)
DECLARE #S int = 5,
#ct int
DECLARE #cur int = (1 + #S)
INSERT INTO #Tab SELECT 1
INSERT INTO #Tab SELECT (1 + #S)
SET #ct = 1
WHILE #ct <= #S - 2
BEGIN
SET #cur = #cur + (#S - #ct)
INSERT INTO #Tab SELECT #cur
SET #ct = #ct + 1
END
SELECT * FROM #Tab
ORDER BY id
To use this in your query, you can do either:
SELECT ordinal, NAME, VAL
FROM myTable
WHERE id IN (SELECT id FROM #Tab)
-- OR
SELECT ordinal, NAME, VAL
FROM myTable t
INNER JOIN #tab t2
ON t2.id = t.id

Clearing prioritized overlapping ranges in SQL Server

This one is nasty complicated to solve.
I have a table containing date ranges, each date range has a priority. Highest priority means this date range is the most important.
Or in SQL
create table #ranges (Start int, Finish int, Priority int)
insert #ranges values (1 , 10, 0)
insert #ranges values (2 , 5 , 1)
insert #ranges values (3 , 4 , 2)
insert #ranges values (1 , 5 , 0)
insert #ranges values (200028, 308731, 0)
Start Finish Priority
----------- ----------- -----------
1 10 0
2 5 1
3 4 2
1 5 0
200028 308731 0
I would like to run a series of SQL queries on this table that will result in the table having no overlapping ranges, it is to take the highest priority ranges over the lower ones. Split off ranges as required, and get rid of duplicate ranges. It allows for gaps.
So the result should be:
Start Finish Priority
----------- ----------- -----------
1 2 0
2 3 1
3 4 2
4 5 1
5 10 0
200028 308731 0
Anyone care to give a shot at the SQL? I would also like it to be as efficient as possible.
This is most of the way there, possible improvement would be joining up adjacent ranges of the same priority. It's full of cool trickery.
select Start, cast(null as int) as Finish, cast(null as int) as Priority
into #processed
from #ranges
union
select Finish, NULL, NULL
from #ranges
update p
set Finish = (
select min(p1.Start)
from #processed p1
where p1.Start > p.Start
)
from #processed p
create clustered index idxStart on #processed(Start, Finish, Priority)
create index idxFinish on #processed(Finish, Start, Priority)
update p
set Priority =
(
select max(r.Priority)
from #ranges r
where
(
(r.Start <= p.Start and r.Finish > p.Start) or
(r.Start >= p.Start and r.Start < p.Finish)
)
)
from #processed p
delete from #processed
where Priority is null
select * from #processed
Here is something to get you started. It is helpful if you use a calendar table:
CREATE TABLE dbo.Calendar
(
dt SMALLDATETIME NOT NULL
PRIMARY KEY CLUSTERED
)
GO
SET NOCOUNT ON
DECLARE #dt SMALLDATETIME
SET #dt = '20000101'
WHILE #dt < '20200101'
BEGIN
INSERT dbo.Calendar(dt) SELECT #dt
SET #dt = #dt + 1
END
GO
Code to setup the problem:
create table #ranges (Start DateTime NOT NULL, Finish DateTime NOT NULL, Priority int NOT NULL)
create table #processed (dt DateTime NOT NULL, Priority int NOT NULL)
ALTER TABLE #ranges ADD PRIMARY KEY (Start,Finish, Priority)
ALTER TABLE #processed ADD PRIMARY KEY (dt)
declare #day0 datetime,
#day1 datetime,
#day2 datetime,
#day3 datetime,
#day4 datetime,
#day5 datetime
select #day0 = '2000-01-01',
#day1 = #day0 + 1,
#day2 = #day1 + 1,
#day3 = #day2 + 1,
#day4 = #day3 + 1,
#day5 = #day4 + 1
insert #ranges values (#day0, #day5, 0)
insert #ranges values (#day1, #day4, 1)
insert #ranges values (#day2, #day3, 2)
insert #ranges values (#day1, #day4, 0)
Actual solution:
DECLARE #start datetime, #finish datetime, #priority int
WHILE 1=1 BEGIN
SELECT TOP 1 #start = start, #finish = finish, #priority = priority
FROM #ranges
ORDER BY priority DESC, start, finish
IF ##ROWCOUNT = 0
BREAK
INSERT INTO #processed (dt, priority)
SELECT dt, #priority FROM calendar
WHERE dt BETWEEN #start and #finish
AND NOT EXISTS (SELECT * FROM #processed WHERE dt = calendar.dt)
DELETE FROM #ranges WHERE #start=start AND #finish=finish AND #priority=priority
END
Results: SELECT * FROM #processed
dt Priority
----------------------- -----------
2000-01-01 00:00:00.000 0
2000-01-02 00:00:00.000 1
2000-01-03 00:00:00.000 2
2000-01-04 00:00:00.000 2
2000-01-05 00:00:00.000 1
2000-01-06 00:00:00.000 0
The solution is not in the exact same format, but the idea is there.
I'm a little confused about what you want to end up with. Is this the same as simply having a set of dates where one range continues until the next one starts (in which case you don't really need the Finish date, do you?)
Or can a range Finish and there's a gap until the next one starts sometimes?
If the range Start and Finish are explicitly set, then I'd be inclined to leave both, but have the logic to apply the higher priority during the overlap. I'd suspect that if dates start getting adjusted, you'll eventually need to roll back a range that got shaved, and the original setting will be gone.
And you'll never be able to explain "how it got that way".
Do you want simply a table with a row for each date, including its priority value? Then when you have a new rule, you can bump the dates that would be trumped by the new rule?
I did a medical office scheduling app once that started with work/vacation/etc. requests with range-type data (plus a default work-week template.) Once I figured out to store the active schedule info as user/date/timerange records, things fell into place a lot more easily. YMMV.
This can be done in 1 SQL (i first made the query in Oracle using lag and lead, but since MSSQL doesn't support those functions i rewrote the query using row_number. I'm not sure if the result is MSSQL compliant, but it should be very close):
with x as (
select rdate rdate
, row_number() over (order by rdate) rn
from (
select start rdate
from ranges
union
select finish rdate
from ranges
)
)
select d.begin
, d.end
, max(r.priority)
from (
select begin.rdate begin
, end.rdate end
from x begin
, x end
where begin.rn = end.rn - 1
) d
, ranges r
where r.start <= d.begin
and r.finish >= d.end
and d.begin <> d.end
group by d.begin
, d.end
order by 1, 2
I first made a table (x) with all dates. Then I turned this into buckets by joining x with itself and taking 2 following rows. After this I linked all the possible priorities with the result. By taking the max(priority) I get the requested result.