Get value with MAX(date) from two table - sql-server

I have two tables.
MainTable:
MainID | LastValue | LastReadingDate
1 | 234 | 01.01.2012
2 | 534 | 03.02.2012
Readings:
MainID | ValueRead | ReadingDate
1 | 123 | 03.02.2012
1 | 488 | 04.03.2012
2 | 324 | 03.02.2012
2 | 683 | 05.04.2012
I want to get
SELECT MainTable.MainID, MainTable.LastValue, MainTable.LastReadingDate, (SELECT ValueRead, MAX(ReadingDate)
FROM Readings
WHERE Readings.MainID=MainTable.MainID ORDER BY ValueRead)
In other words, I want to get the current LastValue and LastReadingDate from MainTable along side the ValueRead with the most recent ReadingDate from Readings.

Here is a query you could use. It'll show all MainTable entries, including those that doesn't have a "Reading" entry yet. Change the LEFT JOIN to an INNER JOIN if you don't want it like that.
WITH LastReads AS (
SELECT ROW_NUMBER() OVER (PARTITION BY MainID ORDER BY ReadingDate DESC) AS ReadingNumber,
MainID,
ValueRead,
ReadingDate
FROM Readings
)
SELECT M.MainID, M.LastValue, M.LastReadingDate, R.ValueRead, R.ReadingDate
FROM MainTable M
LEFT OUTER JOIN LastReads R
ON M.MainID = R.MainID
AND R.ReadingNumber = 1 -- Last reading, use 2 or 3 to get the 2nd newest, 3rd newest, etc.
SQLFiddle-link: http://sqlfiddle.com/#!3/16c68/3
Another link with N number of readings per mainid: http://sqlfiddle.com/#!3/16c68/4

Not tried this myself, but here goes. Please try
select max(r.readingdate), max(t.lastvalue), max(t.lastreadingdate)
from readings r inner join
( select MainID, LastValue, LastReadingDate
from MainTable m
where LastReadingDate =
(select max(minner.LastReadingDate)
from MainTable minner
where minner.MainID = m.MainID
)
) t
on (r.mainid = t.mainid)

try this:
select M.LastValue, M.LastReadingDate,
(select top 1 ValueRead from Readings where MainID=M.MainID order by ReadingDate desc)
from MainTable M

Related

Join with a max and nulls

I have 2 tables:
People:
ID | Name
----------
1 | John
2 | David
3 | Jennifer
another which is has a simple FK to the first
Note:
ID | People_ID | Note
----------------------
1 | 1 | A note
2 | 1 | Another note
3 | 3 | Jen's note
I want to get the note associated with the max(ID) from Note for each person, or a null if no notes, so the desired result is:
People_ID | Name | Note
----------------------------
1 |John | Another Note
2 |David | NULL
3 |Jennifer| Jen's Note
I can perform a join, but can't include David because the max criteria doesn't bring back the null column. Any help please?
That's a left join - and I would recommend pre-aggregating the notes in a subquery:
select p.*, n.*
from people p
left join (
select people_id, max(id) max_note_id
from note
group by people_id
) n on n.people_id = p.id
There are situations where a lateral join would be more efficient:
select p.*, n.*
from people p
outer apply (
select top(1) id max_note_id
from note n
where n.people_id = p.id
order by id desc
) n
The nice thing about the lateral join is that you can easily bring more columns from the top matching record in the note table if you want to (like the text of the note, or else).
You can use below query:
Demo
SELECT A.NAME, A.ID, MAX(B.ID) FROM PEOPLE A LEFT OUTER JOIN NOTE B
ON (A.ID = B.PEOPLE_ID) GROUP BY A.NAME, A.ID;

How to repeat values in case of null values on left join

