SQL Find Nearest Effective Date in Related Table - sql

I'll preface this by stating that this problem is similar to SQL Join on Nearest Less Than Date but that the solution there doesn't work for my problem. Instead of selecting a single column, I need the results of a table that is 'filtered' based on the nearest date.
I have three tables. The main table contains time ticket data in the form:
ticketId
ticketNumber
ticketDate
projectId
A secondary table tracks rate schedules for resources on each daily ticket for the project. It looks something like this:
scheduleId
projectId
effectiveDate
There is also a third table that is related to the second that actually contains the applicable rates. Something like this:
scheduleId
straightTime
overTime
Joining the first two tables on projectId (obviously) replicates data for every record in the rate schedule for the project. If I have 3 rate schedules for project 1, then ticket records result in something like:
ticketNumber | ticketDate | projectId | effectiveDate | scheduleId
------------- | ------------ | ----------- | -------------- | ----------
1234 | 2016-06-18 | 25 | 2016-06-01 | 1
1234 | 2016-06-18 | 25 | 2016-06-15 | 2
1234 | 2016-06-18 | 25 | 2016-06-31 | 3
Selecting the effectiveDate into my results is straightforward with the example:
SELECT *
, (SELECT TOP 1 t1.effectiveFrom
FROM dbo.labourRateSchedule t1
WHERE t1.effectiveFrom <= t2.[date] and t1.projectId = t2.projectId
ORDER BY t1.effectiveFrom desc) as effectiveDate
FROM dbo.timeTicket t2
ORDER BY t.date
However, I need to be able to join the ID of dbo.labourRateSchedule onto the third table to get the actual rates that apply. Adding the t1.ID to the SELECT statement does not make it accessible to JOIN into another related table.
I've been trying to JOIN the SELECT statement in the FROM statement but the results are only resulting with the last effectiveDate value instead of one that is closest to the applicable ticketDate.
I would hugely appreciate any help on this!

You can move your subquery to the FROM clause by using CROSS APPLY:
SELECT *
FROM dbo.timeTicket tt
CROSS APPLY
(
SELECT TOP(1) *
FROM dbo.labourRateSchedule lrs
WHERE lrs.projectId = tt.projectId
AND lrs.effectiveFrom <= tt.date
ORDER BY lrs.effectiveFrom desc
) best_lrs
JOIN dbo.schedule s on s.schedule_id = best_lrs.schedule_id
ORDER BY tt.date

