SQL code to add missing StartDate between 2 dates in SQL Server - sql

I have the below table ordered by clientID, contractID and effectiveDate. One Client has multiple contractID and it's respective effectiveDate.
the desired output is as below, where the new FYStartDate column should add the missing FYStartDate between 2 dates of subsequent contractIDs of a clientID(in this scenario, Fiscal Year starts on 01June of every year)
I would be appreciate if you could share the required SQL code.
I'm attaching the SQL code to generate the first table
CREATE TABLE [client] (
[clientid] [int] NULL,
[contractid] [int] NULL,
[effectivedate] [date] NULL
) ON [PRIMARY]
GO
insert into [client] values
('228','2','6/1/2003'),('228','136','6/1/2004'),('228','242','6/1/2008'),
('228','337','12/1/2012'),('228','584','6/1/2017'),('14216','319','5/1/2013'),
('14216','355','6/1/2013'),('14216','739','6/1/2020'),('14216','10','3/1/2021'),
('14216','1009','6/1/2021')

This is a bit convoluted without as #MatBailie suggest about having more structured data. To accomplish what you ask, each record needs to know when the contract before it and after it comes into effect. I think you need to play with the ordering because I didn't quite get how to order the results... by clientid, contractid, dates, etc?
UPDATED: see comments. Changed some CTEs, JOINS and ORDER BY for better partitioning by clientid.
CREATE TABLE [client] (
[clientid] [int] NULL,
[contractid] [int] NULL,
[effectivedate] [date] NULL
) ON [PRIMARY]
;
insert into [client] values
('228','2','6/1/2003'),('228','136','6/1/2004'),('228','242','6/1/2008'),
('228','337','12/1/2012'),('228','584','6/1/2017'),('14216','319','5/1/2013'),
('14216','355','6/1/2013'),('14216','739','6/1/2020'),('14216','10','3/1/2021'),
('14216','1009','6/1/2021')
;
--Need a sequence of numbers to create a sequence of fiscal years.
WITH x AS (
SELECT * FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as x(a)
), y as (
SELECT ROW_NUMBER() OVER(ORDER BY tens.a, ones.a) as row_num
FROM x as ones, x as tens
), fiscYears as (
SELECT
fyStart = DATEFROMPARTS(2000 + y.row_num -1, 6, 1)
, fyEnd = DATEFROMPARTS(2000 + y.row_num, 5, 31)
FROM y
--Need to order the client records by effective date.
--From updated question... looks like we are reporting by clientid.
), clientOrd as (
SELECT c2.*, ROW_NUMBER() OVER(PARTITION BY c2.clientid ORDER BY c2.effectivedate) as row_num
FROM client c2
--For each contract, get the previous and next contracts by effective date.
), clientWNext as (
SELECT c.*
, cNext.effectivedate as nextEffectiveDate
, cPrev.effectivedate as prevEffectiveDate
FROM clientOrd as c
LEFT JOIN clientOrd as cNext
ON cNext.clientid = c.clientid
AND cNext.row_num = c.row_num + 1
LEFT JOIN clientOrd as cPrev
ON cPrev.clientid = c.clientid
AND cPrev.row_num = c.row_num - 1
)
SELECT
c.clientid
, cwn.contractid
, CASE WHEN cwn.effectiveDate >= fy.fyStart AND cwn.effectiveDate <= fy.fyEnd
THEN cwn.effectivedate
ELSE null
END as effectivedate
, fy.fyStart
FROM fiscYears as fy
--To get a full FY range for each client, we join to a distinct list of clients.
JOIN (
SELECT DISTINCT clientid FROM client
) as c
ON 1=1
--Need to join the list of contracts.
INNER JOIN clientWNext as cwn
ON cwn.clientid = c.clientid
--This is the main join criteria where the effective date is within the fy year start/end.
AND ((
cwn.effectivedate >= fy.fyStart
AND cwn.effectivedate <= fy.fyEnd
)
--This is the "alternate" join criteria where the previous contrat is still in effect
--but there is no new contract to supercede the previous.
OR (
cwn.prevEffectiveDate < fy.fyStart
AND cwn.effectiveDate < fy.fyStart
AND (cwn.nextEffectiveDate > fy.fyEnd OR cwn.nextEffectiveDate IS NULL)
))
--Limiting fiscal year date range.
WHERE fy.fyStart >= '1/1/2003'
AND fy.fyStart < '1/1/2024'
ORDER BY c.clientid, fy.fyStart, cwn.effectivedate
clientid
contractid
effectivedate
fyStart
228
2
2003-06-01
2003-06-01
228
136
2004-06-01
2004-06-01
228
136
null
2005-06-01
228
136
null
2006-06-01
228
136
null
2007-06-01
228
242
2008-06-01
2008-06-01
228
242
null
2009-06-01
228
242
null
2010-06-01
228
242
null
2011-06-01
228
337
2012-12-01
2012-06-01
228
337
null
2013-06-01
228
337
null
2014-06-01
228
337
null
2015-06-01
228
337
null
2016-06-01
228
584
2017-06-01
2017-06-01
228
584
null
2018-06-01
228
584
null
2019-06-01
228
584
null
2020-06-01
228
584
null
2021-06-01
228
584
null
2022-06-01
228
584
null
2023-06-01
14216
319
2013-05-01
2012-06-01
14216
355
2013-06-01
2013-06-01
14216
355
null
2014-06-01
14216
355
null
2015-06-01
14216
355
null
2016-06-01
14216
355
null
2017-06-01
14216
355
null
2018-06-01
14216
355
null
2019-06-01
14216
739
2020-06-01
2020-06-01
14216
10
2021-03-01
2020-06-01
14216
1009
2021-06-01
2021-06-01
14216
1009
null
2022-06-01
14216
1009
null
2023-06-01
fiddle