I have a table with a calendar, and a table with rates. In the table with the rates, there are no values existing for days in the weekend. I'm trying to join the two, in order to have a table where there is a rate for all days, and I need the rates in the weekend to be the latest available rate. Instad of it showing NULL values, as it would when you make a left join and the record doesn't exist, it should just take the latest available, repeating the previous value.
I have the below code, which works, but it takes 2 min to do on 7,397 rows, which is way too long.
Does anyone know a faster way to get the same results?
SELECT
c.CalendarID,
MAX(r.RateID)
FROM Dim_Calendar c
LEFT JOIN Dim_Rates r ON r.RateDate <= c.CalendarID
What I get without <= and just an = is the following
CalendarID | RateID
20131001 | 2
20131002 | 3
20131003 | 4
20131004 | 5
20131005 | NULL
20131006 | NULL
20131007 | 6
And this is the desired table:
CalendarID | RateID
20131001 | 2
20131002 | 3
20131003 | 4
20131004 | 5
20131005 | 5
20131006 | 5
20131007 | 6
You can use LAG() window function:
SELECT c.CalendarID,
COALESCE(
r.RateID,
LAG(r.RateID, 1) OVER (ORDER BY c.CalendarID),
LAG(r.RateID, 2) OVER (ORDER BY c.CalendarID)
) RateID
FROM Dim_Calendar c LEFT JOIN Dim_Rates r
ON r.RateDate = c.CalendarID
ORDER BY c.CalendarID
See the demo.
Results:
> CalendarID | RateID
> ---------: | :-----
> 20131001 | 2
> 20131002 | 3
> 20131003 | 4
> 20131004 | 5
> 20131005 | 5
> 20131006 | 5
> 20131007 | 6
You could use a correlated subquery to fill the gaps:
SELECT
c.CalendarID,
(SELECT TOP 1 r.RateID FROM Dim_Rates r
WHERE r.RateDate <= c.CalendarID AND r.RateID IS NOT NULL
ORDER BY r.RateDate DESC) AS RateID
FROM Dim_Calendar c
ORDER BY c.CalendarID;
This query can be improved by using the following index:
CREATE INDEX idx ON Dim_Rates (RateDate, RateID);
As pointed out, you need to check for proper and covering indexing. It appears you are running a against a DW DB and if that is the case then you can replace the CTE with indexed temp tables if the esitmated row count approximation is way off in the query plan.
;WITH NormalizedData AS
(
SELECT
RateID,CalendarID,
VirtualGroupID = SUM(LastRecordBeforeGap) OVER (ORDER BY CalendarID ROWS UNBOUNDED PRECEDING)
FROM
(
SELECT RateID,CalendarID,
LastRecordBeforeGap = CASE WHEN LEAD(RateID) OVER(ORDER BY CalendarID) IS NULL AND RateID IS NOT NULL THEN 1 ELSE 0 END
FROM
Dim_Calendar c
LEFT JOIN Dim_Rates r ON r.RateDate = c.CalendarID
)AS x
)
SELECT
RateID = ISNULL(RateID, SUM(RateID) OVER(PARTITION BY VirtualGroupID)),
CalendarID
FROM
NormalizedData

using all values from one column in another query

