Microsoft SQL Server - Getting latest record for value - sql

I'm very not SQL savvy at all but I've been tasked with putting together a report for CIS benchmark test scores. I need to get the most recent score for each system without duplicates.
The system_identifier table houses the system info fields; I'm grabbing the value type 1426 (hostname)
The test_result table contains the score and date fields.
Here is my current query:
SELECT system_identifier.value AS [System Name], test_result.score + '%' AS [Most Recent Score], LEFT(MAX(test_result.upload_date), 10) AS [Test Date]
FROM system_identifier INNER JOIN test_result ON system_identifier.target_system_id = test_result.target_system_id
WHERE (system_identifier.type_id = 1426)
GROUP BY system_identifier.value, test_result.score
ORDER BY system_identifier.value
This is currently returning all records where the System Name + Score combination is unique.
System Name Most Recent Score Test Date
----------- ----------------- ---------
system1 84 2019-06-10
system1 87 2019-08-24
system1 94 2019-09-14
system2 78 2019-07-22
system2 85 2019-09-12
system3 65 2019-05-23
system3 74 2019-07-03
system3 81 2019-08-09
system3 91 2019-09-10
Here is what I need:
System Name Most Recent Score Test Date
----------- ----------------- ---------
system1 94 2019-09-14
system2 85 2019-09-12
system3 91 2019-09-10
I've tried researching this but I'm not sure how to frame my question for a search, so I thought I'd ask here since I can be more verbose with what I'm looking for.
Let me know if you need any more info.
EDIT
Neeraj's answer put me on the right track, so marking as the answer.
I ended up creating a view with the initial SELECT to put everything in one table called "merged." Then, I had success with this query (with some formatting to make it pretty):
SELECT DISTINCT
UPPER(merged.value) AS Hostname,
merged.score + '%' AS Score
LEFT(merged.upload_date,10) AS [Date Uploaded],
FROM dbo.merged
INNER JOIN (SELECT merged.value AS hostname, MAX(merged.upload_date) AS Uploaded
FROM dbo.merged
GROUP BY merged.value) t2
ON merged.value = t2.hostname
AND merged.upload_date = t2.Uploaded
WHERE merged.type_id = 1426
ORDER BY hostname

Use correlated subquery:
select t1.SystemName, t1.MostRecentScore, t1.TestDate
from myTable t1
where t1.TestDate =
(
select max(t2.TestDate)
from myTable t2
where t2.SystemName = t1.SystemName
)

Use ROW_NUMBER():
SELECT System_Name,Most_Recent_Score,Test_Date
FROM (
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY system_name ORDER BY test_date DESC) rn
FROM mytable t
) x WHERE rn = 1
In the inner query, ROW_NUMBER() assigns a rank to each record within groups of records having the same system name, ordered by descending test date. Then the outer query selects the top record in each group.
Demo on DB Fiddle:
System_Name | Most_Recent_Score | Test_Date
:---------- | ----------------: | :------------------
system1 | 94 | 14/09/2019 00:00:00
system2 | 85 | 12/09/2019 00:00:00
system3 | 91 | 10/09/2019 00:00:00

You could also use a subquery as
WITH T AS
(
SELECT *
FROM
(
VALUES
('system1', 84, '2019-06-10'),
('system1', 87, '2019-08-24'),
('system1', 94, '2019-09-14'),
('system2', 78, '2019-07-22'),
('system2', 85, '2019-09-12'),
('system3', 65, '2019-05-23'),
('system3', 74, '2019-07-03'),
('system3', 81, '2019-08-09'),
('system3', 91, '2019-09-10')
) T(SystemName, MostRecentScore, TestDate)
)
SELECT T.*
FROM T INNER JOIN
(
SELECT SystemName,
MAX(TestDate) TestDate
FROM T
GROUP BY SystemName
) TT ON T.SystemName = TT.SystemName
AND
T.TestDate = TT.TestDate;
Online Demo

Related

Select all user that have a specific date and have been recorded only one time