Related

T-SQL get values for specific group

I have a table EmployeeContract similar like this:
ContractId
EmployeeId
ValidFrom
ValidTo
Salary
12
5
2018-02-01
2019-06-31
x
25
8
2015-01-01
2099-12-31
x
50
5
2019-07-01
2021-05-31
x
52
6
2011-08-01
2021-12-31
x
72
8
2010-08-01
2014-12-31
x
52
6
2011-08-01
2021-12-31
x
Table includes history contracts in company for each employee. I need to get date when employees started work and last date of contract. Sometime records has duplicates.
For example, based on data from above:
EmployeeId
ValidFrom
ValidTo
5
2018-02-01
2021-05-31
8
2010-08-01
2099-12-31
6
2011-08-01
2021-12-31
Base on this article: https://www.techcoil.com/blog/sql-statement-for-selecting-the-latest-record-in-each-group/
I prepared query like this:
select minv.*, maxv.maxvalidto from
(select distinct con.[EmployeeId], mvt.maxvalidto
from [EmployeeContract] con
join (select [EmployeeId], max(validto) as maxvalidto
FROM [EmployeeContract]
group by [EmployeeId]) mvt
on con.[EmployeeId] = mvt.[EmployeeId] and mvt.maxvalidto = con.validto) maxv
join
(select distinct con.[EmployeeId], mvf.minvalidfrom
from [EmployeeContract] con
join (select [EmployeeId], min(validfrom) as minvalidfrom
FROM [EmployeeContract]
group by [EmployeeId]) mvf
on con.[EmployeeId] = mvf.[EmployeeId] and mvf.minvalidfrom = con.validfrom) minv
on minv.[EmployeeId] = maxv.[EmployeeId]
order by 1
But I'm not satisfied, i think it's not easy to read, and probably optimize is poor. How can I do it better?
I think you want group by:
select employeeid, min(validfrom), max(validto)
from employeecontract
group by employeeid

IF Else or Case Function for SQL select problem

