13

I want to use ORDER BY on every UNION ALL queries, but I can't figure out the right syntax. This is what I want:

(
SELECT id, user_id, other_id, name 
FROM tablename 
WHERE user_id = 123 AND user_in IN (...) 
ORDER BY name
)
UNION ALL
(
SELECT id, user_id, other_id, name 
FROM tablename 
WHERE user_id = 456 AND user_id NOT IN (...) 
ORDER BY name
)

EDIT: Just to be clear: I need two ordered lists like this, not one:

1 2 3 1 2 3 4 5

Thank you very much!

dreftymac
  • 31,404
  • 26
  • 119
  • 182
VORiAND
  • 145
  • 3
  • 17
  • 35
  • The ORDER BY clauses in your query (inside the parens) will be disregarded (per the MySQL Reference Manual.) MySQL is free to return the rows in any sequence, since there is no `ORDER BY` on the query. (For the statement shown in the question, an `ORDER BY` clause would be needed at the end of the statement, after that last close paren. To get the rows returned in the specified sequence, one option is to include a "discriminator" column (i.e. a distinct literal returned in each query, and then include that column in the `ORDER BY` clause. – spencer7593 Jul 11 '14 at 03:09

4 Answers4

16

Something like this should work in MySQL:

SELECT a.*
  FROM ( 
         SELECT ...  FROM ... ORDER BY ... 
       ) a
 UNION ALL 
SELECT b.*
  FROM ( 
         SELECT ...  FROM ... ORDER BY ... 
       ) b

to return rows in an order we'd like them returned. i.e. MySQL seems to honor the ORDER BY clauses inside the inline views.

But, without an ORDER BY clause on the outermost query, the order that the rows are returned is not guaranteed.

If we need the rows returned in a particular sequence, we can include an ORDER BY on the outermost query. In a lot of use cases, we can just use an ORDER BY on the outermost query to satisfy the results.

But when we have a use case where we need all the rows from the first query returned before all the rows from the second query, one option is to include an extra discriminator column in each of the queries. For example, add ,'a' AS src in the first query, ,'b' AS src to the second query.

Then the outermost query could include ORDER BY src, name, to guarantee the sequence of the results.


FOLLOWUP

In your original query, the ORDER BY in your queries is discarded by the optimizer; since there is no ORDER BY applied to the outer query, MySQL is free to return the rows in whatever order it wants.

The "trick" in query in my answer (above) is dependent on behavior that may be specific to some versions of MySQL.

Test case:

populate tables

CREATE TABLE foo2 (id INT PRIMARY KEY, role VARCHAR(20)) ENGINE=InnoDB;
CREATE TABLE foo3 (id INT PRIMARY KEY, role VARCHAR(20)) ENGINE=InnoDB;

INSERT INTO foo2 (id, role) VALUES 
  (1,'sam'),(2,'frodo'),(3,'aragorn'),(4,'pippin'),(5,'gandalf');
INSERT INTO foo3 (id, role) VALUES 
  (1,'gimli'),(2,'boromir'),(3,'elron'),(4,'merry'),(5,'legolas');

query

SELECT a.*
  FROM ( SELECT s.id, s.role
           FROM foo2 s
          ORDER BY s.role
       ) a
 UNION ALL
SELECT b.*
  FROM ( SELECT t.id, t.role
           FROM foo3 t
          ORDER BY t.role
       ) b

resultset returned

    id  role     
 ------  ---------
      3  aragorn  
      2  frodo    
      5  gandalf  
      4  pippin   
      1  sam      
      2  boromir  
      3  elron    
      1  gimli    
      5  legolas  
      4  merry    

The rows from foo2 are returned "in order", followed by the rows from foo3, again, "in order".

Note (again) that this behavior is NOT guaranteed. (The behavior we observer is a side effect of how MySQL processes inline views (derived tables). This behavior may be different in versions after 5.5.)

If you need the rows returned in a particular order, then specify an ORDER BY clause for the outermost query. And that ordering will apply to the entire resultset.

As I mentioned earlier, if I needed the rows from the first query first, followed by the second query, I would include a "discriminator" column in each query, and then include the "discriminator" column in the ORDER BY clause. I would also do away with the inline views, and do something like this:

SELECT s.id, s.role, 's' AS src
  FROM foo2 s
 UNION ALL
SELECT t.id, t.role, 't' AS src
  FROM foo3 t
 ORDER BY src, role
spencer7593
  • 106,611
  • 15
  • 112
  • 140
7

You just use one ORDER BY at the very end.

The Union turns two selects into one logical select. The order-by applies to the entire set, not to each part.

Don't use any parens either. Just:

SELECT 1 as Origin, blah blah FROM foo WHERE x
UNION ALL
SELECT 2 as Origin, blah blah FROM foo WHERE y
ORDER BY Origin, z
pmbAustin
  • 3,890
  • 1
  • 22
  • 33
7

Don't use ORDER BY in an individual SELECT statement inside a UNION, unless you're using LIMIT with it.

The MySQL docs on UNION explain why (emphasis mine):

To apply ORDER BY or LIMIT to an individual SELECT, place the clause inside the parentheses that enclose the SELECT:

(SELECT a FROM t1 WHERE a=10 AND B=1 ORDER BY a LIMIT 10) UNION
(SELECT a FROM t2 WHERE a=11 AND B=2 ORDER BY a LIMIT 10);

However, use of ORDER BY for individual SELECT statements implies nothing about the order in which the rows appear in the final result because UNION by default produces an unordered set of rows. Therefore, the use of ORDER BY in this context is typically in conjunction with LIMIT, so that it is used to determine the subset of the selected rows to retrieve for the SELECT, even though it does not necessarily affect the order of those rows in the final UNION result. If ORDER BY appears without LIMIT in a SELECT, it is optimized away because it will have no effect anyway.

To use an ORDER BY or LIMIT clause to sort or limit the entire UNION result, parenthesize the individual SELECT statements and place the ORDER BY or LIMIT after the last one. The following example uses both clauses:

(SELECT a FROM t1 WHERE a=10 AND B=1)
UNION
(SELECT a FROM t2 WHERE a=11 AND B=2)
ORDER BY a LIMIT 10;

It seems like an ORDER BY clause like the following will get you what you want:

ORDER BY user_id, name
Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
0
      (SELECT id, user_id, other_id, name
         FROM tablename
        WHERE user_id = 123
          AND user_in IN (...))
UNION ALL
      (SELECT id, user_id, other_id, name
         FROM tablename
        WHERE user_id = 456
          AND user_id NOT IN (...)))
 ORDER BY name

You can also simplify this query:

SELECT id, user_id, other_id, name
  FROM tablename
 WHERE (user_id = 123 AND user_in IN (...))
    OR (user_id = 456 AND user_id NOT IN (...))