0

I have a table with name-value pairs and additional attribute. The same name can have more than one value. If that happens I want to return the row which has a higher attribute value.

Table:

ID | name | value | attribute
1  | set1 | 1     | 0
2  | set2 | 2     | 0
3  | set3 | 3     | 0
4  | set1 | 4     | 1

Desired results of query:

name | value
set2 | 2
set3 | 3
set1 | 4

What is the best performing sql query to get the desired results?

3
  • Does the value of attribute matter? Commented Feb 10, 2011 at 0:54
  • Yes, that's the tricky part. I'm not looking for value in row with highest attribute. I think it needs an inner join, but I'm not sure exactly how to write it. Commented Feb 10, 2011 at 0:58
  • What about ties? Suppose there is another row: (5, set1, 5, 1). Should row 4 or row 5 be returned? Commented Feb 10, 2011 at 5:28

8 Answers 8

1

the best performing query would be as follows:

select
 s.set_id,
 s.name as set_name,
 a.attrib_id,
 a.name as attrib_name,
 sav.value
from
 sets s
inner join set_attribute_values sav on 
  sav.set_id = s.set_id and sav.attrib_id = s.max_attrib_id
inner join attributes a on sav.attrib_id = a.attrib_id
order by
 s.set_id;

+--------+----------+-----------+-------------+-------+
| set_id | set_name | attrib_id | attrib_name | value |
+--------+----------+-----------+-------------+-------+
|      1 | set1     |         3 | attrib3     |    20 |
|      2 | set2     |         0 | attrib0     |    10 |
|      3 | set3     |         0 | attrib0     |    10 |
|      4 | set4     |         4 | attrib4     |    10 |
|      5 | set5     |         2 | attrib2     |    10 |
+--------+----------+-----------+-------------+-------+

obviously for this to work you're gonna also have to normalise your design and implement a simple trigger:

drop table if exists attributes;
create table attributes
(
attrib_id smallint unsigned not null primary key,
name varchar(255) unique not null
)
engine=innodb;

drop table if exists sets;
create table sets
(
set_id smallint unsigned not null auto_increment primary key,
name varchar(255) unique not null,
max_attrib_id smallint unsigned not null default 0,
key (max_attrib_id)
)
engine=innodb;

drop table if exists set_attribute_values;
create table set_attribute_values
(
set_id smallint unsigned not null,
attrib_id smallint unsigned not null,
value int unsigned not null default 0,
primary key (set_id, attrib_id)
)
engine=innodb;

delimiter #

create trigger set_attribute_values_before_ins_trig 
before insert on set_attribute_values
for each row
begin
  update sets set max_attrib_id = new.attrib_id 
    where set_id = new.set_id and max_attrib_id < new.attrib_id;
end#

delimiter ;

insert into attributes values (0,'attrib0'),(1,'attrib1'),(2,'attrib2'),(3,'attrib3'),(4,'attrib4');
insert into sets (name) values ('set1'),('set2'),('set3'),('set4'),('set5');

insert into set_attribute_values values
(1,0,10),(1,3,20),(1,1,30),
(2,0,10),
(3,0,10),
(4,4,10),(4,2,20),
(5,2,10);
Sign up to request clarification or add additional context in comments.

Comments

0

This solution will probably perform the best:

Select ...
From Table As T
    Left Join Table As T2
        On T2.name = T.name
            And T2.attribute > T1.attribute
Where T2.ID Is Null

Another solution which may not perform as well (you would need to evaluate against your data):

Select ...
From Table As T
Where Not Exists    (
                    Select 1
                    From Table As T2
                    Where T2.name = T.name
                        And T2.attribute > T.attribute
                    )

Comments

0
select name,max(value)
from table
group by name

1 Comment

This won't do what the question asks. Question asks for rows with the highest 'attribute', not the highest 'value'.
0
SELECT name, value 
FROM (SELECT name, value, attribute 
      FROM table_name 
      ORDER BY attribute DESC) AS t 
GROUP BY name;

3 Comments

The SQL standard does not permit non-aggregate columns (in this case value) in the select list. It might appear to work, but in theory MySQL is allowed to return any value at random.
I was about to check this answer, since I tested it and it works well, but now I'm confused by awm's comment.
@awm group_concat is an aggregate function right? Some simple string manipulation with it to just get the first value should make it fit with the standard. But now we're heading into messy territory. e.g. change the first SELECT to "SELECT name, substring_index(group_concat(value),',',1) as value".
0

There is no easy way to do this.

A similar question was asked here.

Edit: Here's a suggestion:

SELECT `name`,`value` FROM `mytable` ORDER BY `name`,`attribute` DESC

This isn't quite what you asked for, but it'll at least give you the higher attribute values first, and you can ignore the rest.

Edit again: Another suggestion:

If you know that value is a positive integer, you can do this. It's yucky, but it'll work.

SELECT `name`,CAST (GROUP_CONCAT(`value` ORDER by `attribute` DESC) as UNSIGNED) FROM `mytable` GROUP BY `name`

To include negative integers you could change UNSIGNED to SIGNED.

Comments

0

Might want to benchmark all these options, here's another one.

SELECT t1.name, t1.value
  FROM temp t1
 WHERE t1.attribute IN (
   SELECT MAX(t2.attribute)
     FROM temp t2
    WHERE t2.name = t1.name);

5 Comments

+1 This is a rather inefficient query, but so far it's the only answer that actually does what was asked.
:D, well we could suggest he normalize the tables.
Please do. How would you normalize the tables and then how would you run the query?
This query has the side effect that multiple rows with the same name and the same highest attribute value would all be returned. May or may not be the desired behavior.
I voted this answer up, and someone else voted it back down... ?!
0

How about:

SELECT ID, name, value, attribute  
  FROM table A
 WHERE A.attribute = (SELECT MAX(B.attribute) FROM table B WHERE B.NAME = A.NAME);

Edit: Seems like someones said the same already.

Comments

0

Did not benchmark them, but here is how it is doable: TableName = temm

1) Row with maximum value of attribute :

select t.name, t.value
from (
   select name, max(attribute) as maxattr
   from temm group by name
) as x inner join temm as t on t.name = x.name and t.attribute = x.maxattr;

2) Top N rows with maximum attribute value :

select name, value
from temm
where (
   select count(*) from temm as n
   where n.name = temm.name and n.attribute > temm.attribute
) < 1 ; /*  1 can be changed to 2,3,4 ..N  to get N rows */

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.