Hi I would like to make a select expression using case or if/else which seems to be a simple solution from logic perspective but I can't seem to get it to work. Basically I am joining against two table here, the first table is customer record with date filter called min_del_date and then the second table for the model scoring table with BIN and update_date parameters.
There are two logics I want to display
Picking the model score that was the month before min_del_date
If model score month before delivery is greater than 50 (Bin > 50) then pick the model score for same month as min_del_date
My 1st logic code is below
with cust as (
select
distinct cust_no, max(del_date) as del_date, min(del_date) as min_del_date, (EXTRACT(YEAR FROM min(del_date)) -1900)*12 + EXTRACT(MONTH FROM min(del_date)) AS upd_seq
from customer.cust_history
group by 1
)
,model as (
select party_id, model_id, update_date, upd_seq, bin, var_data8, var_data2
from
(
select
party_id, update_date, bin, var_data8, var_data2,
(EXTRACT(YEAR FROM UPDATE_DATE) -1900)*12 + EXTRACT(MONTH FROM UPDATE_DATE) AS upd_seq,
dense_Rank() over (partition by (EXTRACT(YEAR FROM UPDATE_DATE) -1900)*12 + EXTRACT(MONTH FROM UPDATE_DATE) order by update_date desc) as rank1
from
(
select party_id,update_date, bin, var_data8, var_data2
from model.rpm_model
group by party_id,update_date, bin, var_data8, var_data2
) model
)model_final
where rank1 = 1
)
-- Add model scores
-- 1st logic Picking the model score that was the month before delivery date
select *
from
(
select cust.cust_no, cust.del_date, cust.min_del_date, model.upd_seq, model.bin
from cust
left join cust
on cust.cust_no = model.party_id
and cust.upd_seq = model.upd_seq + 1
)a
Now I am struggling in creating the 2nd logic in the same query?.. any assistance would be appreciated
cust table
cust_no
min_del_date
upd_seq
123
2021-01-11
1453
234
2020-06-29
1446
456
2020-07-20
1447
model table
party_id
update_date
upd_seq
BIN
123
2020-11-30
1451
22
123
2020-12-25
1452
54
123
2020-01-11
1453
14
234
2020-05-23
1445
76
234
2020-06-18
1446
48
234
2020-07-23
1447
12
456
2020-06-18
1446
23
456
2020-07-23
1447
39
456
2020-08-21
1448
21
desired results
cust_no
min_del_date
model.upd_seq
update_date
BIN
123
2021-01-11
1453
2020-01-11
14
234
2020-06-29
1446
2020-06-18
48
456
2020-07-20
1446
2020-06-18
23
Update
I managed to find the solution by myself, thanks for everyone who has attending this question. The solution is per below
select a.cust_no, a.del_date, a.min_del_date, b.update_date, b.upd_seq, b.bin
from
(
select cust.cust_no, cust.del_date, cust.min_del_date,
CASE WHEN model.BIN <=50 THEN model.upd_seq WHEN BIN > 50 THEN model.upd_seq +1 ELSE NULL END as upd_seq
from cust
inner join model
on cust.cust_no = model.party_id
and cust.upd_seq = model.upd_seq + 1
)a
inner join model b
on a.cust_no = b.party_id
and a.upd_seq = b.upd_seq

Find Datediff in the same column

I have a BorderCrossingData Table. A would like to get the PassportNames have minimum one BorderCrossingDateTime-interval what is longer than 4 month.
BorderCrossingID PassportNumber BorderCrossingDateTime
1 ER-2222 2019-01-07 22:11:12.000
2 ER-2222 2019-01-07 23:11:12.000
3 KL-5233 2018-10-03 17:10:39.000
130 FF-4444 2019-01-08 11:11:11.000
5 ER-1111 NULL
6 KL-5686 NULL
7 ER-1111 NULL
8 KL-5235 NULL
9 QW-5656 NULL
10 DF-5685 NULL
11 KL-4558 NULL
--------
113 LL-8989 2019-01-15 16:24:26.333
114 ZZ-0005 2019-01-17 16:18:12.273
115 LL-0223 2019-01-17 16:19:12.000
116 ER-2222 2019-01-03 08:24:29.000
117 ER-2222 2019-02-01 08:25:03.873
118 ER-2222 2019-03-13 08:25:17.000
119 ER-2222 2019-04-10 08:25:32.000
120 ER-2222 2019-09-30 08:25:47.000
I have already get BorderCrossings have BorderCrossingDateTime and put them in Order.
SELECT DISTINCT PassportNumber, BorderCrossingDateTime FROM Passports
WHERE DATEDIFF(Compare 2 upcoming DateTimes)
EXCEPT
SELECT PassportNumber, BorderCrossingDateTime FROM Passports
WHERE BorderCrossingDateTime IS NULL
ORDER BY BorderCrossingDateTime
The result should be like this:
PassportName
ER-2222
TO-0140
NN-4444
TP-0140
TT-0140
WU-5645
Below code will work. I have tested in SQL Server. I am using LEAD, LAG functions to find out the previous, next border crossing dates.
CREATE TABLE #borderCrossing (BorderCrossingID INT, PassportNumber VARCHAR(10), BorderCrossingDateTime DATETIME)
INSERT INTO #borderCrossing
VALUES (1, 'ER-2222', '2019-01-07 22:11:12.000'), (2, 'ER-2222', '2019-03-07 22:11:12.000'), (3, 'ER-2222', '2019-08-07 22:11:12.000');
SELECT DISTINCT PassportNumber
FROM (
SELECT BorderCrossingId, BorderCrossingDateTime AS CurrentBorderCrossingDateTime, passportNumber, lag(BorderCrossingDateTime, 1, NULL) OVER (
PARTITION BY passportNumber ORDER BY BorderCrossingDateTime
) AS prevBorderCrossingDateTime, lead(BorderCrossingDateTime, 1, NULL) OVER (
PARTITION BY passportNumber ORDER BY BorderCrossingDateTime
) AS nextBorderCrossingDateTime
FROM #borderCrossing
) AS t
WHERE DATEDIFF(mm, prevBorderCrossingDateTime, nextBorderCrossingDateTime) > 4

