Join returns too much info - sql

I have 2 tables
Table1
+---------+--------+-------+----+
| CALDATE | GROOMS | ROOMS | fn |
+---------+--------+-------+----+
| 1/5/18 | 15 | 17 | A12|
| 1/5/18 | 0 | 0 | A12|
| 1/6/18 | 0 | 0 | B34|
| 1/6/18 | 75 | 77 | B34|
| 1/7/18 | 123 | 125 | C56|
| 1/7/18 | 0 | 0 | C56|
+---------+--------+-------+----+
-
Table2
+----------+--------+----+
| ROOMDATE | pickup | FN |
+----------+--------+----+
| 1/5/18 | 0 | A12|
| 1/5/18 | 2 | A12|
| 1/5/18 | 1 | A12|
| 1/5/18 | 7 | A12|
| 1/6/18 | 2 | B34|
| 1/6/18 | 1 | B34|
| 1/6/18 | 13 | B34|
| 1/7/18 | 3 | C56|
| 1/7/18 | 0 | C56|
| 1/7/18 | 12 | C56|
+----------+--------+----+
Querying each I use
Select caldate as date, sum(grooms) as g, sum (rooms) as r
from Table1
and
Select roomdate as date, sum(pickup) as p
from Table2
These each give me the info I'm expecting, however when I try and join them things get wonky. I was hoping for something like
Select caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from Table1
inner join table2 on table1.fn = table2.fn
But that returns way too high of each.
How do I join these queries so that I get my expected output of
+--------+-----+-----+----+----+
| Date | g | r | p | fn |
+--------+-----+-----+----+----+
| 1/5/18 | 15 | 17 | 10 | A12|
| 1/6/18 | 75 | 77 | 16 | B34|
| 1/7/18 | 123 | 125 | 15 | C56|
+--------+-----+-----+----+----+

Each row in your first table will match with each available row in the other table based on your join predicate. Take fn = A12 for example: since you have 2 rows in table1 and 4 rows in table2, you will end up with (4x2) 8 rows in your result set. That will cause your sums to be higher than they should be.
One way to fix this is to use derived tables to get your sums, then join them together:
SELECT t1.date, g, r, p, t1.fn
FROM (SELECT fn, caldate as date, sum(grooms) as g, sum (rooms) as r
FROM Table1
GROUP BY fn, caldate) t1
JOIN (SELECT fn, roomdate as date, sum(pickup) as p
FROM Table2
GROUP BY fn, roomdate) t2 on t1.fn = t2.fn
This makes sure there is one row returned from each table before the join.

You should be Grouping by 'caldate'. This way you will only be getting the sums per date.
Select caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from Table1
inner join table2 on table1.fn = table2.fn
group by caldate

The reason that you get more rows than expected is because of join condition. if you want to find grooms and rooms for each fn on each day you have add date to join condition as well:
Select table1.caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from table1
inner join table2 on table1.fn = table2.fn
and table1.caldate = table2.roomdate

Here's an answer that allows you to group by FN and date:
select format(caldate, 'M/d/yyyy') as date, sum(grooms) as g, sum(rooms) as r, t2.p, t1.fn
from Table1 t1
inner join (
select fn, roomdate, sum(pickup) as p from Table2 group by fn, roomdate
)t2 on t1.fn = t2.fn and t1.caldate = t2.roomdate
group by t1.caldate, t1.fn, t2.p
I created some sample data with DML statements so you can test grouping by different combinations of FN and date, and the output:
declare #t1 table (caldate datetime, grooms int, rooms int, fn varchar(3))
declare #t2 table (roomdate datetime, pickup int, fn varchar(3))
insert into #t1 select '1/5/18', 15, 17,'A12'
insert into #t1 select '1/5/18', 0, 0,'A12'
insert into #t1 select '1/6/18', 0, 0,'B34'
insert into #t1 select '1/6/18', 75, 77,'B34'
insert into #t1 select '1/7/18',123,125,'C56'
insert into #t1 select '1/8/18',100,200,'C56' -- changed to 1/8/18, changed vals
insert into #t2 select '1/5/18', 0 ,'A12'
insert into #t2 select '1/5/18', 2 ,'A12'
insert into #t2 select '1/5/18', 1 ,'A12'
insert into #t2 select '1/5/18', 7 ,'A12'
insert into #t2 select '1/6/18', 2 ,'B34'
insert into #t2 select '1/6/18', 1 ,'B34'
insert into #t2 select '1/6/18',13 ,'B34'
insert into #t2 select '1/7/18', 3 ,'C56'
insert into #t2 select '1/7/18', 0 ,'C56'
insert into #t2 select '1/8/18',12 ,'C56' -- changed to 1/8/18
select format(caldate, 'M/d/yyyy') as date, sum(grooms) as g, sum(rooms) as r, t2.p, t1.fn
from #t1 t1
inner join (
select fn, roomdate, sum(pickup) as p from #t2 group by fn, roomdate
)t2 on t1.fn = t2.fn and t1.caldate = t2.roomdate
group by t1.caldate, t1.fn, t2.p
Output:
date g r p fn
1/5/2018 15 17 10 A12
1/6/2018 75 77 16 B34
1/7/2018 123 125 3 C56
1/8/2018 100 200 12 C56
If you don't also join by date, then adding different combinations of caldate/fn give you duplicates. You must mean to join by the date as well, right?

