MySQL Max() doesn't affect other columns [duplicate] - mysql

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 7 months ago.
I have a database contains multiple values with same id but different build. What I am trying is to get only the row with highest build.
Lets say I have a data like below;
| id | build | name | value |
|------|---------|--------|---------|
| 1 | 100 | Older | 5 |
| 1 | 101 | Old | 10 |
| 1 | 102 | Curr | 15 |
When I run the following query;
SELECT id, MAX(build), name, value
FROM myTable
WHERE id = 1 (or id in (1..n) in real life)
GROUP BY id
I get the following
| id | build | name | value |
|------|---------|--------|---------|
| 1 | 102 | Older | 5 |
instead of;
| id | build | name | value |
|------|---------|--------|---------|
| 1 | 102 | Curr | 15 |
I am trying to achieve expected result without subquery. Is there any way to achieve this?
Thanks in advance!

You must group by name and value too :
SELECT id, MAX(build), name, value
FROM myTable
WHERE id = 1 (or id in (1..n) in real life)
GROUP BY id, name, value

Many databases process the "FROM" line first, the "WHERE" line second, and the "SELECT" last. Because of that, you are getting the first column that fits your "WHERE" line. You can eliminate this by adding the max requirement in the "WHERE" line such as:
SELECT id, MAX(build), name, value
FROM myTable
WHERE id = 1 (or id in (1..n) in real life)
AND build = (SELECT MAX(build) from myTable)
GROUP BY id;

I think you want a LIMIT query here:
SELECT id, build, name, value
FROM myTable
ORDER BY build DESC
LIMIT 1;
In the event that there could be two or more records tied for the maximum build value, then use a subquery:
SELECT id, build, name, value
FROM myTable
WHERE build = (SELECT MAX(build) FROM myTable);
Edit: To handle your problem finding the max build record for each separate id groups of records, use either ROW_NUMBER or RANK on MySQL 8+. Assuming ROW_NUMBER:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY build DESC) rn
FROM myTable
)
SELECT id, build, name, value
FROM cte
WHERE rn = 1;

This should do it for you:
WITH tempdata as
(
SELECT ROW_NUMBER() OVER(PARTITION BY id
ORDER BY build desc) as RowNumber,
id,
build,
name,
value
FROM #temptable
)
SELECT id,build,name,value FROM tempdata WHERE RowNumber = 1

Related

Why can't I use this code to select the max row

I'm trying to select the row that contains the largest number and have accomplished it using this fairly simple query:
SELECT s1.score, name
FROM scores s1
JOIN (
SELECT id, MAX(score) score
FROM scores
GROUP BY id
) s2 ON s1.score = s2.score
All it does (If im not wrong), is just checking if the score field is equal the the MAX(score). So why can't we just do it using one single SELECT statement ?. Something like this:
SELECT id, score
FROM scores
GROUP BY id
HAVING MAX(score) = score
*The code above does not work, I want to ask why it is not working, because its essentially doing the same thing as the previous code I posted
Also here's the data I'm working with:
The problem in your second query is the fact that the GROUP BY clause requires all non-aggregated fields within its context. In your case you are dealing with three fields (namely "id", "score" and "MAX(score)") and you're referencing only one (namely "id") inside the GROUP BY clause.
Fixing that would require you to add the non-aggregated "score" field inside your GROUP BY clause, as follows:
SELECT id, score
FROM scores
GROUP BY id, score
HAVING MAX(score) = score
Though this would lead to a wrong aggregation and output, because it would attempt to get the maximum score for each combination of (id, score).
And if you'd attempt to remove the "score" field from both the SELECT and GROUP BY clauses, to solve the non-aggregated columns issue, as follows:
SELECT id
FROM scores
GROUP BY id
HAVING MAX(score) = score
Then the HAVING clause would complain as long it references the "score" field but it is found neither within the SELECT clause nor within the GROUP BY clause.
There's really no way for you to use that kind of notation, as it either violates the full GROUP_BY mode, or it just returns the wrong output.
It returns all persons with same score which the score is the max:
WITH CTE AS (
SELECT *, ROW_NUMBER() OVER(ORDER BY score desc) RN
FROM scores
)
SELECT * FROM CTE
WHERE CTE.RN = 1
Here's what your queries return
DROP table if exists t;
create table t
(id int,score int);
insert into t values
(1,10),(2,20),(3,20);
SELECT s1.id,s1.score
FROM t s1
JOIN (
SELECT id, MAX(score) score
FROM t
GROUP BY id
) s2 ON s1.score = s2.score ;
+------+-------+
| id | score |
+------+-------+
| 1 | 10 |
| 2 | 20 |
| 2 | 20 |
| 3 | 20 |
| 3 | 20 |
+------+-------+
5 rows in set (0.001 sec)
SELECT id, score,max(score)
FROM t
GROUP BY id
HAVING MAX(score) = score
+------+-------+------------+
| id | score | max(score) |
+------+-------+------------+
| 1 | 10 | 10 |
| 2 | 20 | 20 |
| 3 | 20 | 20 |
+------+-------+------------+
3 rows in set (0.001 sec)
Neither result seems to be what you are looking for. You could clarify by posting sample data and desired outcome.