I have this table.
My Sql table in (SQL Fiddle)
ID Date Value
___ ____ _____
3241 01/01/00 15456
3241 9/17/12 5
3241 9/16/12 100
3241 9/15/12 20
4355 01/01/00 01
4355 9/16/12 12
4355 9/15/12 132
4355 9/14/12 4
1001 01/01/00 456
1001 9/16/12 125
5555 01/01/00 01
1234 01/01/00 01
1234 9/16/12 45
2236 01/01/00 879
2236 9/15/12 128
2236 9/14/12 323
2002 01/01/00 567
I would like, to select all the record that have 01-01-00 as date and have been showed only one time.
The result that i'm trying to have is like the table below.
ID Date Value
___ ____ _____
5555 01/01/00 01
2002 01/01/00 567
I tried to use HAVING clause but because of the GROUP BY, the result is wrong because one of my select has more than one record which isn't good for my case.
My Wrong Attempt:
SELECT * FROM
(SELECT *
FROM table1
GROUP BY id, date, value
HAVING count(Id)=1) t1
WHERE date='01-01-00'
Query Result (SQL Fiddle)
I would use:
select id, max(date) as date, max(value) as value
from t1
group by id
having max(date) = '01-01-00' and count(*) = 1;
A somewhat faster method might be:
select t1.*
from t1
where date = '01-01-00' and
not exists (select 1 from t1 tt1 where tt1.id = t1.id and tt1.date <> '01-01-00');
This can take advantage of index on t1(date) and t1(id, date).
Use IN
SELECT *
FROM table1
WHERE id IN (
SELECT id
FROM table1
GROUP BY id
HAVING count(Id)=1
) and date='01-01-00'
I just didn't notice that i make a error in my group by instead of making only the ID, I put all the columns
SELECT * FROM
(SELECT *
FROM Table1
GROUP BY `ID`
HAVING count(`ID`)=1) t1
WHERE `Date`='2000-01-01 00:00:00'
However for my problem, I take this solution from Gordon Linoff because it's seems better for me.
PS:I have 2 million records.
Change your query as
SELECT *
FROM
(SELECT *
FROM table1
GROUP BY id
HAVING count(id)=1) t1
WHERE t1.date='01-01-00'

SQL Query to display timekeeping table

I have a table like this
id Marca CodCartela Line Post status Time
---- ------ ----------- ---- ---- ------ -----------------------
178 346 4516645709 AS01 55 1 2015-04-05 02:30:12.627
179 346 4516645709 AS01 55 0 2015-04-05 02:31:23.593
180 346 4516645709 AS01 88 1 2015-04-05 02:32:05.107
181 346 4516645709 AS01 88 0 2015-04-05 02:32:22.060
Status 1 means IN and status 0 means OUT.
What I want is to display in and out time on the same line for each person.
Example
id Marca CodCartela Line Post status TimeIN TimeOUT
---- ---------------- ---- ---- ------ -----------------------------------------
178 346 4516645709 AS01 55 1 2015-04-05 02:30:12.627 2015-04-05 02:31:23.593
Is this possible or I need to rethink all the timekeeping aplications :(
This will track each person's Ins and Outs. Just switch yourTable in the CTE to your actual table name.
WITH CTE_yourTable
AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Marca ORDER BY [Time]) activity_num
FROM yourTable
)
SELECT A.ID,
A.Marca,
A.CodCartela,
A.[Line],
A.[Post],
--A.[Status],
A.[Time] AS TimeIn,
B.[Time] AS [TimeOut]
FROM CTE_yourTable A
LEFT JOIN CTE_yourTable B
ON A.Marca = B.Marca
AND A.activity_num = B.activity_num - 1
WHERE A.[Status] = 1
Results:
ID Marca CodCartela Line Post TimeIn TimeOut
----------- ----------- --------------------------------------- ---- ----------- ---------------------- ----------------------
178 346 4516645709 AS01 55 2015-04-0502:30:12.627 2015-04-0502:31:23.593
180 346 4516645709 AS01 88 2015-04-0502:32:05.107 2015-04-0502:32:22.060
Its simple use conditional Aggregate.
SELECT marca,
codcartela,
line,
post,
Max(CASE WHEN status = 1 THEN time END) AS TimeIN,
Max(CASE WHEN status = 0 THEN time END) AS TimeOUT
FROM yourtable
GROUP BY marca,
codcartela,
line,
post
Assuming that the combination post, status is unique you can do a join:
select
t1.*
,t2.Time as timeOut
from tab t1
left join tab t2
on t1.post = t2.post
and t2.status = 0
where t1.status = 1
Otherwise you might have to add additional conditions.
I don't know exactly how is the schema structure, and didn't get you well, I'm not sure but according to your sample data and desired output I think you want something like this:
select t1.marca,
t1.codcartela,
t1.line,
t1.post,
t1.time as TimeIN,
t2.time as TimeOUT
FROM table_name t1
LEFT OUTER JOIN table_name t2 on t1.post=t2.post
and t1.marca = t2.marca
WHERE t1.status=1 and isnull(t2.status,0)=0
Edit: changed join column to marca as mentioned it is for person

Latest Data based on DateTime or CustMCId