Related

case when statement in oracle across tables

Hi apologies for formatting but im stumped and frustrated and i just need some help.
I've got two tables. I have made a good faith attempt to follow community standards but just in case it doesnt work, Table A has 3 columns 'ID', to identify a sales rep, 'Start' to indicate what company term they started, and 'Sales' to indicate their sales in that first term. Table B is just an expansion of Table A where it lists all terms (i marked it as quarters) a sales person was there and their sales.
Table A
+----+---------+-------+
| ID | Quarter | Sales |
+----+---------+-------+
| 1 | 141 | 30 |
| 2 | 151 | 50 |
| 3 | 151 | 80 |
+----+---------+-------+
Table B
+----+---------+-------+
| ID | Quarter | Sales |
+----+---------+-------+
| 1 | 141 | 30 |
| 1 | 142 | 25 |
| 1 | 143 | 45 |
| 2 | 151 | 50 |
| 2 | 152 | 60 |
| 2 | 153 | 75 |
| 3 | 151 | 80 |
| 3 | 152 | 50 |
| 3 | 153 | 70 |
+----+---------+-------+
My desired output is a table with ID, start term, sales from that term, second term, sales from that term, etc. for the first 6 terms an employee is there
my code is this
select a.id, start, a.sales,
case when a.start+1 = b.quarter then sales end as secondquartersales,
case when a.start+2 = b.quarter then sales end as thridquartersales,.....
from tablea a
left join tableb b
on a.id = b.id;
it gives nulls for all case when statements. please help
maybe try GROUP BY
create table a ( id number, strt number, sales number);
create table b (id number, quarter number , sales number);
insert into a values (1,141,30);
insert into a values (2,151,50);
insert into a values (3,151,80);
insert into b values ( 1,141,30);
insert into b values ( 1,142,25);
insert into b values ( 1,143,45);
insert into b values ( 2,151,50);
insert into b values ( 2,152,60);
insert into b values ( 2,153,75);
insert into b values ( 3,151,80);
insert into b values ( 3,152,50);
insert into b values ( 3,153,70);
select a.id, a.strt, a.sales,
max(case when a.strt+1 = b.quarter then b.sales end ) as secondquartersales,
max(case when a.strt+2 = b.quarter then b.sales end ) as thridquartersales
from a, b
where a.id = b.id
group by a.id, a.strt, a.sales;
OR PIVOT
select * from (
select a.id,
case when a.strt+1 = b.quarter then 'Q2'
when a.strt+2 = b.quarter then 'Q3'
when a.strt+3 = b.quarter then 'Q4'
when a.strt = b.quarter then 'Q1'end q,
b.sales sales
from a, b
where a.id = b.id)
pivot ( max(nvl(sales,0)) for Q in ('Q1', 'Q2', 'Q3', 'Q4'));
This is valid ANSI 92 SQL, as it is an inner join. The whole ANSI style version is just syntax candy.

I want to make this happen in SQL Server