Get last 3 rows from SQL table without duplicates of a row

Lets say we have a table that looks like this:
+---------------+----------------+-------------------+
| ID | random_string | time |
+---------------+----------------+-------------------+
| 2 | K2K3KD9AJ |2022-07-21 20:41:15|
| 1 | SJQJ8JD0W |2022-07-17 23:46:13|
| 1 | JSDOAJD8 |2022-07-11 02:52:21|
| 3 | KPWJOFPSS |2022-07-11 02:51:57|
| 1 | DA8HWD8HHD |2022-07-11 02:51:49|
------------------------------------------------------
I want to select the last 3 entries into the table, however they must all have separate ID's.
Expected Result:
+---------------+----------------+-------------------+
| ID | random_string | time |
+---------------+----------------+-------------------+
| 2 | K2K3KD9AJ |2022-07-21 20:41:15|
| 1 | SJQJ8JD0W |2022-07-17 23:46:13|
| 3 | KPWJOFPSS |2022-07-11 02:51:57|
------------------------------------------------------
I have already tried:
SELECT DISTINCT id FROM table ORDER BY time DESC LIMIT 3;
And:
SELECT MIN(id) as id FROM table GROUP BY time DESC LIMIT 3;
If you're not on MySQL 8, then I have two suggestions.
Using EXISTS:
SELECT m1.ID,
m1.random_string,
m1.time
FROM mytable m1
WHERE EXISTS
(SELECT ID
FROM mytable AS m2
GROUP BY ID
HAVING m1.ID=m2.ID
AND m1.time= MAX(time)
)
Using JOIN:
SELECT m1.ID,
m1.random_string,
m1.time
FROM mytable m1
JOIN
(SELECT ID, MAX(time) AS mxtime
FROM mytable
GROUP BY ID) AS m2
ON m1.ID=m2.ID
AND m1.time=m2.mxtime
I've not test in large data so don't know which will perform better (speed) however this should return the same result:
Here's a fiddle
Of course, this is considering that there will be no duplicate of exact same ID and time value; which seems to be very unlikely but still it's possible.
Using MySql 8 an easy solution is to assign a row number using a window:
select Id, random_string, time
from (
select *, Row_Number() over(partition by id order by time desc) rn
from t
)t
where rn = 1
order by time desc
limit 3;
See Demo

Get Earliest or Latest Date of Row Detail in MySQL [duplicate]