Can you try something like this (you should change something, as you didn't post all information).
SELECT A.*, C.*
FROM timeTicket A
INNER JOIN (SELECT * , ROW_NUMBER() OVER (PARTITION BY projectId ORDER BY effectiveFrom DESC) AS RN
FROM labourRateSchedule) B ON A.projectId=B.projectId AND B.RN=1
INNER JOIN YOUR3TABLE C ON B.SCHEDULEID=C.SCHEDULEID

You can do this by CTE and Rank function -
create table timeTicket (ticketId int,
ticketNumber int ,
ticketDate smalldatetime ,
projectId int )
go
create table labourRateSchedule
(scheduleId int,
projectId int,
effectiveDate smalldatetime )
go
create table ApplicableRates
(scheduleId int,
straightTime smalldatetime ,
overTime smalldatetime)
go
insert into timeTicket
select 1 , 1234 ,'2016-06-18' ,25
go
insert into labourRateSchedule
select 1 , 25 ,'2016-06-01'
union all select 2 , 25 ,'2016-06-15'
union all select 3 , 25 ,'2016-06-30'
go
insert into ApplicableRates
select 1 , '2016-06-07' ,'2016-06-07'
union all select 2 , '2016-06-17' ,'2016-06-17'
union all select 3 , '2016-06-22' ,'2016-06-25'
go
with cte
as (
select t1.ticketNumber ,t1.ticketDate ,t1.projectId ,t2.effectiveDate ,t3.scheduleId ,t3.straightTime
,t3.overTime , rank() over ( partition by t1.ticketNumber order by abs (DATEDIFF(day,t1.ticketDate, t2.effectiveDate) ) ) DyaDiff
from timeTicket t1 join labourRateSchedule t2
on t1.projectId = t2.projectId
join ApplicableRates t3
on t2.scheduleId = t3.scheduleId)
select * from cte where DyaDiff = 1

Related

update student ordered by Batch.dateCreated asc where Batch.PK and Student.BatchFK matches

I have two tables in sql server - Student and Batch.
The student table is of this nature -
ID | studentname | batchTypeFfk | batchName
1 | Rob | 1 | Eng
The batch table is of this nature -
ID | name | batchTypeFk | date
1 | Eng | 1 | 05/18/2019
2 | Mtt | 1 | 05/20/2019
The batch type is of this nature -
ID | name |
1 | Summer
My challenge is to sort batch table by date ascending and update the Student.batchName table with the latest date where Batch.batchypeFk = Student.batchTypeFk having in mind that there could be as many batch with different batch types and as many student as possible with batch types using sql server
I have an attempt like this in mind, but its not solving the problem as it has sql errors
UPDATE a.batchName
FROM STUDENT a, BATCH b
where a.batchTypeFk == b.batchTypeFk orderby data asc
Try this-
UPDATE S
SET S.name = C.name
FROM student S
INNER JOIN
(
--Picking Name by joining to the same table
--Based on the Max Date and batchTypeFk
--selectted in the sub query
SELECT A.batchTypeFk,B.name
FROM
(
--Here only select MAX Date per batchTypeFk
SELECT batchTypeFk,MAX(date) date
FROM batch
GROUP BY batchTypeFk
)A
INNER JOIN batch B ON A.batchTypeFk = B.batchTypeFk AND A.date = B.date
)C ON S.batchTypeFk = C.batchTypeFk
I think , You can update like this.
Create Table #Batch
(
Id Int
,name varchar(50)
,batchTypeFk Int
,date datetime
)
Insert Into #Temp_St
Values
(1,'Rob',1,'Eng')
Insert Into #Batch
Values
(1,'Eng','1','20190518')
,(2,'Mtt',1,'20190520')
With Cte
As
(
Select ROW_NUMBER() OVER (Order By date desc) as rn,s.*
From #Batch b
Inner Join #Temp_St s On b.batchTypeFk=s.batchTypeFk
)
Update Cte
Set batchName='Summer'
Where rn=1

Mathematical comparison between rows

I have a table
+----+-----------+---------+
| ID | StartTime | EndTime |
+----+-----------+---------+
| 1 | 2:00pm | 3:00pm |
| 2 | 4:00pm | 5:00pm |
| 3 | 7:00pm | 9:00pm |
+----+-----------+---------+
I need to get the difference between the end time of one row and the start time of the NEXT row. i.e. End time of row 1 compared to start time of row 2, or end time of row 2 compared to start time of row 3.
Ideally I'd like a result that looks similar to
+----+----------------+
| ID | TimeDifference |
+----+----------------+
| 2 | 1.0 hours |
| 3 | 2.0 hours |
+----+----------------+
I have no clue whatsoever on how to do something like this. I'm thinking that I may need 2 temp tables, one to hold start times another for end times so that I can more easily do the comparisons, but honestly that's just a shot in the dark at the moment.
FYI, on server 2008 in case that makes a difference for some of the commands.
NOTE: The question was not tagged SQL Server 2008 when this answer was written.
You can use lag():
select t.*,
datediff(minute, lag(endtime) over (order by id), starttime) / 60.0 as hours_diff
from t;
This does not filter out any rows. The description of the problem ("next row") and the sample data (which is based on "previous row") are inconsistent.
Well, since it's 2008 version you can't use the Lead() or Lag() window functions, but you can use subqueries to mimic them:
SELECT Id,
DATEDIFF(minute,
(
SELECT TOP 1 EndTime
FROM table t1
WHERE t1.Id < t0.Id
ORDER BY t1.Id DESC
), StartTime) / 60.0 As TimeDifference
FROM Table t0
WHERE EXISTS
(
SELECT 1
FROM Table t2
WHERE t2.Id < t0.Id
)
You can try it
declare #t as table (ID int, StartTime time , EndTime time)
INSERT #t SELECT 1 ,'2:00pm', '3:00pm'
INSERT #t SELECT 2 ,'4:00pm', '5:00pm'
INSERT #t SELECT 3 ,'7:00pm', '9:00pm'
---- For sequential IDs
select
a.ID
,a.StartTime
,a.EndTime
,datediff(minute, (SELECT EndTime FROM #t b where b.ID = a.ID - 1) , a.StartTime) / 60.0 as hours_diff
from #t a
---- For non-sequential IDs
;WIth cte_times as (
SELECT
ROW_NUMBER() OVER (ORDER BY Id) as new_ID
, ID
,StartTime
,EndTime
FROM
#t
)
select
a.ID
,a.StartTime
,a.EndTime
,datediff(minute, (SELECT EndTime FROM cte_times b where b.new_ID = a.new_ID - 1) , a.StartTime) / 60.0 as hours_diff
from cte_times a

SQL Joins . One to many relationship

I have two tables as below
Table 1
-----------------------------------
UserID | UserName | Age | Salary
-----------------------------------
1 | foo | 22 | 33000
-----------------------------------
Table 2
------------------------------------------------
UserID | Age | Salary | CreatedDate
------------------------------------------------
1 | NULL | 35000 | 2015-01-01
------------------------------------------------
1 | 28 | NULL | 2015-02-01
------------------------------------------------
1 | NULL | 28000 | 2015-03-01
------------------------------------------------
I need the result like this.
Result
-----------------------------------
UserID | UserName | Age | Salary
-----------------------------------
1 | foo | 28 | 28000
-----------------------------------
This is just an example. In my real project I have around 6 columns like Age and Salary in above tables.
In table 2 , each record will have only have one value i.e if Age has value then Salary will be NULL and viceversa.
UPDATE :
Table 2 has CreatedDate Column. So i want to get latest "NOTNULL" CELL Value instead of maximum value.
You can get this done using a simple MAX() and GROUP BY:
select t1.userid,t1.username, MAX(t2.Age) as Age, MAX(t2.Salary) as Salary
from table1 t1 join
table2 t2 on t1.userid=t2.userid
group by t1.userid,t1.username
Result:
userid username Age Salary
--------------------------------
1 foo 28 35000
Sample result in SQL Fiddle
Note: I'm giving you the benefit of the doubt that you know what you're doing, and you just haven't told us everything about your schema.
It looks like Table 2 is actually an "updates" table, in which each row contains a delta of changes to apply to the base entity in Table 1. In which case you can retrieve each column's data with a correlated join (technically an outer-apply) and put the results together. Something like the following:
select a.UserID, a.UserName,
coalesce(aAge.Age, a.Age),
coalesce(aSalary.Salary, a.Salary)
from [Table 1] a
outer apply (
select Age
from [Table 2] x
where x.UserID = a.UserID
and x.Age is not null
and not exists (
select 1
from [Table 2] y
where x.UserID = y.UserID
and y.Id > x.Id
and y.Age is not null
)
) aAge,
outer apply (
select Salary
from [Table 2] x
where x.UserID = a.UserID
and x.Salary is not null
and not exists (
select 1
from [Table 2] y
where x.UserID = y.UserID
and y.Id > x.Id
and y.Salary is not null
)
) aSalary
Do note I am assuming you have at minimum an Id column in Table 2 which is monotonically increasing with each insert. If you have a "change time" column, use this instead to get the latest row, as it is better.
To get the latest value based on CreatedDate, you can use ROW_NUMBER to filter for latest rows. Here the partition is based UserID and the other columns, Age and Salary.
SQL Fiddle
;WITH Cte AS(
SELECT
UserID,
Age = MAX(Age),
Salary = MAX(Salary)
FROM(
SELECT *, Rn = ROW_NUMBER() OVER(
PARTITION BY
UserID,
CASE
WHEN Age IS NOT NULL THEN 1
WHEN Salary IS NOT NULL THEN 2
END
ORDER BY CreatedDate DESC
)
FROM Table2
)t
WHERE Rn = 1
GROUP BY UserID
)
SELECT
t.UserID,
t.UserName,
Age = ISNULL(c.Age, t.Age),
Salary = ISNULL(c.Salary, t.Salary)
FROM Table1 t
LEFT JOIN Cte c
ON t.UserID = c.UserID
following query should work(working fine in MSSQL) :
select a.userID,a.username,b.age,b.sal from <table1> a
inner join
(select userID,MAX(age) age,MAX(sal) sal from <table2> group by userID) b
on a.userID=b.userID

SQL - Join two tables without unique fields

This is my first post in this forum so please be understanding.
I have following issue.
I want to two join two tables:
Table1:
Product | Start Date | End Date
-------------------------------------
Product1 | 01/01/2014 | 01/05/2015
Product2 | 01/03/2014 | 01/01/2015
Table2:
Product | Start Date | End Date | Value
--------------------------------------------
Product1 | 01/01/2014 | 01/02/2015 | 10
Product1 | 02/02/2014 | 01/04/2015 | 15
Product1 | 02/04/2014 | 01/05/2015 | 15
Product2 | 01/03/2014 | 04/05/2014 | 5
Product2 | 05/05/2014 | 01/01/2015 | 5
To have a table with latest value like:
Product | Start Date | End Date | Value
------------------------------------------------
Product1 | 02/04/2014 | 01/05/2015 | 15
Product2 | 05/05/2014 | 01/01/2015 | 5
I need to join them and not use just the second table because both of them have more unique columns that I need to use.
I was thinking about firstly using some kind of IF function on second table to make one row per product (the one with latest start date) and than join it simply then with first table. But I have no idea how to do the first part.
I am really looking forward for your help.
Regards,
Matt
Just use WHERE NOT EXISTS to filter out everything but the latest date from TABLE2 (I am assuming that you are asking for the latest STARTDATE from TABLE2; also I add 'SomeOtherField' to Table1, because otherwise you could just query Table2):
SELECT t1.Product, t1.SomeOtherField, t2.StartDate, t2.EndDate, t2.Value
FROM Table1 t1
JOIN (SELECT a.Product, a.StartDate, a.EndDate, a.Value FROM Table2 a
WHERE NOT EXISTS (SELECT * FROM Table2 b
WHERE b.Product = a.Product AND b.StartDate > a.StartDate)) t2
ON (t2.Product = t1.Product)
This is possible, the query will involve three steps:
Find all the max start date for each product in table 2. Hint: use group by.
Join table 2 with the result from #1 to get the Value.
Join table 1 with the result from #2 to filter out products that are not in table 1.
Not sure you need Table1 at all in your example, you just need to aggregate Table2 to find the MAX([Start Date] for each Product:
SELECT a.*
FROM Table2 a
JOIN (SELECT Product,MAX([Start Date]) AS Mx_Start_Dt
FROM Table2
GROUP BY Product
) b
ON a.Product = b.Product
AND a.[Start Date] = b.Mx_Start_Dt
If you do need to bring in fields from Table you can just add another JOIN:
SELECT a.*,b.*
FROM Table1 a
JOIN (SELECT a.*
FROM Table2 a
JOIN (SELECT Product,MAX([Start Date]) AS Mx_Start_Dt
FROM Table2
GROUP BY Product
) b
ON a.Product = b.Product
AND a.[Start Date] = b.Mx_Start_Dt
) c
ON a.Product = b.Product
If using a database that supports analytic functions, you can make it cleaner via the ROW_NUMBER() function:
;with cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Product ORDER BY [Start Date] DESC) AS RN
FROM Table2
)
SELECT *
FROM Table1 a
JOIN cte b
ON a.Product = b.Product
AND b.RN = 1
Here is a solution using ROW_NUMBER in SQLServer:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY Product ORDER BY StartDate DESC) RN
FROM #T
) Results
WHERE RN = 1