driverphone| drivername|guarantor1_phone|guarantor2_phone
---------------------------------------------------------
0801 |Mr A |0803 |0802
0802 |Mr B |0804 |0801
0803 |Mr C |0805 |0801
0804 |Mr D |0802 |0805
0805 |Mr E |0801 |0803
I want to get this result set in SQL Server
driverphone| drivername|Total Guaranteed
----------------------------------------
0801 |Mr A | 3
0802 |Mr B | 2
0803 |Mr C | 2
0804 |Mr D | 1
0805 |Mr E | 2
That is to select the total number guaranteed by each driver.
driver->guarantor relationship is based on phone numbers.
Do make LEFT JOIN with both guarantor columns and make a Distinct count of it.
Schema:
CREATE TABLE #TAB (
driverphone VARCHAR(10)
,drivername VARCHAR(10)
,guarantor1_phone VARCHAR(10)
,guarantor2_phone VARCHAR(10)
)
INSERT INTO #TAB
SELECT '0801',' Mr A', '0803', '0802'
UNION ALL
SELECT '0802',' Mr B', '0804', '0801'
UNION ALL
SELECT '0803',' Mr C', '0805', '0801'
UNION ALL
SELECT '0804',' Mr D', '0802', '0805'
UNION ALL
SELECT '0805',' Mr E', '0801', '0803'
Now do select like below
SELECT T.driverphone
,T.drivername
,COUNT(DISTINCT T2.driverphone) + COUNT(DISTINCT T3.driverphone)
FROM #TAB T
LEFT JOIN #TAB T2 ON T.driverphone = T2.guarantor1_phone
LEFT JOIN #TAB T3 ON T.driverphone = T3.guarantor2_phone
GROUP BY T.driverphone
,T.drivername
Result will be
+-------------+------------+------------------+
| driverphone | drivername | (No column name) |
+-------------+------------+------------------+
| 0801 | Mr A | 3 |
| 0802 | Mr B | 2 |
| 0803 | Mr C | 2 |
| 0804 | Mr D | 1 |
| 0805 | Mr E | 2 |
+-------------+------------+------------------+
I think the simplest method is outer apply:
select t.driverphone, t.drivername, g.totalguaranteed
from t outer apply
(select count(*) as totalguaranteed
from (values (guarantor1_phone), (guarantor2_phone)) v(guarantor)
where v.guarantor = t.driverphone
) g;

How to Inner Join Three Tables and Return Maximum of Value in Third

I have three tables as follows:
First table:
ordenes
id_orden | date | total | id_usuario
1 |15-may|50 | 1
2 |20-may|60 | 2
Second table:
usuario
id_usuario | name | phone
1 | abc | 999
2 | def | 888
Third table:
estado
id_orden | edo
1 | c
1 | b
1 | a
2 | b
2 | a
And this is the desired result:
Results:
id_orden | date | total | id_usuario | name | phone | maxedo
1 |15-may|50 | 1 | abc | 999 | c
2 |20-may|60 | 2 | def | 888 | b
maxedo needs to be the maximum record from the edo in the third table after aggregating based on order.
How do I do this?
The below code sample gives you the result.
CREATE TABLE #ordenes(id_orden int, datevalue date, total int, id_usuario int)
INSERT INTO #ordenes
VALUES
(1,'20160515',50,1),
(2,'20160520',60,2)
CREATE TABLE #usuario(id_usuario int, name varchar(10), phone int)
INSERT INTO #usuario
VALUES
(1,'abc',999),
(2,'def',888)
CREATE TABLE #estado(id_orden int, edo char(1))
INSERT INTO #estado
VALUES
(1,'c'),
(1,'b'),
(1,'a'),
(2,'b'),
(2,'a')
SELECT id_orden,datevalue,total,id_usuario,name,phone,edo as maxedo
FROM
(SELECT o.id_orden,o.datevalue,o.total,o.id_usuario,u.name,u.phone,e.edo,ROW_NUMBER() OVER(PARTITION BY o.id_orden ORDER BY e.edo DESC) as rnk
FROM #ordenes o
JOIN #usuario u
on o.id_usuario = u.id_usuario
join #estado e
on o.id_orden = e.id_orden) as t
where rnk = 1
The following should do the job (assuming edo is actually a numeric amount). I've included aliases using the AS command so you even get the column titles you want.
SELECT
oe.id_orden AS id_orden,
oe.date AS date,
oe.total AS total,
u.id_usario AS id_usuario,
u.name AS name,
u.phone AS phone,
oe.maxedo AS maxedo
FROM usuario u
INNER JOIN
(SELECT
o.id_orden AS id_orden,
o.date AS date,
o.total AS total,
o.id_usuario AS id_usuario,
e.maxestedo AS maxestedo
FROM ordenes o
INNER JOIN
(SELECT
id_orden AS id_orden,
MAX(edo) AS maxedo
FROM estado
GROUP BY id_orden) e
ON e.id_orden=o.id_orden) oe
ON u.id_usuario=oe.id_usuario
In order of processing (which is not how SQL works but is useful way of breaking it down into steps) it goes:
Create table of Maximum edos (NB: MAX also works on alphabetical order);
Links this to ordenes using the id_ordene;
Joins this to usuario data using the id_usuario; and
Publishes this as a table in the required format.
The problem can be split into the following three steps:
Step 1: Calculate maximum edo for each id_orden in the table estado:
Select id_orden, max(edo) maxedo
From estado
Group By id_orden;
Result:
| id_orden | edo |
| 1 | c |
| 2 | b |
Step 2: Join the two tables ordenes and usuario on the key "id_usuario":
Select o.id_orden, o.date, o.total, o.id_usuario, u.name, u.phone
From ordenes o Join usuario u
On o.id_usuario = u.id_usuario;
Result:
id_orden | date | total | id_usuario | name | phone
1 |15-may|50 | 1 | abc | 999
2 |20-may|60 | 2 | def | 888
Step 3: Join the table form the step1 and step2 on the key id_orden:
Select a.id_orden, a.date, a.total, a.id_usuario, a.name, a.phone, b.maxestado
From (Select o.id_orden, o.date, o.total, o.id_usuario, u.name, u.phone
From ordenes o Inner Join usuario u
On o.id_usuario = u.id_usuario ) a
Join (Select id_orden, max(edo) maxestado
From estado
Group By id_orden) b
On a.id_orden = b.id_orden;
Result:
id_orden | date | total | id_usuario | name | phone | maxedo
1 |15-may|50 | 1 | abc | 999 | c
2 |20-may|60 | 2 | def | 888 | b
SQLFiddle example: http://sqlfiddle.com/#!5/a79a1/2
:)
I think you need to get the max(edo) from the third table and group by id_orden, yes? try this.
select temp.*, max(edo) as maxedo
from estado
inner join(
select ordenes.*,usuario.name,usuario.phone
from ordenes,usuario
where ordenes.id_usuario = usuario.id_usuario
) as temp
on temp.id_orden = estado.id_orden
group by estado.id_orden