I am trying to find a solution for the following issue that I have in sql-server:
I have one table t1 of which I want to use each date for each agency and loop it through the query to find out the avg_rate. Here is my table t1:
Table T1:
+--------+-------------+
| agency | end_date |
+--------+-------------+
| 1 | 2017-10-01 |
| 2 | 2018-01-01 |
| 3 | 2018-05-01 |
| 4 | 2012-01-01 |
| 5 | 2018-04-01 |
| 6 | 2017-12-01l |
+--------+-------------+
I literally want to use all values in the column end_date and plug it into the query here (I marked it with ** **):
with averages as (
select a.id as agency
,c.rate
, avg(c.rate) over (partition by a.id order by a.id ) as avg_cost
from table_a as a
join rates c on a.rate_id = c.id
and c.end_date = **here I use all values from t1.end_date**
and c.Start_date = **here I use all values from above minus half a year** = dateadd(month,-6,end_date)
group by a.id
,c.rate
)
select distinct agency, avg_cost from averages
order by 1
The reason why I need two dynamic dates is that the avg_rates vary if you change the timeframe between these dates.
My problem and my question is now:
How can you take the end_date from table t1 plug it into the query where c.end_date is and loop if through all values in t1.end_date?
I appreciate your help!
Do you really need a windowed average? Try this out.
;with timeRanges AS
(
SELECT
T.end_date,
start_date = dateadd(month,-6, T.end_date)
FROM
T1 AS T
)
select
a.id as agency,
c.rate,
T.end_date,
T.start_date,
avg_cost = avg(c.rate)
from
table_a as a
join rates c on a.rate_id = c.id
join timeRanges AS T ON A.DateColumn BETWEEN T.start_date AND T.end_date
group by
a.id ,
c.rate,
T.end_date,
T.start_date
You need a date column to join your data against T1 (I called it DateColumn in this example), otherwise all time ranges would return the same averages.
I can think of several ways to do this - Cursor, StoredProcedure, Joins ...
Given the simplicity of your query, a cartesian product (Cross Join) of Table T1 against the averages CTE should do the magic.

Updating 1 table from another using wheres

Trying to update one column, from another table with the highest Date.
Table 1 Example:
PartNumber | Cost
1000 | .10
1001 | .20
Table 2 Example:
PartNumber | Cost | Date
1000 | .10 | 2017-01-01
1000 | .50 | 2017-02-01
1001 | .20 | 2017-01-01
1002 | .50 | 2017-02-02
I would like to update table 1 with the most recent values from table2, which would be .50 for each... The query I use to update this has worked just fine until I realized I was not grabbing the correct Cost because there were multiples.. I now want to grab the highest dated revision.
My query:
UPDATE dex_mfgx..insp_master
SET dex_mfgx..insp_master.costperpart = t2.sct_cst_tot
FROM dex_mfgx..insp_master AS t1
INNER JOIN qad_repl..sct_det_sql AS t2
ON t1.partnum = t2.sct_part
WHERE t1.partnum = t2.sct_part and t2.sct_cst_date = MAX(t2.sct_cst_date) ;
My Error:
Msg 147, Level 15, State 1, Line 6
An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.
Not having much luck with HAVING or GROUPING, although I havent used them much..
Any have an idea that would help?
I think I understand what you are trying to solve now. Thanks to Lamak for setting me straight as I was way off base originally.
Something like this I think is what you are looking for.
with TotalCosts as
(
SELECT t2.sct_cst_tot
, t1.partnum
, RowNum = ROW_NUMBER() over(partition by t1.partnun order by t2.sct_cst_date desc)
FROM dex_mfgx..insp_master AS t1
INNER JOIN qad_repl..sct_det_sql AS t2 ON t1.partnum = t2.sct_part
)
update t1
set costperpart = tc.sct_cst_tot
from dex_mfgx..insp_master AS t1
join TotalCosts tc on tc.partnum = t1.partnum
where tc.RowNum = 1

Sum variable amount of intervals together