SQL Select First column and for each row select unique ID and the last date

I have a problems this mornig , I have tried many solutions and nothing gave me the expected result.
I have a table that looks like this :
+----+----------+-------+
| ID | COL2 | DATE |
+----+----------+-------+
| 1 | 1 | 2001 |
| 1 | 2 | 2002 |
| 1 | 3 | 2003 |
| 1 | 4 | 2004 |
| 2 | 1 | 2001 |
| 2 | 2 | 2002 |
| 2 | 3 | 2003 |
| 2 | 4 | 2004 |
+----+----------+-------+
And I have a query that returns a result like this :
I have the unique ID and for this ID I want to take the last date of the ID
+----+----------+-------+
| ID | COL2 | DATE |
+----+----------+-------+
| 1 | 4 | 2004 |
| 2 | 4 | 2004 |
+----+----------+-------+
But I don't have any idea how I can do that.
I tried Join , CROSS APPLY ..
If you have some idea ,
Thank you
Clement FAYARD
declare #t table (ID INT,Col2 INT,Date INT)
insert into #t(ID,Col2,Date)values (1,1,2001)
insert into #t(ID,Col2,Date)values (1,2,2001)
insert into #t(ID,Col2,Date)values (1,3,2001)
insert into #t(ID,Col2,Date)values (1,4,2001)
insert into #t(ID,Col2,Date)values (2,1,2002)
insert into #t(ID,Col2,Date)values (2,2,2002)
insert into #t(ID,Col2,Date)values (2,3,2002)
insert into #t(ID,Col2,Date)values (2,4,2002)
;with cte as(
select
*,
rn = row_number() over(partition by ID order by Col2 desc)
from #t
)
select
ID,
Col2,
Date
from cte
where
rn = 1
SELECT ID,MAX(Col2),MAX(Date) FROM tableName GROUP BY ID
If col2 and date allways the highest value in combination than you can try
SELECT ID, MAX(COL2), MAX(DATE)
FROM Table1
GROUP BY ID
But it is not realy good.
The alternative is a subquery with:
SELECT yourtable.ID, sub1.COL2, sub1.DATE
FROM yourtable
INNER JOIN -- try with CROSS APPLY for performance AND without ON 1=1
(SELECT TOP 1 COL2, DATE
FROM yourtable sub2
WHERE sub2.ID = topquery.ID
ORDER BY COL2, DATE) sub1 ON 1=1
You didn't tell what's the name of your table so I'll assume below it is tbl:
SELECT m.ID, m.COL2, m.DATE
FROM tbl m
LEFT JOIN tbl o ON m.ID = o.ID AND m.DATE < o.DATE
WHERE o.DATE is NULL
ORDER BY m.ID ASC
Explanation:
The query left joins the table tbl aliased as m (for "max") against itself (alias o, for "others") using the column ID; the condition m.DATE < o.DATE will combine all the rows from m with rows from o having a greater value in DATE. The row having the maximum value of DATE for a given value of ID from m has no pair in o (there is no value greater than the maximum value). Because of the LEFT JOIN this row will be combined with a row of NULLs. The WHERE clause selects only these rows that have NULL for o.DATE (i.e. they have the maximum value of m.DATE).
Check the SQL Antipatterns: Avoiding the Pitfalls of Database Programming book for other SQL tips.
In order to do this you MUST exclude COL2 Your query should look like this
SELECT ID, MAX(DATE)
FROM table_name
GROUP BY ID
The above query produces the Maximum Date for each ID.
Having COL2 with that query does not makes sense, unless you want the maximum date for each ID and COL2
In that case you can run:
SELECT ID, COL2, MAX(DATE)
GROUP BY ID, COL2;
When you use aggregation functions(like max()), you must always group by all the other columns you have in the select statement.
I think you are facing this problem because you have some fundemental flaws with the design of the table. Usually ID should be a Primary Key (Which is Unique). In this table you have repeated IDs. I do not understand the business logic behind the table but it seems to have some flaws to me.