This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
Closed 2 years ago.
I have a table like so. The way it works is that the billing occurs daily to make sure that accounts are current.
+------+------------+-------------+
| ID | AcctType | BillingDate |
+------+------------+-------------+
| 100 | Individual | 2020-01-01 |
| 100 | Individual | 2020-01-02 |
| 100 | Individual | 2020-01-03 |
| 101 | Group | 2020-01-01 |
| 101 | Group | 2020-01-02 |
| 101 | Individual | 2020-01-01 |
+------+------------+-------------+
What I need to find is the first and last AcctType of each plan by ID since the AcctType can change. I am using MySQL and the aggregation of select ID, AcctType, min(BillingDate) from table group by ID won't work because AcctType will return a random value associated with the ID. How do I reliably get the latest and earliest AcctType by ID? Using version 5.6.
If you are running MySQL 8.0, you can use window functions for this:
select distinct
id,
first_value(acctType) over(
partition by id
order by billingDate
rows between unbounded preceding and unbounded following
) firstAccType,
last_value(acctType) over(
partition by id
order by billingDate
rows between unbounded preceding and unbounded following
) lastAccType
from mytable
This generates a single record for each id, with the first and last value of accType in columns.
In earlier versions, using correlated subquery is probably the simplest solution to achieve the same result:
select distinct
id,
(
select t1.accType
from mytable t1
where t1.id = t.id
order by billingDate asc
limit 1
) firstAccType,
(
select t1.accType
from mytable t1
where t1.id = t.id
order by billingDate desc
limit 1
) lastAccType
from mytable

In MySQL, select one row out of multiple rows based on condition, otherwise select first row and exclude the rest

I am interested in MySQL of writing a query that looks through a list consisting of IDs and locations. Each ID represents a unique person who can be tied to multiple locations.
I have the following table to simplify things:
+----+----------+
| ID | Location |
+----+----------+
| 1 | Bldg#1 |
| 1 | Bldg#2 |
| 2 | Bldg#3 |
+----+----------+
I am looking to deduplicate the above table to only end up with ONE row per ID, but I would also like to add a conditional that preferences Bldg#1 for any given ID. In other words, given a table of multiple rows with potentially the same ID and multiple locations, I would like to write a query that outputs 1 row per ID, and if any of the rows associated with that ID also have a location of Bldg#1, I want to keep that row and drop the rest. Otherwise, I just want to keep one arbitrary location row for that ID.
For the above table, I would like the following as output:
+----+----------+
| ID | Location |
+----+----------+
| 1 | Bldg#1 |
| 2 | Bldg#3 |
+----+----------+
You can group by id and use conditional aggregation:
select id,
case
when max(location = 'Bldg#1') then 'Bldg#1'
else any_value(location)
end location
from tablename
group by id
See the demo.
Results:
| id | location |
| --- | -------- |
| 1 | Bldg#1 |
| 2 | Bldg#3 |
You can use row_number() with a case expression:
select id, location
from (select t.*,
row_number() over (partition by id
order by (case location when 'Bldg#1' then 1 when 'Bldg#2' then 2 when 'Bldg#3' then 3 else 4 end)
) as seqnum
from t
) t
where seqnum = 1;
This does not assume any particular ordering -- such as alphabetical ordering.
Is this you looking for?
Exmaple:
Query:
DROP TABLE IF EXISTS #TEST
CREATE TABLE #TEST (ID INT, Location NVARCHAR(10))
INSERT INTO #TEST
SELECT 1,'Bldg#1'
UNION
SELECT 1,'Bldg#2'
UNION
SELECT 2,'Bldg#3'
SELECT ID,Location FROM (
SELECT ID,Location, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RNM
FROM #TEST) T
WHERE RNM = 1
inner query will make sure the location is in order so that Bldg#1 is always the first for each id, so then the outer group by will pick the first record
SELECT * FROM
(
SELECT id, location
FROM location
ORDER BY id, location ASC
)a
GROUP BY id

Limit MySQL Results to One From Each "Group"

Suppose we have a table like the one below.
Id | Name | Group
-----------------
1 | John | 1
2 | Zayn | 2
3 | Four | 2
4 | Ben_ | 3
5 | Joe_ | 2
6 | Anna | 1
The query below will select all of them.
SELECT `Name` FROM `Table` WHERE 1;
How would I select only one person from each group? Who it is doesn't really matter, as long as there's only one name from group 1 and one name from group 2 etc.
The GROUP BY clause isn't fit for this (according to my error console) because I am selecting non aggregated values, which makes sense.
The DISTINCT clause isn't great here either, since I don't want to select the "Group" and definitely not group by their names.
If is not important the resulting name You can anawy leverage some group functions eg with max or min..
leverage the group functions
select max(name) from your_table
group by Group;
otherwise you can use subquery
select name from your_table
where Id in (select min(Id) from your_table group by Group);