Data:
Name | Score | Date
==
John | 10 | 09/01/2012
John | 20 | 09/01/2012
John | 5 | 09/01/2012
Frank | 20 | 11/01/2012
I want to run an SQL statement that will pull only the highest score from each day only, so I don't want three scores from one date, only the highest. So the return I'd like from SQL would be:
John | 20 | 09/01/2012
Frank | 20 | 11/01/2012
Is it possible to do this via SQL, currently I can do it after pulling everything by checking dates. But would be great if it's possible to do it direct from the DB.
I have tried a few solutions but as Date and Score aren't distinct, as I want the highest I am unsure of where to proceed. The SQL is being used with a MS Access database.
Here's a complete example. I did it in Oracle, so you might have to tweak the syntax a bit for Access.
CREATE TABLE tbl1 (NAME VARCHAR2(100), score INT, Dt DATE);
INSERT INTO tbl1 VALUES ('John',10,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('John',20,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('John',5,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('Harry',15,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('Frank',20,to_date('20121101','YYYYMMDD'));
select a.dt
, b.name
, a.score
FROM (SELECT dt
, MAX(score) score
FROM tbl1
GROUP BY dt) a
, (SELECT NAME
, dt
, MAX(score) score
FROM tbl1
GROUP BY name, dt) b
WHERE a.dt = b.dt
AND a.score = b.score
Output:
1 11/1/2012 Frank 20
2 9/1/2012 John 20
Note that if two people have highest score for given day, it will print them both. For example:
INSERT INTO tbl1 VALUES ('John',10,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('John',20,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('John',5,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('Harry',20,to_date('20120901','YYYYMMDD'));
INSERT INTO tbl1 VALUES ('Frank',20,to_date('20121101','YYYYMMDD'));
select a.dt
, b.name
, a.score
FROM (SELECT dt
, MAX(score) score
FROM tbl1
GROUP BY dt) a
, (SELECT NAME
, dt
, MAX(score) score
FROM tbl1
GROUP BY name, dt) b
WHERE a.dt = b.dt
AND a.score = b.score
Output:
DT NAME SCORE
1 11/1/2012 Frank 20
2 9/1/2012 John 20
3 9/1/2012 Harry 20
I guess it is pretty simple
Select [Name], max([score]) as Score, [Date] from scoresTable
group by [Date], [Name]
Edited:
More accurate but a little complex
select o.[Name], i.[Date], i.Score from scoresTable o
inner join (select [Date], max(Score) as Score
from scoresTable group by [Date]) as i
on o.[Date] = i.[Date] and i.Score = o.Score
Even More, if your date field contains time too
select o.[Name], i.[Date], i.Score from scoresTable o
inner join (select convert(varchar, [Date], 101) as Date, max(Score) as Score
from scoresTable
group by convert(varchar, [Date], 101)) as i
on convert(varchar, o.[Date], 101) = i.[Date] and i.Score = o.Score
Related
I have two tables, which one table (table A) contains the users' payment data, and the other (Table B) contains the users' rank history.
Table A
IDNO LName FName Start_Date PayType Current_Rank
------------------------------------------------------------
SJ01 Smith John 11/13/2016 Cert AC
DJ01 Doe Jack 10/20/2020 Assignment BC
Table B
IDNO Date Rank
----------------------
SJ01 10/01/2010 CAP
SJ01 10/01/2016 BC
SJ01 10/01/2020 AC
DJ01 01/01/2010 LT
DJ01 01/01/2015 CAP
DJ01 01/01/2020 BC
I need to show the user's rank according to the start_date from the table A and bring in the rank from the table B. So my end result can look like this:
IDNO LName FName Start_Date PayType Rank
------------------------------------------------------------
SJ01 Smith John 11/13/2016 Cert BC
DJ01 Doe Jack 10/20/2020 Assignment BC
How can I join these two tables and compare the dates, so that I can bring in the rank from the history table based on the start_date from the table A?
Another very simple way is to just select the corresponding Rank directly using a correlated query
select a.*,
(select top(1) [rank] from TableB b
where b.idno=a.idno and a.start_date>b.date
order by b.date desc) as [Rank]
from TableA a
here is one way:
select a.* , c.Rank From TableA a
cross apply (select top 1 * from tableB b
where a.IdNo = b.IDno
and a.Start_Date > b.date
order by b.date desc
) c
;WITH CTE
AS
(
SELECT A.IDNO,A.LName,A.FName,A.Start_Date,A.PayType,B.Rank,RNo=ROW_NUMBER() OVER(PARTITION BY B.IDNO ORDER BY B.DATE DESC)
FROM Table_B B
JOIN Table_A A ON B.IDNO=A.IDNO AND A.Start_Date>=B.Date
) SELECT IDNO,LName,FName,Start_Date,PayType,Rank
FROM CTE WHERE RNo=1
ORDER BY Start_Date
select idno, lname, fname, start_date, paytype, pp.rank current_rank
from
(select xx.idno, xx.rank, min(xx.datediff) from
(select tableA.idno, tableA.start_date - tableB.date datediff, tableB.rank
from tableA, tableB
where tableA.idno = tableB.idno) XX
group by xx.idno, xx.rank)pp,
tableA
where tableA.idno = pp.idno
I have bunch of data out of which I'm showing ID, max date and it's corresponding values (user id, type, ...). Then I need to take MAX date for each ID, substract 30 days and show first date and it's corresponding values within this date period.
Example:
ID Date Name
1 01.05.2018 AAA
1 21.04.2018 CCC
1 05.04.2018 BBB
1 28.03.2018 AAA
expected:
ID max_date max_name previous_date previous_name
1 01.05.2018 AAA 05.04.2018 BBB
I have working solution using subselects, but as I have quite huge WHERE part, refresh takes ages.
SUBSELECT looks like that:
(SELECT MIN(N.name)
FROM t1 N
WHERE N.ID = T.ID
AND (N.date < MAX(T.date) AND N.date >= (MAX(T.date)-30))
AND (...)) AS PreviousName
How'd you write the select?
I'm using TSQL
Thanks
I can do this with 2 CTEs to build up the dates and names.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE t1 (ID int, theDate date, theName varchar(10)) ;
INSERT INTO t1 (ID, theDate, theName)
VALUES
( 1,'2018-05-01','AAA' )
, ( 1,'2018-04-21','CCC' )
, ( 1,'2018-04-05','BBB' )
, ( 1,'2018-03-27','AAA' )
, ( 2,'2018-05-02','AAA' )
, ( 2,'2018-05-21','CCC' )
, ( 2,'2018-03-03','BBB' )
, ( 2,'2018-01-20','AAA' )
;
Main Query:
;WITH cte1 AS (
SELECT t1.ID, t1.theDate, t1.theName
, DATEADD(day,-30,t1.theDate) AS dMinus30
, ROW_NUMBER() OVER (PARTITION BY t1.ID ORDER BY t1.theDate DESC) AS rn
FROM t1
)
, cte2 AS (
SELECT c2.ID, c2.theDate, c2.theName
, ROW_NUMBER() OVER (PARTITION BY c2.ID ORDER BY c2.theDate) AS rn
, COUNT(*) OVER (PARTITION BY c2.ID) AS theCount
FROM cte1
INNER JOIN cte1 c2 ON cte1.ID = c2.ID
AND c2.theDate >= cte1.dMinus30
WHERE cte1.rn = 1
GROUP BY c2.ID, c2.theDate, c2.theName
)
SELECT cte1.ID, cte1.theDate AS max_date, cte1.theName AS max_name
, cte2.theDate AS previous_date, cte2.theName AS previous_name
, cte2.theCount
FROM cte1
INNER JOIN cte2 ON cte1.ID = cte2.ID
AND cte2.rn=1
WHERE cte1.rn = 1
Results:
| ID | max_date | max_name | previous_date | previous_name |
|----|------------|----------|---------------|---------------|
| 1 | 2018-05-01 | AAA | 2018-04-05 | BBB |
| 2 | 2018-05-21 | CCC | 2018-05-02 | AAA |
cte1 builds the list of max_date and max_name grouped by the ID and then using a ROW_NUMBER() window function to sort the groups by the dates to get the most recent date. cte2 joins back to this list to get all dates within the last 30 days of cte1's max date. Then it does essentially the same thing to get the last date. Then the outer query joins those two results together to get the columns needed while only selecting the most and least recent rows from each respectively.
I'm not sure how well it will scale with your data, but using the CTEs should optimize pretty well.
EDIT: For the additional requirement, I just added in another COUNT() window function to cte2.
I would do:
select id,
max(case when seqnum = 1 then date end) as max_date,
max(case when seqnum = 1 then name end) as max_name,
max(case when seqnum = 2 then date end) as prev_date,
max(case when seqnum = 2 then name end) as prev_name,
from (select e.*, row_number() over (partition by id order by date desc) as seqnum
from example e
) e
group by id;
First of all I would like to thank the friends who helped this complex and difficult query.
I have three tables
Table 1
StaffId FirstName LastName staffType
---------------------------------------
1 Adam Sorme Student
2 Lara Sandra Teacher
3 Jack Jones Student
Table 2
GateId GateName
---------------------------------------
1 frontDoor
2 superDoor
Table 3
Id transitionDate GateId StaffId
---------------------------------------
1 2018-01-1 08:00:00 1 1
2 2018-01-1 10:00:00 2 1
3 2018-01-1 20:00:00 2 1
4 2018-01-2 07:00:00 1 2
5 2018-01-2 10:00:00 1 3
6 2018-01-9 12:00:00 2 2
I want the first and last movements of students for each day. Value must be set to null if no movement is available between the specified dates
transitionDate> '2018-01-1 00:00:00 000'
and transitionDate< '2018-01-03 00:00:00 000'
OUTPUT:
Id Date MinTransitionDate MaxTransitionDate FirstGateName LastGateName StaffId StaffType
1 2018-01-01 2018-01-1 08:00:00 2018-01-1 20:00:00 frontDoor superDoor 1 Student
2 2018-01-01 null null null null 3 student
3 2018-01-02 null null null null 1 student
4 2018-01-02 2018-01-2 10:00:00 null frontDoor null 3 student
The following query is partially working.
select
q.*,
g1.GateName as first_gate_name,
g2.GateName as last_gate_name
from
(
select s.staffId, d.dte,
min(t.transitionDate) as min_Date,
max_Date= case when count(1)>1 then max(t.transitionDate) else null end,
max(case when seqnum_asc = 1 then gateId end) as first_gateid,
max(case when seqnum_desc = 1 then gateId end) as last_gateid
from (select s.* from Staff s where stafftype = 'Student') s cross join
(select distinct cast(transitionDate as date) as dte from Transitions) d left join
(select t.*,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate) as seqnum_asc,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate desc) as seqnum_desc
from Transitions t
) t
on cast(t.transitiondate as date) = d.dte and
t.staffId = s.staffId and
1 in (t.seqnum_asc, t.seqnum_desc)
group by s.staffId, d.dte
) q
left join Gates g1 on g1.gateId = q.first_gateid
left join Gates g2 on g2.gateId = q.last_gateid
see working demo
Problem : max_date in 4. row is empty. I want the value of last_gateid to be null. Can you help me?
screenshot 4. row
https://cdn.pbrd.co/images/H7vyu31.png
A quick fix would be replacing this line
max(case when seqnum_desc = 1 then gateId end) as last_gateid
with
max(case when (seqnum_desc = 1 and seqnum_asc != 1) then t.gateId end) as last_gateid
It is obvious that some one helped you write the query and you gave him more explanation of the problem , what i did is tweaking the query you provided and used the same logic to give you the desired output although i would prefer to rewrite it in a more understandable way :
select
q.*,
g1.GateName as first_gate_name,
g2.GateName as last_gate_name
from
(
select s.staffId, d.dte,
min(t.transitionDate) as min_Date,
max_Date= case when count(1)>1 then max(t.transitionDate) else null end,
max(case when seqnum_asc = 1 then gateId end) as first_gateid,
case when count(1)>1 then MAX(gateId) end as last_gateid
from (select s.* from Staff s where stafftype = 'Student') s cross join
(select distinct cast(transitionDate as date) as dte from Transitions) d left join
(select t.*,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate) as seqnum_asc,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate desc) as seqnum_desc
from Transitions t
) t
on cast(t.transitiondate as date) = d.dte and
t.staffId = s.staffId and
1 in (t.seqnum_asc, t.seqnum_desc)
group by s.staffId, d.dte
) q
left join Gates g1 on g1.gateId = q.first_gateid
left join Gates g2 on g2.gateId = q.last_gateid
IF OBJECT_ID('dbo.Staff') IS NOT NULL DROP TABLE Staff
create table Staff (StaffId int, FirstName varchar(20), LastName
varchar(20), staffType varchar(20))
insert into Staff values
(1, 'Adam', 'Sorme', 'Student'),
(2, 'Lara', 'Sandra', 'Teacher'),
(3, 'Jack', 'Jones', 'Student')
IF OBJECT_ID('dbo.Gates') IS NOT NULL DROP TABLE Gates
create table Gates (GateId int, GateName varchar(20))
insert into Gates values
(1, 'frontDoor'),
(2, 'backDoor')
IF OBJECT_ID('dbo.Transitions') IS NOT NULL DROP TABLE Transitions
create table Transitions (Id int, transitionDate datetime, GateId int,
StaffId int)
insert into Transitions values
(1, '2018-01-1 08:00:00', 1, 1),
(2, '2018-01-1 10:00:00', 2, 1),
(3, '2018-01-1 20:00:00', 2, 1),
(4, '2018-01-2 07:00:00', 1, 2),
(5, '2018-01-2 10:00:00', 2, 3),
(6, '2018-01-9 12:00:00', 2, 2)
--select * from Transitions
DECLARE #Datefrom DATETIME = '2018-01-01'
DECLARE #DateTo DATETIME = '2018-01-03'
----1. If the transition table itself is not reliable meaning there
--could be imports and the maxID could be for a lesser datetime
IF OBJECT_ID('tempdb..#Transitions') IS NOT NULL DROP TABLE
#Transitions
SELECT *, [GateSortID] = RANK() OVER (PARTITION BY StaffID ORDER BY
StaffID,GateID,TransitionDate)
INTO #Transitions
FROM Transitions
----2.Based on above temp table get first and last dates
IF OBJECT_ID('tempdb..#FirstandLastDates') IS NOT NULL DROP TABLE
#FirstandLastDates
SELECT T.StaffId,
[FirstDate] = FirstDate.transitionDate,
[LastDate] = LastDate.transitionDate
INTO #FirstandLastDates
FROM #Transitions T
JOIN
(
SELECT TT.StaffId,
[MinGateSortID] = MIN(GateSortID),
TT.GateID,
G.transitionDate
FROM #Transitions TT
JOIN Transitions G
ON TT.StaffId = G.StaffId
AND TT.Id = G.ID
AND TT.GateId = G.GateId
WHERE GateSortID = 1-- MIN VALUE WILL ALWAYS BE 1
GROUP BY TT.StaffID,TT.GateID,G.transitionDate
)FirstDate --lol!!
ON T.StaffID= FirstDate.StaffID
LEFT JOIN
(
SELECT TT.StaffId, TT.GateId, G.TransitionDate
FROM #Transitions TT
JOIN #Transitions G
ON TT.StaffId = G.StaffId
AND TT.Id = G.ID
AND TT.GateId = G.GateId
JOIN
(
SELECT StaffID,
[MaxGateSortID] = MAX(GateSortID)
FROM #Transitions TT
WHERE GateSortID <> 1 -- SO THAT IF THE PERSON HAS NOT --CLOCKED OUT YET ONLY CLOCKED IN DO NOT CONSIDER THAT THE
--GATES ARE SAME
GROUP BY StaffID
) LastGate1
ON TT.StaffId = LastGate1.StaffId
AND TT.GateSortID = LastGate1.MaxGateSortID
)LastDate
ON T.StaffId = LastDate.StaffId
GROUP BY T.StaffId,FirstDate.transitionDate,LastDate.transitionDate
----3.Based on above temp table get first and last transition gates
IF OBJECT_ID('tempdb..#FirstandLastGates') IS NOT NULL DROP TABLE
#FirstandLastGates
SELECT T.StaffId,
[FirstGate] = FirstGate.GateName,
[LastGate] = LastGate.GateName
INTO #FirstandLastGates
FROM #Transitions T
JOIN
(
SELECT StaffID,
[MinGateSortID] = MIN(GateSortID),
TT.GateID,
GateName
FROM #Transitions TT
JOIN Gates G
ON TT.GateId = G.GateId
WHERE GateSortID = 1-- MIN VALUE WILL ALWAYS BE 1
--AND TT.transitionDate BETWEEN #Datefrom AND #DateTo
GROUP BY StaffID,TT.GateID,GateName
)FirstGate
ON T.StaffID= FirstGate.StaffID
LEFT JOIN
(
SELECT TT.StaffId, TT.GateId, G.GateName
FROM #Transitions TT
JOIN Gates G
ON TT.GateId = G.GateId
JOIN
(
SELECT StaffID,
[MaxGateSortID] = MAX(GateSortID)
FROM #Transitions TT
WHERE GateSortID <> 1 -- SO THAT IF THE PERSON HAS NOT CLOCKED
-- OUT YET ONLY CLOCKED IN DO NOT CONSIDER THAT THE GATES ARE --SAME
--AND TT.transitionDate BETWEEN #Datefrom AND #DateTo
GROUP BY StaffID
) LastGate1
ON TT.StaffId = LastGate1.StaffId
AND TT.GateSortID = LastGate1.MaxGateSortID
)LastGate
ON T.StaffId = LastGate.StaffId
GROUP BY T.StaffId,FirstGate.GateName,LastGate.GateName
--MAIN OUTPUT
SELECT DISTINCT --t.Id,
--t.transitionDate,
[MinTransitionDate] = D.FirstDate,
[MaxTransitionDate] = D.LastDate,
[FirstGateName] = G.FirstGate,
[LastGateName] = G.LastGate,
T.[StaffId],
S.StaffType
from Staff S
JOIN #Transitions T
ON S.StaffId = T.StaffId
JOIN #FirstandLastDates D
ON T.StaffId = D.StaffId
JOIN #FirstandLastGates G
ON T.StaffId = G.StaffId
GROUP BY t.Id, D.FirstDate, D.LastDate, G.FirstGate,G.LastGate,T.[StaffId],
S.StaffType
I have following Data in myRecords Table
Id Date Name Cash
1 11/25/2016 4:23.123 Ramesh 10000
2 11/25/2016 4:23.173 Suresh 15000
1 11/27/2016 5:23.320 Ramesh 30000
2 11/27/2016 5:23.670 Suresh 40000
and I want to create view So I can get data in following Format
Id1 Date1 Name1 Cash1 Id2 Date2 Name2 Cash2
1 11/25/2016 4:23.123 Ramesh 10000 2 11/25/2016 4:23.173 Suresh 15000
1 11/27/2016 5:23.320 Ramesh 30000 2 11/27/2016 5:23.670 Suresh 40000
How can I do it.
If you are doing date and there will always only be 2 records per day you could convert to drop off the time and do a self join:
DECLARE #myRecords AS TABLE (Id INT, DATE DATETIME, Name VARCHAR(20), CASH INT)
INSERT INTO #myRecords VALUES (1,'11/25/2016 4:23','Ramesh',10000),(2,'11/25/2016 4:23','Suresh',15000)
,(1,'11/27/2016 5:23','Ramesh',30000),(2,'11/27/2016 4:23','Suresh',40000)
SELECT
m1.Id as Id1
,m1.Date as Date1
,m1.Name as Name1
,m1.Cash as Cash1
,m2.Id as Id2
,m2.Date as Date2
,m2.Name as Name2
,m2.Cash as Cash2
FROM
#myRecords m1
LEFT JOIN #myRecords m2
ON CAST(m1.DATE AS DATE) = CAST(m2.DATE AS DATE)
AND m1.Id <> m2.Id
WHERE
m1.Id = 1
Then you can also introduce ROW_NUMBER() to figure out whatever order you want then take all of the ODD RowNumbers and SELF JOIN to the Even RowNumbers:
;WITH cte AS (
SELECT
*
,RowNum = ROW_NUMBER() OVER (ORDER BY Date)
FROM
#myRecords
)
SELECT *
FROM
cte c1
LEFT JOIN cte c2
ON c1.RowNum + 1 = c2.RowNum
WHERE
c1.RowNum % 2 <> 0
As long as your Id joining logic is unclear, this will help In this case but you will need to add Id Filter or additional Identity column and row_number() in future I guess.
SELECT
T.*, TT.*
FROM
[Table] AS T
INNER JOIN
[Table] AS TT
ON T.Date = TT.Date
You can use Cross Apply for the required result set.
SELECT [ID],
[DATE],
[NAME],
[CASH],
B.*
FROM #TABLE1 A
CROSS APPLY (SELECT ID AS ID2,
[DATE] AS DATE2,
[NAME] AS NAME2,
[CASH] AS CASH2
FROM #TABLE1 B
WHERE A.ID < B.ID
AND CONVERT(DATE, A.DATE) = CONVERT(DATE, B.DATE))B
This will also return the same result:
select a.id, a.date, a.name, a.cash, b.id as id2, b.date as date2,
b.name as name2, b.cash as cash2
from myTable a
inner join myTable b on a.id+1 = b.id
and cast(a.date as date) <> cast(b.date as date)
I have a table A, with 4 columns:
first_name, invoice, value, date.
And a table B (first_name, max_invoice_name, max_invoice_value, last_date)
I want to create a procedure in order to move data from A, to B, but:
first_name should be one time in B,
max_invoice_name is the name of the max invoice value
max_invoice_value is the max value
last_date is the latest date from invoices from the same first_name.
For example:
TABLE A:
Smith | Invoice1 | 100 | 23.06.2016
John | Invoice13 | 23 | 18.07.2016
Smith | Invoice3 | 200 | 01.01.2015
Table B should be:
Smith |Invoice3 | 200 | 23.06.2016
John |Invoice13| 23 | 18.07.2016
Something like this should work:
select *, (select max(date) from #Table1 T1 where T1.first_name = X.first_name)
from (
select
*,
row_number() over (partition by first_name order by invoice_Value desc) as RN
from
#Table1
) X
where RN = 1
Row number takes care of selecting the row with biggest value, and the max get's the date. You'll need to list the columns in correct place instead of *
You will need to create 2 scalar functions getMaxNameForMaxValue AND getLastDateByFirstName to get the values you want.
INSERT INTO TableB (first_name, max_invoice_name, max_invoice_value, last_date) (SELECT DISTINCT first_name, getMaxNameForMaxValue(MAX(max_value)) AS 'max_invoice_name', MAX(max_invoice_value) AS 'max_invoice_value', getLastDateByFirstName(first_name) AS 'lastDate' FROM Table A)
You can use something like this:
--INSERT INTO TableB
SELECT first_name,
invoice_name,
invoice_value,
last_date
FROM (
SELECT a.first_name,
a.invoice_name,
a.invoice_value,
COALESCE(p.last_date,a.last_date) as last_date,
ROW_NUMBER() OVER (PARTITION BY a.first_name ORDER BY a.last_date) as rn
FROM TableA a
OUTER APPLY (SELECT TOP 1 * FROM TableA WHERE first_name = a.first_name and last_date > a.last_date) as p
) as res
WHERE rn = 1
As output:
first_name invoice_name invoice_value last_date
John Invoice13 23 2016-07-18
Smith Invoice3 200 2016-06-23
Try this
Insert into TableB(first_name, max_invoice_name, max_invoice_value, last_date)
select t1.first_name,t1.invoice,t1,value,t2.date from TableA as t1 inner join
(
select first_name, max(replace(invoice,'invoice','')) as invoice, max(date) as date
from TableA group by first_name
) as t2 on t1.first_name=t2.first_name and t1.invoice=t2.invoice