How to query the previous record that is in another table?

I have a view that shows something like the following:
View VW
| ID | DT | VAL|
|----|------------|----|
| 1 | 2016-09-01 | 7 |
| 2 | 2016-08-01 | 5 |
| 3 | 2016-07-01 | 8 |
I have a table with historical date that has something like:
Table HIST
| ID | DT | VAL|
|----|------------|----|
| 1 | 2016-06-27 | 4 |
| 1 | 2016-06-29 | 3 |
| 1 | 2016-07-15 | 0 |
| 1 | 2016-09-12 | 8 |
| 2 | 2016-05-05 | 3 |
What I need is to add another column to my view with a boolean that means "the immediately previous record exist in history and has a related value greater than zero".
The expected output is the following:
| ID | DT | VAL| FLAG |
|----|------------|----|------|
| 1 | 2016-09-01 | 7 | false| -- previous is '2016-07-15' and value is zero. '2016-09-12' in hist is greater than '2016-09-01' in view, so it is not the previous
| 2 | 2016-08-01 | 5 | true | -- previous is '2016-05-05' and value is 3
| 3 | 2016-07-01 | 8 | false| -- there is no previous value in HIST table
What have I tried
I've used the query below. It works for small loads, but fails in performance in production because my view is extremely complex and the historical table is too large. Is it possible to query this without using the view multiple times? (if so, the performance should be better and I won't see anymore timeouts)
You can test here http://rextester.com/l/sql_server_online_compiler
create table vw (id int, dt date, val int);
insert into vw values (1, '2016-09-01', 7), (2, '2016-08-01', 5), (3, '2016-07-01', 8);
create table hist (id int, dt date, val int);
insert into hist values (1, '2016-06-27', 4), (1, '2016-06-29', 3), (1, '2016-07-15', 0), (1, '2016-09-12', 8), (2, '2016-05-05', 3);
select vw.id, vw.dt, vw.val, (case when hist_with_flag.flag = 'true' then 'true' else 'false' end)
from vw
left join
(
select hist.id, (case when hist.val > 0 then 'true' else 'false' end) flag
from
(
select hist.id, max(hist.dt) as dt
from hist
inner join vw on vw.id = hist.id
where hist.dt < vw.dt
group by hist.id
) hist_with_max_dt
inner join hist
on hist.id = hist_with_max_dt.id and hist.dt = hist_with_max_dt.dt
) hist_with_flag
on vw.id = hist_with_flag.id
You can use OUTER APPLY in order to get the immediately previous record:
SELECT v.ID, v.DT, v.VAL,
IIF(t.VAL IS NULL OR t.VAL = 0, 'false', 'true') AS FLAG
FROM Vw AS v
OUTER APPLY (
SELECT TOP 1 VAL, DT
FROM Hist AS h
WHERE v.ID = h.ID AND v.DT > h.DT
ORDER BY h.DT DESC) AS t
Can you please try with this query, it returns same result as your query. It should work good performance wise
SELECT vw.id, MAX(vw.dt) dt,
MAX(vw.val) val,
case when MAX(h.val) > 0 then 'true' else 'false' END flag
FROM vw
OUTER APPLY(SELECT MAX(dt) dt FROM hist WHERE vw.id = hist.id
AND dt<vw.dt GROUP BY hist.id) t
LEFT JOIN hist h ON vw.id = h.id AND h.dt = t.dt
GROUP BY vw.id
You can avoid multiple JOIN using a simple CTE with 'ROW_NUMBER'.
;with cte_1
as
(select vw.id, vw.dt, vw.val,hist.val HistVal,hist.dt HistDt,ROW_NUMBER()OVER (PARTITION BY vw.id,vw.dt ORDER BY vw.id,vw.dt,hist.dt desc) RNO
FROM vw
left join hist
on hist.id = vw.id and hist.dt < vw.dt
)
SELECT Id,Dt,Val,case when ISNULL(HistVal,0)=0 THEN 'FALSE' ELSE 'TRUE' END as FLAG
FROM cte_1 WHERE RNO=1

A very basic SQL issue I'm stuck with [duplicate]

I have a table of player performance:
CREATE TABLE TopTen (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
home INT UNSIGNED NOT NULL,
`datetime`DATETIME NOT NULL,
player VARCHAR(6) NOT NULL,
resource INT NOT NULL
);
What query will return the rows for each distinct home holding its maximum value of datetime? In other words, how can I filter by the maximum datetime (grouped by home) and still include other non-grouped, non-aggregate columns (such as player) in the result?
For this sample data:
INSERT INTO TopTen
(id, home, `datetime`, player, resource)
VALUES
(1, 10, '04/03/2009', 'john', 399),
(2, 11, '04/03/2009', 'juliet', 244),
(5, 12, '04/03/2009', 'borat', 555),
(3, 10, '03/03/2009', 'john', 300),
(4, 11, '03/03/2009', 'juliet', 200),
(6, 12, '03/03/2009', 'borat', 500),
(7, 13, '24/12/2008', 'borat', 600),
(8, 13, '01/01/2009', 'borat', 700)
;
the result should be:
id
home
datetime
player
resource
1
10
04/03/2009
john
399
2
11
04/03/2009
juliet
244
5
12
04/03/2009
borat
555
8
13
01/01/2009
borat
700
I tried a subquery getting the maximum datetime for each home:
-- 1 ..by the MySQL manual:
SELECT DISTINCT
home,
id,
datetime AS dt,
player,
resource
FROM TopTen t1
WHERE `datetime` = (SELECT
MAX(t2.datetime)
FROM TopTen t2
GROUP BY home)
GROUP BY `datetime`
ORDER BY `datetime` DESC
The result-set has 130 rows although database holds 187, indicating the result includes some duplicates of home.
Then I tried joining to a subquery that gets the maximum datetime for each row id:
-- 2 ..join
SELECT
s1.id,
s1.home,
s1.datetime,
s1.player,
s1.resource
FROM TopTen s1
JOIN (SELECT
id,
MAX(`datetime`) AS dt
FROM TopTen
GROUP BY id) AS s2
ON s1.id = s2.id
ORDER BY `datetime`
Nope. Gives all the records.
I tried various exotic queries, each with various results, but nothing that got me any closer to solving this problem.
You are so close! All you need to do is select BOTH the home and its max date time, then join back to the topten table on BOTH fields:
SELECT tt.*
FROM topten tt
INNER JOIN
(SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt
ON tt.home = groupedtt.home
AND tt.datetime = groupedtt.MaxDateTime
The fastest MySQL solution, without inner queries and without GROUP BY:
SELECT m.* -- get the row that contains the max value
FROM topten m -- "m" from "max"
LEFT JOIN topten b -- "b" from "bigger"
ON m.home = b.home -- match "max" row with "bigger" row by `home`
AND m.datetime < b.datetime -- want "bigger" than "max"
WHERE b.datetime IS NULL -- keep only if there is no bigger than max
Explanation:
Join the table with itself using the home column. The use of LEFT JOIN ensures all the rows from table m appear in the result set. Those that don't have a match in table b will have NULLs for the columns of b.
The other condition on the JOIN asks to match only the rows from b that have bigger value on the datetime column than the row from m.
Using the data posted in the question, the LEFT JOIN will produce this pairs:
+------------------------------------------+--------------------------------+
| the row from `m` | the matching row from `b` |
|------------------------------------------|--------------------------------|
| id home datetime player resource | id home datetime ... |
|----|-----|------------|--------|---------|------|------|------------|-----|
| 1 | 10 | 04/03/2009 | john | 399 | NULL | NULL | NULL | ... | *
| 2 | 11 | 04/03/2009 | juliet | 244 | NULL | NULL | NULL | ... | *
| 5 | 12 | 04/03/2009 | borat | 555 | NULL | NULL | NULL | ... | *
| 3 | 10 | 03/03/2009 | john | 300 | 1 | 10 | 04/03/2009 | ... |
| 4 | 11 | 03/03/2009 | juliet | 200 | 2 | 11 | 04/03/2009 | ... |
| 6 | 12 | 03/03/2009 | borat | 500 | 5 | 12 | 04/03/2009 | ... |
| 7 | 13 | 24/12/2008 | borat | 600 | 8 | 13 | 01/01/2009 | ... |
| 8 | 13 | 01/01/2009 | borat | 700 | NULL | NULL | NULL | ... | *
+------------------------------------------+--------------------------------+
Finally, the WHERE clause keeps only the pairs that have NULLs in the columns of b (they are marked with * in the table above); this means, due to the second condition from the JOIN clause, the row selected from m has the biggest value in column datetime.
Read the SQL Antipatterns: Avoiding the Pitfalls of Database Programming book for other SQL tips.
Here goes T-SQL version:
-- Test data
DECLARE #TestTable TABLE (id INT, home INT, date DATETIME,
player VARCHAR(20), resource INT)
INSERT INTO #TestTable
SELECT 1, 10, '2009-03-04', 'john', 399 UNION
SELECT 2, 11, '2009-03-04', 'juliet', 244 UNION
SELECT 5, 12, '2009-03-04', 'borat', 555 UNION
SELECT 3, 10, '2009-03-03', 'john', 300 UNION
SELECT 4, 11, '2009-03-03', 'juliet', 200 UNION
SELECT 6, 12, '2009-03-03', 'borat', 500 UNION
SELECT 7, 13, '2008-12-24', 'borat', 600 UNION
SELECT 8, 13, '2009-01-01', 'borat', 700
-- Answer
SELECT id, home, date, player, resource
FROM (SELECT id, home, date, player, resource,
RANK() OVER (PARTITION BY home ORDER BY date DESC) N
FROM #TestTable
)M WHERE N = 1
-- and if you really want only home with max date
SELECT T.id, T.home, T.date, T.player, T.resource
FROM #TestTable T
INNER JOIN
( SELECT TI.id, TI.home, TI.date,
RANK() OVER (PARTITION BY TI.home ORDER BY TI.date) N
FROM #TestTable TI
WHERE TI.date IN (SELECT MAX(TM.date) FROM #TestTable TM)
)TJ ON TJ.N = 1 AND T.id = TJ.id
EDIT
Unfortunately, there are no RANK() OVER function in MySQL.
But it can be emulated, see Emulating Analytic (AKA Ranking) Functions with MySQL.
So this is MySQL version:
SELECT id, home, date, player, resource
FROM TestTable AS t1
WHERE
(SELECT COUNT(*)
FROM TestTable AS t2
WHERE t2.home = t1.home AND t2.date > t1.date
) = 0
This will work even if you have two or more rows for each home with equal DATETIME's:
SELECT id, home, datetime, player, resource
FROM (
SELECT (
SELECT id
FROM topten ti
WHERE ti.home = t1.home
ORDER BY
ti.datetime DESC
LIMIT 1
) lid
FROM (
SELECT DISTINCT home
FROM topten
) t1
) ro, topten t2
WHERE t2.id = ro.lid
I think this will give you the desired result:
SELECT home, MAX(datetime)
FROM my_table
GROUP BY home
BUT if you need other columns as well, just make a join with the original table (check Michael La Voie answer)
Best regards.
Since people seem to keep running into this thread (comment date ranges from 1.5 year) isn't this much simpler:
SELECT * FROM (SELECT * FROM topten ORDER BY datetime DESC) tmp GROUP BY home
No aggregation functions needed...
Cheers.
You can also try this one and for large tables query performance will be better. It works when there no more than two records for each home and their dates are different. Better general MySQL query is one from Michael La Voie above.
SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
FROM t_scores_1 t1
INNER JOIN t_scores_1 t2
ON t1.home = t2.home
WHERE t1.date > t2.date
Or in case of Postgres or those dbs that provide analytic functions try
SELECT t.* FROM
(SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
, row_number() over (partition by t1.home order by t1.date desc) rw
FROM topten t1
INNER JOIN topten t2
ON t1.home = t2.home
WHERE t1.date > t2.date
) t
WHERE t.rw = 1
SELECT tt.*
FROM TestTable tt
INNER JOIN
(
SELECT coord, MAX(datetime) AS MaxDateTime
FROM rapsa
GROUP BY
krd
) groupedtt
ON tt.coord = groupedtt.coord
AND tt.datetime = groupedtt.MaxDateTime
This works on Oracle:
with table_max as(
select id
, home
, datetime
, player
, resource
, max(home) over (partition by home) maxhome
from table
)
select id
, home
, datetime
, player
, resource
from table_max
where home = maxhome
Try this for SQL Server:
WITH cte AS (
SELECT home, MAX(year) AS year FROM Table1 GROUP BY home
)
SELECT * FROM Table1 a INNER JOIN cte ON a.home = cte.home AND a.year = cte.year
Here is MySQL version which prints only one entry where there are duplicates MAX(datetime) in a group.
You could test here http://www.sqlfiddle.com/#!2/0a4ae/1
Sample Data
mysql> SELECT * from topten;
+------+------+---------------------+--------+----------+
| id | home | datetime | player | resource |
+------+------+---------------------+--------+----------+
| 1 | 10 | 2009-04-03 00:00:00 | john | 399 |
| 2 | 11 | 2009-04-03 00:00:00 | juliet | 244 |
| 3 | 10 | 2009-03-03 00:00:00 | john | 300 |
| 4 | 11 | 2009-03-03 00:00:00 | juliet | 200 |
| 5 | 12 | 2009-04-03 00:00:00 | borat | 555 |
| 6 | 12 | 2009-03-03 00:00:00 | borat | 500 |
| 7 | 13 | 2008-12-24 00:00:00 | borat | 600 |
| 8 | 13 | 2009-01-01 00:00:00 | borat | 700 |
| 9 | 10 | 2009-04-03 00:00:00 | borat | 700 |
| 10 | 11 | 2009-04-03 00:00:00 | borat | 700 |
| 12 | 12 | 2009-04-03 00:00:00 | borat | 700 |
+------+------+---------------------+--------+----------+
MySQL Version with User variable
SELECT *
FROM (
SELECT ord.*,
IF (#prev_home = ord.home, 0, 1) AS is_first_appear,
#prev_home := ord.home
FROM (
SELECT t1.id, t1.home, t1.player, t1.resource
FROM topten t1
INNER JOIN (
SELECT home, MAX(datetime) AS mx_dt
FROM topten
GROUP BY home
) x ON t1.home = x.home AND t1.datetime = x.mx_dt
ORDER BY home
) ord, (SELECT #prev_home := 0, #seq := 0) init
) y
WHERE is_first_appear = 1;
+------+------+--------+----------+-----------------+------------------------+
| id | home | player | resource | is_first_appear | #prev_home := ord.home |
+------+------+--------+----------+-----------------+------------------------+
| 9 | 10 | borat | 700 | 1 | 10 |
| 10 | 11 | borat | 700 | 1 | 11 |
| 12 | 12 | borat | 700 | 1 | 12 |
| 8 | 13 | borat | 700 | 1 | 13 |
+------+------+--------+----------+-----------------+------------------------+
4 rows in set (0.00 sec)
Accepted Answers' outout
SELECT tt.*
FROM topten tt
INNER JOIN
(
SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home
) groupedtt ON tt.home = groupedtt.home AND tt.datetime = groupedtt.MaxDateTime
+------+------+---------------------+--------+----------+
| id | home | datetime | player | resource |
+------+------+---------------------+--------+----------+
| 1 | 10 | 2009-04-03 00:00:00 | john | 399 |
| 2 | 11 | 2009-04-03 00:00:00 | juliet | 244 |
| 5 | 12 | 2009-04-03 00:00:00 | borat | 555 |
| 8 | 13 | 2009-01-01 00:00:00 | borat | 700 |
| 9 | 10 | 2009-04-03 00:00:00 | borat | 700 |
| 10 | 11 | 2009-04-03 00:00:00 | borat | 700 |
| 12 | 12 | 2009-04-03 00:00:00 | borat | 700 |
+------+------+---------------------+--------+----------+
7 rows in set (0.00 sec)
SELECT c1, c2, c3, c4, c5 FROM table1 WHERE c3 = (select max(c3) from table)
SELECT * FROM table1 WHERE c3 = (select max(c3) from table1)
Another way to gt the most recent row per group using a sub query which basically calculates a rank for each row per group and then filter out your most recent rows as with rank = 1
select a.*
from topten a
where (
select count(*)
from topten b
where a.home = b.home
and a.`datetime` < b.`datetime`
) +1 = 1
DEMO
Here is the visual demo for rank no for each row for better understanding
By reading some comments what about if there are two rows which have same 'home' and 'datetime' field values?
Above query will fail and will return more than 1 rows for above situation. To cover up this situation there will be a need of another criteria/parameter/column to decide which row should be taken which falls in above situation. By viewing sample data set i assume there is a primary key column id which should be set to auto increment. So we can use this column to pick the most recent row by tweaking same query with the help of CASE statement like
select a.*
from topten a
where (
select count(*)
from topten b
where a.home = b.home
and case
when a.`datetime` = b.`datetime`
then a.id < b.id
else a.`datetime` < b.`datetime`
end
) + 1 = 1
DEMO
Above query will pick the row with highest id among the same datetime values
visual demo for rank no for each row
Why not using:
SELECT home, MAX(datetime) AS MaxDateTime,player,resource FROM topten GROUP BY home
Did I miss something?
In MySQL 8.0 this can be achieved efficiently by using row_number() window function with common table expression.
(Here row_number() basically generating unique sequence for each row for every player starting with 1 in descending order of resource. So, for every player row with sequence number 1 will be with highest resource value. Now all we need to do is selecting row with sequence number 1 for each player. It can be done by writing an outer query around this query. But we used common table expression instead since it's more readable.)
Schema:
create TABLE TestTable(id INT, home INT, date DATETIME,
player VARCHAR(20), resource INT);
INSERT INTO TestTable
SELECT 1, 10, '2009-03-04', 'john', 399 UNION
SELECT 2, 11, '2009-03-04', 'juliet', 244 UNION
SELECT 5, 12, '2009-03-04', 'borat', 555 UNION
SELECT 3, 10, '2009-03-03', 'john', 300 UNION
SELECT 4, 11, '2009-03-03', 'juliet', 200 UNION
SELECT 6, 12, '2009-03-03', 'borat', 500 UNION
SELECT 7, 13, '2008-12-24', 'borat', 600 UNION
SELECT 8, 13, '2009-01-01', 'borat', 700
Query:
with cte as
(
select id, home, date , player, resource,
Row_Number()Over(Partition by home order by date desc) rownumber from TestTable
)
select id, home, date , player, resource from cte where rownumber=1
Output:
id
home
date
player
resource
1
10
2009-03-04 00:00:00
john
399
2
11
2009-03-04 00:00:00
juliet
244
5
12
2009-03-04 00:00:00
borat
555
8
13
2009-01-01 00:00:00
borat
700
db<>fiddle here
This works in SQLServer, and is the only solution I've seen that doesn't require subqueries or CTEs - I think this is the most elegant way to solve this kind of problem.
SELECT TOP 1 WITH TIES *
FROM TopTen
ORDER BY ROW_NUMBER() OVER (PARTITION BY home
ORDER BY [datetime] DESC)
In the ORDER BY clause, it uses a window function to generate & sort by a ROW_NUMBER - assigning a 1 value to the highest [datetime] for each [home].
SELECT TOP 1 WITH TIES will then select one record with the lowest ROW_NUMBER (which will be 1), as well as all records with a tying ROW_NUMBER (also 1)
As a consequence, you retrieve all data for each of the 1st ranked records - that is, all data for records with the highest [datetime] value with their given [home] value.
Try this
select * from mytable a join
(select home, max(datetime) datetime
from mytable
group by home) b
on a.home = b.home and a.datetime = b.datetime
Regards
K
#Michae The accepted answer will working fine in most of the cases but it fail for one for as below.
In case if there were 2 rows having HomeID and Datetime same the query will return both rows, not distinct HomeID as required, for that add Distinct in query as below.
SELECT DISTINCT tt.home , tt.MaxDateTime
FROM topten tt
INNER JOIN
(SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt
ON tt.home = groupedtt.home
AND tt.datetime = groupedtt.MaxDateTime
this is the query you need:
SELECT b.id, a.home,b.[datetime],b.player,a.resource FROM
(SELECT home,MAX(resource) AS resource FROM tbl_1 GROUP BY home) AS a
LEFT JOIN
(SELECT id,home,[datetime],player,resource FROM tbl_1) AS b
ON a.resource = b.resource WHERE a.home =b.home;
Hope below query will give the desired output:
Select id, home,datetime,player,resource, row_number() over (Partition by home ORDER by datetime desc) as rownum from tablename where rownum=1
(NOTE: The answer of Michael is perfect for a situation where the target column datetime cannot have duplicate values for each distinct home.)
If your table has duplicate rows for homexdatetime and you need to only select one row for each distinct home column, here is my solution to it:
Your table needs one unique column (like id). If it doesn't, create a view and add a random column to it.
Use this query to select a single row for each unique home value. Selects the lowest id in case of duplicate datetime.
SELECT tt.*
FROM topten tt
INNER JOIN
(
SELECT min(id) as min_id, home from topten tt2
INNER JOIN
(
SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt2
ON tt2.home = groupedtt2.home
) as groupedtt
ON tt.id = groupedtt.id
Accepted answer doesn't work for me if there are 2 records with same date and home. It will return 2 records after join. While I need to select any (random) of them. This query is used as joined subquery so just limit 1 is not possible there.
Here is how I reached desired result. Don't know about performance however.
select SUBSTRING_INDEX(GROUP_CONCAT(id order by datetime desc separator ','),',',1) as id, home, MAX(datetime) as 'datetime'
from topten
group by (home)