I have a table in which different Clients are assign to different MC. Like Client (84) switch the MC 3 times. Now I want to get the latest MC of Client=84. I make this Query
select max(cstmrMC.CustMCId),cstmrMC.CustId,cstmrMC.MCID,cstmrMC.AssignDate
from CustomerMC cstmrMC
where cstmrMC.CustId=84
group by cstmrMC.CustMCId,cstmrMC.CustId,cstmrMC.MCID,cstmrMC.AssignDate
ORDER BY cstmrMC.CustMCId,cstmrMC.CustId,cstmrMC.MCID,cstmrMC.AssignDate
which shows this result
CustMCId CustId MCID AssignDate
52 84 18 2013-10-01 18:21:56.000
59 84 7 2013-09-09 16:10:06.000
80 84 19 2013-10-09 03:54:00.000
156 84 21 2013-11-11 00:00:00.000
NOw I want only this
156 84 21 2013-11-11 00:00:00.000
How can I get this result????
Use the row_number function to partition and order the customers so that the most recent MCID (based on AssignDate) is first within each customer.
WITH cteCustomers AS (
SELECT CustMCId, CustId, MCID, AssignDate,
ROW_NUMBER() OVER(PARTITION BY CustId ORDER BY AssignDate DESC) AS RowNum
FROM CustomerMC
)
SELECT CustMCId, CustId, MCID, AssignDate
FROM cteCustomers
WHERE RowNum = 1;

Sql get latest records of the month for each name

This question is probably answered before but i cant find how to get the latest records of the months.
The problem is that I have a table with sometimes 2 row for the same month. I cant use the aggregate function(I guess) cause in the 2 rows, i have different data where i need to get the latest.
Example:
name Date nuA nuB nuC nuD
test1 05/06/2013 356 654 3957 7033
test1 05/26/2013 113 237 399 853
test3 06/06/2013 145 247 68 218
test4 06/22/2013 37 37 6 25
test4 06/27/2013 50 76 20 84
test4 05/15/2013 34 43 34 54
I need to get a result like:
test1 05/26/2013 113 237 399 853
test3 06/06/2013 145 247 68 218
test4 05/15/2013 34 43 34 54
test4 06/27/2013 50 76 20 84
** in my example the data is in order but in my real table the data is not in order.
For now i have something like:
SELECT Name, max(DATE) , nuA,nuB,nuC,nuD
FROM tableA INNER JOIN
Group By Name, nuA,nuB,nuC,nuD
But it didn't work as i want.
Thanks in advance
Edit1:
It seems that i wasn't clear with my question...
So i add some data in my example to show you how i need to do it.
Thanks guys
Use SQL Server ranking functions.
select name, Date, nuA, nuB, nuC, nuD from
(Select *, row_number() over (partition by name, datepart(year, Date),
datepart(month, Date) order by Date desc) as ranker from Table
) Z
where ranker = 1
Try this
SELECT t1.* FROM Table1 t1
INNER JOIN
(
SELECT [name],MAX([date]) as [date] FROM Table1
GROUP BY [name],YEAR([date]),MONTH([date])
) t2
ON t1.[date]=t2.[date] and t1.[name]=t2.[name]
ORDER BY t1.[name]
Can you not just do an order
select * from tablename where Date = (select max(Date) from tablename)
followed by only pulling the first 3?

SQL: Return only first occurrence

I seldomly use SQL and I cannot find anything similar in my archive so I'm asking this simple query question: I need a query which one returns personID and only the first seenTime
Records:
seenID | personID | seenTime
108 3 13:34
109 2 13:56
110 3 14:22
111 3 14:31
112 4 15:04
113 2 15:52
Wanted result:
personID | seenTime
3 13:34
2 13:56
4 15:04
That's what I did & failed:
SELECT t.attendanceID, t.seenPersonID, t.seenTime
(SELECT ROW_NUMBER() OVER (PARTITION BY seenID ORDER BY seenID) AS RowNo,
seenID,
seenPersonID,
seenTime
FROM personAttendances) t
WHERE t.RowNo=1
P.S: Notice SQL CE 4
If your seenTime increases as seenID increases:
select personID, min(seenTime) as seenTime
from personAttendances
group by personID
Update for another case:
If this is not the case, and you really want the seenTime that corresponds with the minimum seenID (assuming seenID is unique):
select a.personID, a.seenTime
from personAttendances as a
join (
-- Get the min seenID for each personID
select personID, min(seenID) as seenID
from personAttendances
group by personID
) as b on a.personID = b.personID
where a.seenID = b.seenID
You're making it way too difficult:
select personID, min(seenTime)
from personAttendances
group by personID
for PostgreSQL there is DISTINCT ON
You need to order by seen time not by seen id:
PARTITION BY seenID ORDER BY seenTime
Add this to your SQL:
and where not exists
(select 1 from personAttendances t2
where t.personID=t2.personID
and t2.seenID < t.seenID)