简体   繁体   中英

MySQL ORDER BY Column = value AND distinct?

I'm getting grey hair by now...

I have a table like this.

ID - Place - Person
1 - London - Anna
2 - Stockholm - Johan
3 - Gothenburg - Anna
4 - London - Nils

And I want to get the result where all the different persons are included, but I want to choose which Place to order by.

For example. I want to get a list where they are ordered by LONDON and the rest will follow, but distinct on PERSON.

Output like this:
ID - Place - Person
1 - London - Anna
4 - London - Nils
2 - Stockholm - Johan

Tried this:

SELECT ID, Person
FROM users        
ORDER BY FIELD(Place,'London'), Person ASC "

But it gives me:

ID - Place - Person
1 - London - Anna
4 - London - Nils
3 - Gothenburg - Anna
2 - Stockholm - Johan

And I really dont want Anna, or any person, to be in the result more then once.

This is one way to get the specified output, but this uses MySQL specific behavior which is not guaranteed:

SELECT q.ID
     , q.Place
     , q.Person
  FROM ( SELECT IF(p.Person<=>@prev_person,0,1) AS r
              , @prev_person := p.Person AS person
              , p.Place
              , p.ID
           FROM users p
          CROSS
           JOIN (SELECT @prev_person := NULL) i  
          ORDER BY p.Person, !(p.Place<=>'London'), p.ID
       ) q
 WHERE q.r = 1
ORDER BY !(q.Place<=>'London'), q.Person

This query uses an inline view to return all the rows in a particular order, by Person, so that all of the 'Anna' rows are together, followed by all the 'Johan' rows, etc. The set of rows for each person is ordered by, Place='London' first, then by ID.

The "trick" is to use a MySQL user variable to compare the values from the current row with values from the previous row. In this example, we're checking if the 'Person' on the current row is the same as the 'Person' on the previous row. Based on that check, we return a 1 if this is the "first" row we're processing for aa person, otherwise we return a 0.

The outermost query processes the rows from the inline view, and excludes all but the "first" row for each Person (the 0 or 1 we returned from the inline view.)

(This isn't the only way to get the resultset. But this is one way of emulating analytic functions which are available in other RDBMS.)


For comparison, in databases other than MySQL, we could use SQL something like this:

SELECT ROW_NUMBER() OVER (PARTITION BY t.Person ORDER BY 
         CASE WHEN t.Place='London' THEN 0 ELSE 1 END, t.ID) AS rn
      , t.ID
      , t.Place
      , t.Person
   FROM users t
  WHERE rn=1
  ORDER BY CASE WHEN t.Place='London' THEN 0 ELSE 1 END, t.Person

Followup

At the beginning of the answer, I referred to MySQL behavior that was not guaranteed. I was referring to the usage of MySQL User-Defined variables within a SQL statement.

Excerpts from MySQL 5.5 Reference Manual http://dev.mysql.com/doc/refman/5.5/en/user-variables.html

"As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement."

"For other statements, such as SELECT, you might get the results you expect, but this is not guaranteed."

"the order of evaluation for expressions involving user variables is undefined."

You want to use group by instead of distinct :

SELECT ID, Person
FROM users   
GROUP BY ID, Person     
ORDER BY MAX(FIELD(Place, 'London')), Person ASC;

The GROUP BY does the same thing as SELECT DISTINCT . But, you are allowed to mention other fields in clauses such as HAVING and ORDER BY .

Try this:

SELECT ID, Place, Person
FROM users
GROUP BY Person
ORDER BY FIELD(Place,'London') DESC, Person ASC;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM