Related
I want select rows from my table with last status_Id if there is a row with status_Id = 2 for that rows
ticketStatus_Id ticket_Id status_Id
======================================
1 1 1
2 1 2 -
3 1 3 *
4 2 1
5 3 1
6 3 2 - *
7 4 1
8 4 2 -
9 4 3
10 4 4 *
I want select just rows 3, 6, 10. there are another rows with status_Id = 2 (rows 2, 6, 8) for that ticket_Id,
In other word How to select rows 3,6,10 with ticket_Id =1,3,4 that there are another row with these ticket_Ids and status_Id=2 (rows 2,6,8)
If you want the complete row, then I would view this as exists:
select t.*
from t
where exists (select 1
from t t2
where t2.ticket_id = t.ticket_id and t2.status_id = 2
) and
t.status_Id = (select max(t2.status_id)
from t t2
where t2.ticket_id = t.ticket_id
);
If you just want the ticket_id and status_id (and not the whole row), I would recommend aggregation:
select ticket_id, max(status_id)
from t
group by ticket_id
having sum(status_id = 2) > 0;
In your case, ticketStatus_Id seems to increase with status_id, so you can use:
select max(ticketStatus_Id) as ticketStatus_Id, ticket_id, max(status_id) as Status_Id
from t
group by ticket_id
having sum(status_id = 2) > 0;
First, for each ticket we get the row with the highest status. We can do this with a self-join. Each row is joined with the row with the next highest status. We select the rows which have no higher status, those will be the highest. Here's a more detailed explanation.
select ts1.*
from ticket_statuses ts1
left outer join ticket_statuses ts2
on ts1.ticket_Id = ts2.ticket_Id
and ts1.status_Id < ts2.status_Id
where ts2.ticketStatus_Id is null
3 1 3
4 2 1
6 3 2
10 4 4
11 5 3
Note that I've added a curve-ball of 11, 5, 3 to ensure we only select tickets with a status of 2, not greater than 2.
Then we can use that as a CTE (or subquery if you're not using MySQL 8) and select only those tickets who have a status of 2.
with max_statuses as (
select ts1.*
from ticket_statuses ts1
left outer join ticket_statuses ts2
on ts1.ticket_Id = ts2.ticket_Id
and ts1.status_Id < ts2.status_Id
where ts2.ticketStatus_Id is null
)
select ms.*
from max_statuses ms
join ticket_statuses ts
on ms.ticket_id = ts.ticket_id
and ts.status_id = 2;
3 1 3
6 3 2
10 4 4
This approach ensures we select the complete rows with the highest statuses and any extra data they may contain.
dbfiddle
This is basicaly a "last row per group" problem. You will find some solutions here. My prefered solution would be:
select t.*
from (
select max(ticketStatus_Id) as ticketStatus_Id
from mytable
group by ticket_Id
) tmax
join mytable t using(ticketStatus_Id)
The difference in your question is that you have a condition requiring a specific value within the group. This can be solved with a JOIN within the subquery:
select t.*
from (
select max(t1.ticketStatus_Id) as ticketStatus_Id
from mytable t2
join mytable t1 using(ticket_Id)
where t2.status_Id = 2
group by t2.ticket_Id
) tmax
join mytable t using(ticketStatus_Id)
Result:
| ticketStatus_Id | ticket_Id | status_Id |
| --------------- | --------- | --------- |
| 3 | 1 | 3 |
| 6 | 3 | 2 |
| 10 | 4 | 4 |
View on DB Fiddle
A solution using window functions could be:
select ticketStatus_Id, ticket_Id, status_Id
from (
select *
, row_number() over (partition by ticket_Id order by ticketStatus_Id desc) as rn
, bit_or(status_Id = 2) over (partition by ticket_Id) > 0 as has_status2
from mytable
) x
where has_status2 and rn = 1
A quite expressive way is to use EXISTS and NOT EXISTS subquery conditions:
select t.*
from mytable t
where exists (
select *
from mytable t1
where t1.ticket_Id = t.ticket_Id
and t1.status_Id = 2
)
and not exists (
select *
from mytable t1
where t1.ticket_Id = t.ticket_Id
and t1.ticketStatus_Id > t.ticketStatus_Id
)
SELECT a.*
FROM t a
JOIN
(
SELECT ticket_id, MAX(status_id) max_status_id
FROM t
WHERE status_id >= 2
GROUP BY ticket_id
) b
ON a.ticket_id = b.ticket_id
AND a.status_id = b.max_status_id;
SELECT
MAX(m1.ticketstatus_Id) as ticket_status,
m1.ticket_Id as ticket,
MAX(m1.status_Id) as status
FROM mytable m1
WHERE
m1.ticket_Id in (select m2.ticket_Id from mytable m2 where m2.ticket_Id=m1.ticket_Id and m2.status_Id=2)
GROUP BY m1.ticket_Id
having a table structure of id and a name:
create table Mytable (
id integer not null,
name varchar(30) not null,
unique(id)
);
insert into Mytable (id,name) values
(1 , 'one'),
(2 , 'two'),
(3 , 'three'),
(4 , 'four'),
(6 , 'six');
How may I get a mix of even and odd rows in a result table like:
even | odd
-----------
null one '0 is not in Mytable so it puts null value
two three
four null '5 and 6 are not in Mytable so it puts null value
six null
I was trying to first get the following as a template and use it later as
a dictionary:
SELECT MIN(id-1) as id,MAX(id-1) as col
FROM Mytable
GROUP BY FLOOR((id+1)/2);
I get:
id col
0 1
2 3
5 5
But I do not know how to continue
For MySQL Version <= 5.7, You can use the below query
Query 1:
SELECT
MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even,
MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd
FROM
(
SELECT
(SELECT MAX(id) FROM mytable) AS maxid,
#rn := #rn + 1 AS rn,
(SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid
FROM
mytable
JOIN
(SELECT #rn := -1) AS var
) AS t
JOIN
mytable m ON FLOOR(m.id/2) = t.rid
GROUP BY rid;
Result 1:
even | odd
:--- | :----
null | one
two | three
four | null
six | null
Demo 1:
db fiddle
Query 2:
After confirmation based on #Madhur Bhaiya comment. If there is no row for id = 8 and 9 then it will show null, null.
SELECT
MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even,
MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd
FROM
(
SELECT
(SELECT MAX(id) FROM mytable) AS maxid,
#rn := #rn + 1 AS rn,
(SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid
FROM
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t
JOIN
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2
JOIN
(SELECT #rn := -1) var -- currently it will return 1..100, if needed more add joins based on your needs
) AS t
LEFT JOIN
mytable m ON FLOOR(m.id/2) = t.rid
GROUP BY rid HAVING rid IS NOT NULL;
Result 2:
even | odd
:------ | :-----
null | one
two | three
four | null
six | null
null | null
null | eleven
null | null
null | null
sixteen | null
Demo 2:
db fiddle
For MySQL Version > 8.0, You can use #Nick query but if you need null, null like Result 2 mentioned for <= v5.7 then add LEFT JOIN with ORDER BY clause.
Query:
with recursive maxid as (
select max(id) as id from Mytable)
, cte as (
select 0 as rid
union all
select rid + 1
from cte
cross join maxid
where (rid + 1) * 2 <= maxid.id)
select max(case when m.id % 2 = 0 then name end) as even,
max(case when m.id % 2 = 1 then name end) as odd
from cte
left join Mytable m on floor(m.id / 2) = cte.rid
group by rid order by rid;
Result:
even | odd
:------ | :-----
null | one
two | three
four | null
six | null
null | null
null | eleven
null | null
null | null
sixteen | null
Demo: db fiddle
Credits: Thanks to #Nick, #Madhur Bhaiya for the fiddle and the logic used to create this query.
Here's a CTE based query that will work in SQL Server and MySQL > v8.0 (with the addition of the keyword recursive before maxid). It generates a list of rows that encompasses the pairs of MyTable values (in the sample, this is 0,1,2,3) and then JOINs that to Mytable to extract the even/odd column values:
with maxid as (
select max(id) as id from Mytable)
, cte as (
select 0 as rid
union all
select rid + 1
from cte
cross join maxid
where (rid + 1) * 2 <= maxid.id)
select max(case when m.id % 2 = 0 then name end) as even,
max(case when m.id % 2 = 1 then name end) as odd
from cte
join Mytable m on m.id / 2 = cte.rid
group by rid
Output:
even odd
one
two three
four
six
Demo on dbfiddle
I'm trying to calculate and list the websites in order of biggest overall reduction in response time from one time period to the next.
I don't strictly need to use a single query to do this, I can potentially run multiple queries.
websites:
| id | url |
| 1 | stackoverflow.com |
| 2 | serverfault.com |
| 3 | stackexchange.com |
responses:
| id | website_id | response_time | created_at |
| 1 | 1 | 93.26 | 2014-01-28 11:51:39
| 2 | 1 | 99.46 | 2014-01-28 11:52:38
| 2 | 1 | 94.51 | 2014-01-28 11:53:38
| 2 | 1 | 104.46 | 2014-01-28 11:54:38
| 2 | 1 | 85.46 | 2014-01-28 11:56:38
| 2 | 1 | 100.00 | 2014-01-28 11:57:36
| 2 | 1 | 50.00 | 2014-01-28 11:58:37
| 2 | 2 | 100.00 | 2014-01-28 11:58:38
| 2 | 2 | 80 | 2014-01-28 11:58:39
Ideally the result would look like:
| percentage_change | website_id |
| 52 | 1 |
| 20 | 2 |
I've got as far as figuring out the largest response time, but no idea how to do another query to calculate the lowest response time and then do the math, then sort the maths.
SELECT * FROM websites
LEFT JOIN (
SELECT distinct *
FROM responses
ORDER BY response_time desc) responsetable
ON websites.id=responsetable.website_id group by website_id
Thanks
You need the equivalent of the lag() or lead() function. In MySQL, I do this using a correlated subquery:
select website_id, max(1 - (prev_response_time / response_time)) * 100
from (select t.*,
(select t2.response_time
from table t2
where t2.website_id = t.website_id and
t2.created_at < t.created_at
order by t2.created_at desc
limit 1
) as prev_response_time
from table t
) t
group by website_id;
EDIT:
If you want the change from the highest to the lowest:
select website_id, (1 - min(response_time) / max(response_time)) * 100
from table t
group by website_id;
Using a couple of sequence numbers:-
SELECT a.id, a.url, MAX(100 * (LeadingResponse.response_time - TrailingResponse.response_time) / LeadingResponse.response_time)
FROM
(
SELECT website_id, created_at, response_time, #aCnt1 := #aCnt1 + 1 AS SeqCnt
FROM responses
CROSS JOIN
(
SELECT #aCnt1:=1
) Deriv1
ORDER BY website_id, created_at
) TrailingResponse
INNER JOIN
(
SELECT website_id, created_at, response_time, #aCnt2 := #aCnt2 + 1 AS SeqCnt
FROM responses
CROSS JOIN
(
SELECT #aCnt2:=2
) Deriv2
ORDER BY website_id, created_at
) LeadingResponse
ON LeadingResponse.SeqCnt = TrailingResponse.SeqCnt
AND LeadingResponse.website_id = TrailingResponse.website_id
INNER JOIN websites a
ON LeadingResponse.website_id = a.id
GROUP BY a.id, a.url
SQL fiddle for this:-
http://www.sqlfiddle.com/#!2/ace08/1
EDIT - different way of doing it. This will only work if the id on the responses table is in date / time order.
SELECT a.id, a.url, MAX(100 * (r2.response_time - r1.response_time) / r2.response_time)
FROM responses r1
INNER JOIN responses r2
ON r1.website_id = r2.website_id
INNER JOIN
(
SELECT r1.website_id, r1.id, MAX(r2.id) AS prev_id
FROM responses r1
INNER JOIN responses r2
ON r1.website_id = r2.website_id
AND r1.id > r2.id
GROUP BY r1.website_id, r1.id
) ordering_query
ON r1.website_id = ordering_query.website_id
AND r1.id = ordering_query.id
AND r2.id = ordering_query.prev_id
INNER JOIN websites a
ON r1.website_id = a.id
GROUP BY a.id, a.url
You could do a similar thing based on the response_time field rather than the id, but that would require the response_time for a website to be unique.
EDIT
Just seen that you do not just want consecutive changes, rather just the highest to lowest response. Assuming that the lowest doesn't have to come after the highest:-
SELECT id, url, MAX(100 * (max_response - min_response) / max_response)
FROM
(
SELECT a.id, a.url, MIN(r1.response_time) AS min_response, MAX(r1.response_time) AS max_response
FROM responses r1
INNER JOIN websites a
ON r1.website_id = a.id
GROUP BY a.id, a.url
) Sub1
If you are only interested in the lower response time being after the higher one:-
SELECT id, url, MAX(100 * (max_response - min_following_response) / max_response)
FROM
(
SELECT a.id, a.url, MAX(r1.response_time) AS max_response, MIN(r2.response_time) AS min_following_response
FROM responses r1
INNER JOIN responses r2
ON r1.website_id = r2.website_id
AND (r1.created_at < r2.created_at
OR (r1.created_at = r2.created_at
AND r1.id < r2.id))
INNER JOIN websites a
ON r1.website_id = a.id
GROUP BY a.id, a.url
) Sub1
(assuming that the id field on the response table is unique and in created at order)
From your "I've got as far as figuring out the largest response time, but no idea how to do another query to calculate the lowest response time and then do the math, then sort the maths." I understant that you want smallest response time and largest response time and do your math.
drop table #test
create table #test(
id int, website_id int, response_time decimal, created_at datetime)
insert into #test (id , website_id , response_time , created_at) values ( 1 , 1 , 93.26, '2014-01-28 11:51:39')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 99.46 , '2014-01-28 11:52:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 94.51 , '2014-01-28 11:53:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 104.46 , '2014-01-28 11:54:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 85.46 , '2014-01-28 11:56:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 100.00 , '2014-01-28 11:57:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 50.00 , '2014-01-28 11:58:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 2 , 100.00 , '2014-01-28 11:58:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 2 , 80 , '2014-01-28 11:58:38')
select * from #test
select distinct MINT.website_id,MINT.MINRT,maxT.MINRT,(MINT.MINRT/maxT.MINRT)*100--Do your calculation here---
from #test t0
inner join(select min(response_time) as MINRT,website_id from #test group by website_id ) MINT
on MINT.website_id = t0.website_id
inner join(select max(response_time) as MINRT,website_id from #test group by website_id ) maxT
on maxT.website_id = t0.website_id
You want to divide the minimum response time by the maximum response time per website? That would simply be:
select
websites.id as website_id,
100 - min(response_time) / max(response_time) * 100 as percentage_change
from websites
left join responses on websites.id = responses.website_id
group by websites.id;
(I assume response_time can never be zero. In case it can, you will have to use a case statement for that.)
Group the response times by website_id, find MIN(response_time) and MAX(response_time) and compare whether MIN() happened after MAX() to filter only websites which improved their performance.
<?php
$rows = $db->fetchAll('
select
r.website_id, min(r.response_time) min_time, max(r.response_time) max_time,
(select
rmin.created_at
FROM
responses rmin
WHERE
rmin.response_time = min(r.response_time) AND
rmin.website_id = r.website_id
ORDER BY rmin.created_at
LIMIT 1) min_created_at,
(select
rmax.created_at
FROM
responses rmax
WHERE
rmax.response_time = max(r.response_time) AND
rmax.website_id = r.website_id
ORDER BY rmax.created_at DESC
LIMIT 1) max_created_at
FROM
responses r
GROUP BY
r.website_id');
foreach($rows as $row) {
if($row['max_created_at'] < $row['min_created_at']) {
echo 'Website '.$row['website_id'].
' improved by '.
(100 - (($row['min_time'] / $row['max_time']) * 100)).
"%\n";
}
}
The query will be however most probably pretty slow with large datasets. You'll have to optimize the indexes and/or query.
sqlfiddle: http://www.sqlfiddle.com/#!2/fa8f9/8
I have a SQL table with the following structure:
id Integer, (represents a userId)
version Integer,
attribute varchar(50)
So some sample rows are:
4, 1, 'test1'
4, 2, 'test2'
4, 3, 'test3'
I need to generate output in the following format:
4, 1, 'test1', 'test1'
4, 2, 'test2', 'test1'
4, 3, 'test3', 'test2'
So the format of my output would be:
id Integer,
version Integer,
attribute_current varchar,
attribute_old varchar
I already tried the following:
select versionTest.id, versionTest.version, versionTest.attribute, maxVersionTest.attribute
from versionTest
inner join
versionTest maxVersionTest
ON
versionTest.id = versionTest.id
AND
versionTest.version =
(Select max(version) currentMaxVersion
from versionTest maxRow
where maxRow.id = id);
The above query executes, but returns incorrect results. It only returns the largest version instead of returning rows for all versions.
Any ideas about how I should fix my query to produce the correct results? Thanks!
Note - The version numbers are not guaranteed to be sequential starting at 1. My actual database has some unusual version numbers (i.e. user 7 has version 3 and 15 with no versions 4, 5, 6, etc...).
SELECT a.*, COALESCE(b.attribute,a.attribute) attribute_old
FROM
( SELECT x.*
, COUNT(*) rank
FROM versiontest x
JOIN versiontest y
ON y.id = x.id
AND y.version <= x.version
GROUP
BY x.id
, x.version
) a
LEFT
JOIN
( SELECT x.*
, COUNT(*) rank
FROM versiontest x
JOIN versiontest y
ON y.id = x.id
AND y.version <= x.version
GROUP
BY x.id
, x.version
) b
ON b.id = a.id
AND b.rank = a.rank-1;
Sample output (DEMO):
+----+---------+-----------+------+---------------+
| id | version | attribute | rank | attribute_old |
+----+---------+-----------+------+---------------+
| 4 | 1 | test1 | 1 | test1 |
| 4 | 5 | test2 | 2 | test1 |
| 4 | 7 | test3 | 3 | test2 |
| 5 | 2 | test3 | 1 | test3 |
| 5 | 3 | test4 | 2 | test3 |
| 5 | 8 | test5 | 3 | test4 |
+----+---------+-----------+------+---------------+
Since you have mentioned that:
The version numbers are not guaranteed to be sequential starting at 1. My actual database has some unusual version numbers (i.e. user 7 has version 3 and 15 with no versions 4, 5, 6, etc...)
MySQL doesn't support window functions like any other RDBMS does, you can still simulate on how you can create a sequential numbers and used as the linking column to get the previous rows. Ex,
SELECT a.ID, a.Version, a.attribute attribute_new,
COALESCE(b.attribute, a.attribute) attribute_old
FROM
(
SELECT ID, version, attribute,
#r1 := #r1 + 1 rn
FROM TableName, (SELECT #r1 := 0) b
WHERE ID = 4
ORDER BY version
) a
LEFT JOIN
(
SELECT ID, version, attribute,
#r2 := #r2 + 1 rn
FROM TableName, (SELECT #r2 := 0) b
WHERE ID = 4
ORDER BY version
) b ON a.rn = b.rn + 1
SQLFiddle Demo
If version numbers always increase by 1, you could:
select cur.id
, cur.version
, cur.attribute
, coalesce(prev.attribute, cur.attribute)
from versionTest
left join
versionTest prev
on prev.id = cur.id
and prev.version = cur.version + 1
You could try...
SELECT ID,
VERSION,
ATTRIBUTE,
(SELECT ATTRIBUTE
FROM VERSIONTEST V3
WHERE V3.ID = V1.ID AND
V3.VERSION = (SELECT MAX(VERSION)
FROM VERSIONTEST V2
WHERE V2.ID = V1.ID AND
V2.VERSION < V1.VERSION)) AS PREVIOUSATTRIBUTE
FROM VERSIONTEST V1;
provided the version values are in numerical order.
I think the easiest way to express this is with a correlated subquery:
select id, version, attribute as attribute_current,
(select attribute
from VersionTest vt2
where vt2.id = vt.id and vt2.version < vt.version
order by vt2.version
limit 1
) as attribute_prev
from VersionTest vt
This version would put in NULL as the prev value for the first row. If you really want it repeated:
select id, version, attribute as attribute_current,
coalesce((select attribute
from VersionTest vt2
where vt2.id = vt.id and vt2.version < vt.version
order by vt2.version
limit 1
), vt.attribute
) as attribute_prev
from VersionTest vt
I need help in finding the rows that correspond to the most recent date, the next most recent and the one after that, where some condition ABC is "Y" and group it by a column name XYZ ASC but XYZ can appear multiple times. So, say XYZ is 50, then for the rows in the three years, the XYZ will be 50. I have the following code that executes but returns only two rows out of thousands which is impossible. I tried executing just the date condition but it returned dates that were less than or equal to MAX(DATE)-3 as well. Don't know where I am going wrong.
select * from money.cash where DATE =(
select
MAX(DATE)
from
money.cash
where
DATE > (select MAX(DATE)-3 from money.cash)
)
GROUP BY XYZ ASC
having ABC = "Y";
The structure of the table is as follows (only a schematic, not the real thing).
Comp_ID DATE XYZ ABC $$$$ ....
1 2012-1-1 10 Y SOME-AMOUNT
2 2011-1-1 10 Y
3 2006-1-1 10 Y
4 2011-1-1 20 Y
5 2002-1-1 20 Y
6 2000-1-1 20 Y
7 1998-1-1 20 Y
The desired o/p would be the first three rows for XYZ=10 in ascending order and the most recent 3 dates for XYZ=20.
LAST AND IMPORTANT-This table's values keeps changing as new data comes in. So, the o/p(which will be in a new table) must reflect the dynamics in the 1st/original/above TABLE.
MySQL doesn't have functionallity that is friendly to greatest-n-per-group queries.
One option would be...
- Find the MAX(Date) per group (XYZ)
- Then use that result to find the MAX(Date) of all records before that date
- Then do it again for all records before that date
It's really innefficient, but MySQL hasn't got the functionality required to do this efficiently. Sorry...
CREATE TABLE yourTable
(
comp_id INT,
myDate DATE,
xyz INT,
abc VARCHAR(1)
)
;
INSERT INTO yourTable SELECT 1, '2012-01-01', 10, 'Y';
INSERT INTO yourTable SELECT 2, '2011-01-01', 10, 'Y';
INSERT INTO yourTable SELECT 3, '2006-01-01', 10, 'Y';
INSERT INTO yourTable SELECT 4, '2011-01-01', 20, 'Y';
INSERT INTO yourTable SELECT 5, '2002-01-01', 20, 'Y';
INSERT INTO yourTable SELECT 6, '2000-01-01', 20, 'Y';
INSERT INTO yourTable SELECT 7, '1998-01-01', 20, 'Y';
SELECT
yourTable.*
FROM
(
SELECT
lookup.XYZ,
COALESCE(MAX(yourTable.myDate), lookup.MaxDate) AS MaxDate
FROM
(
SELECT
lookup.XYZ,
COALESCE(MAX(yourTable.myDate), lookup.MaxDate) AS MaxDate
FROM
(
SELECT
yourTable.XYZ,
MAX(yourTable.myDate) AS MaxDate
FROM
yourTable
WHERE
yourTable.ABC = 'Y'
GROUP BY
yourTable.XYZ
)
AS lookup
LEFT JOIN
yourTable
ON yourTable.XYZ = lookup.XYZ
AND yourTable.myDate < lookup.MaxDate
AND yourTable.ABC = 'Y'
GROUP BY
lookup.XYZ,
lookup.MaxDate
)
AS lookup
LEFT JOIN
yourTable
ON yourTable.XYZ = lookup.XYZ
AND yourTable.myDate < lookup.MaxDate
AND yourTable.ABC = 'Y'
GROUP BY
lookup.XYZ,
lookup.MaxDate
)
AS lookup
INNER JOIN
yourTable
ON yourTable.XYZ = lookup.XYZ
AND yourTable.myDate >= lookup.MaxDate
WHERE
yourTable.ABC = 'Y'
ORDER BY
yourTable.comp_id
;
DROP TABLE yourTable;
There are other options, but they're all a bit hacky. Search SO for greatest-n-per-group mysql.
My results using your example data:
Comp_ID | DATE | XYZ | ABC
------------------------------
1 | 2012-1-1 | 10 | Y
2 | 2011-1-1 | 10 | Y
3 | 2006-1-1 | 10 | Y
4 | 2011-1-1 | 20 | Y
5 | 2002-1-1 | 20 | Y
6 | 2000-1-1 | 20 | Y
Here's another way, hopefully more efficient than Dems' answer.
Test it with an index on (abc, xyz, date):
SELECT m.xyz, m.date --- for all columns: SELECT m.*
FROM
( SELECT DISTINCT xyz
FROM money.cash
WHERE abc = 'Y'
) AS dm
JOIN
money.cash AS m
ON m.abc = 'Y'
AND m.xyz = dm.xyz
AND m.date >= COALESCE(
( SELECT im.date
FROM money.cash AS im
WHERE im.abc = 'Y'
AND im.xyz = dm.xyz
ORDER BY im.date DESC
LIMIT 1
OFFSET 2 --- to get 3 latest rows per xyz
), DATE('1000-01-01') ) ;
If you have more than rows with same (abc, xyz, date), the query may return more than 3 rows per xyz (all tied in 3rd place will all be shown).