we just changed our telephony system and every agents are now being logged through 15 minute intervals and we need 1 line per event
table event:
empid | code | timestamp | duration
5111 | 5 | 09:45:00 | 45
5222 | 2 | 09:58:00 | 120
5111 | 5 | 10:00:00 | 900
5111 | 5 | 10:15:00 | 900
5111 | 5 | 10:15:30 | 30
5222 | 5 | 11:00:00 | 8
5222 | 5 | 11:00:05 | 5
timestamp is writen after the fact, so a timestamp at 9:45:00 with a duration of 45 was from 9:44:15 and since the interval stopped at 9:45, it was written at that time, but i need 9:44:15 save
result should give me
empid | code | timestamp | duration
5111 | 5 | 09:44:15 | 1875
5222 | 2 | 09:56:00 | 120
5222 | 5 | 10:59:52 | 13
The problem is the phones are locked with a 2 hours max delay, and as you can see with my employee # 5222 he spent 13 seconds on two lines... i could join the same table 10 times. 1 to avoid when there is the same code where the end time of the previous line = the starttime of the new line
this is on MSSQL 2008
Select e.empid
,e.code
,convert(time(0),DATEADD(ss,- e.Duration, e.timestamp))
,e.duration + isnull(e1.duration,0) + isnull(e2.duration,0)
from [event] e
left join [event] e0 on
convert(TIME(0),DATEADD(ss,- e.Duration, e.timestamp)) = e0.timestamp
and
e.empid = e0.empid
and
e.code = e0.code
left join [event] e1 on
convert(TIME(0),DATEADD(ss,- e1.Duration, e1.timestamp)) = e.timestamp
and
e.empid = e1.empid
and
e.code = e1.code
left join [event] e2 on
convert(TIME(0),DATEADD(ss,- e2.Duration, e2.timestamp)) = e1.timestamp
and
e2.empid = e1.empid
and
e2.code = e1.code
--etc......
where isnull(e0.duration,'-10') = '-10'
This works but far from optimal...
i would rather use an aggregate function but i dont know how to write it as there is no comon key other than last timestamps match with new - duration with this table!
it is important to know that agent 5111 could go again on code 5 on the same day, and i would need 2 lines for this one.... if not it would have been too easy!
thank you in advance!
Try this. I have commented in the code, but the basic algorithm
find rowswhich are continuations i.e. there exists a row which matches once
you subtract the duration
find the "originals" i.e. the start of each call by subtracting the continuations
for each original, find the next original so we can determine a range of times to look for continuations
join it all together and add the total duration from continuations appropriate to each original
Hope this helps, it was an interesting challenge!
declare #data table
(
empid int,
code int,
[timestamp] time,
duration int
);
insert into #data values(5111,5,'09:45',45),
(5222,2,'09:58',120),
(5111,5,'10:00',900),
(5111,5,'10:15',900),
(5111,5,'10:15:30',30),
(5222,5,'11:00',8),
(5222,5,'11:00:05',5),
-- added these rows to include the situation you describe where 5111 goes again on code 5:
(5111,5,'13:00',45),
(5111,5,'13:15',900),
(5111,5,'13:15:25',25);
-- find where a row is a continuation
with continuations as (
select a.empid, a.code, a.[timestamp] , a.duration
from #data a
inner join #data b on a.empid = b.empid
and a.code = b.code
where dateadd(ss, -a.duration, a.[timestamp]) = b.[timestamp]
),
-- find the "original" rows as the complement of continuations
originals as
(
select d.empid, d.code, d.[timestamp], d.duration
from #data d
left outer join continuations c on d.empid = c.empid and d.code = c.code and d.timestamp = c.timestamp
where c.empid is null
),
-- to hand the situation where we have more than one call for same agent and code,
-- find the next timestamp for each empid/code
nextcall as (
select a.*, a2.[timestamp] nex
from originals a
outer apply (
select top 1 [timestamp]
from originals a2
where a2.[timestamp] > a.[timestamp]
and a.empid = a2.empid
and a.code = a2.code
order by a2.[timestamp] desc
) a2
)
select o.empid,
o.code,
dateadd(ss, -o.duration, o.timestamp) as [timestamp],
o.duration + isnull(sum(c.duration),0) as duration
from originals o
left outer join nextcall n on o.empid = n.empid and o.code = n.code and o.[timestamp] = n.[timestamp]
left outer join continuations c on o.empid = c.empid
and o.code = c.code
-- filter the continuations on the range of times based on finding the next one
and c.[timestamp] > o.[timestamp]
and (n.nex is null or c.[timestamp] < n.nex)
group by o.empid,
o.code,
o.duration,
o.[timestamp]

Resources