PostgreSQL - Group by filter out specific rows

I have 3 tables in a Postgres 9.5 DB like below,
threshold
id threshold_amount
----------------------
111 100
112 200
113 80
customers - each customer has a threshold_id of threshold table
id customer_name threshold_id
--------------------------------
313 abc 111
314 xyz 112
315 pqr 113
charges - per customer there is charges so this table has customer_id
id customer_id amount post_date
------------------------------------
211 313 50 4/1/2017
212 313 50 4/30/2017
213 313 50 5/15/2017
214 314 100 3/1/2017
215 314 50 3/21/2017
216 314 50 4/21/2017
217 314 100 5/1/2017
218 315 80 5/5/2017
I want to query it and return the specific post_date with sum( amount ) == threshold_amount by ascending order of charges.id column,
The resultset look like below,
customer_id post_date
-----------------------
313 4/30/2017
314 4/21/2017
315 5/5/2017
I've tried sum( amount ) with group by customer_id and call the one separate the stored procedure from select clause and pass the amount, post_date and threshold_amount then created one temp table and insert post_date into it if the above condition get match and then again access that temp table but it seems something not valid so I want to know if some other solution or Can I do it in query?
Thanks
Your question is asking about an exact match for the threshold. This is basically a cumulative sum:
select cct.*
from (select ch.customer_id, ch.amount,
sum(ch.amount) over (partition by ch.customer_id order by post_date) as running_amount,
t.threshold_amount
from charges ch join
customers c
on ch.customer_id = c.id join
threshholds t
on c.threshold_id = t.id
) cct
where running_amount = threshold_amount;
try this:
select
c.customer_id,
c.post_date
from charges c
join customers cu on cu.id = c.customer_id
join threshold t on t.id = cu.threshold_id
where (select sum(cc.amount) from charges cc where cc.id <= c.id
and cc.customer_id = c.customer_id) = t.threshold_amount

Sql server 2008 - how to get records by the earliest dates

table is rpt
custID dates stores
111089 2015-09-28 103
111089 2015-06-19 119
111089 2015-10-11 106
555555 2015-05-02 103
555555 2015-08-21 125
555555 2015-09-20 125
123456 2015-01-01 119
123456 2015-05-13 116
123456 2015-09-15 120
123456 2015-08-29 115
result should be
custID dates store
111089 2015-06-19 119
555555 2015-05-02 103
123456 2015-01-01 119
the table is a very big table and I need all custID and store with the earliest date. like the result above.
only one row per custID
You can do this with a windowed function with a PARTITION on the CustID and ordering by dates:
;With Cte As
(
Select *, Row_Number() Over (Partition By CustID Order By Dates Asc) As Row_Number
From rpt
)
Select custID, dates, stores
From Cte
Where Row_Number = 1
SELECT rpt.custid, rpt.date, rpt2.stores
FROM (select r.custid, min(r.DATE) as 'Date'
from rpt r
group by r.custid) rpt
left join (select r.custid, r.DATE, r.stores
from rpt r) rpt2 on rpt2.custid = rpt.custid and rpt2.date = rpt.date