I was trying to write some migration code using UNION and an ORDER BY and found it putting the ORDER BY before the second query. Queries added via SelectQueryInterface::union() should either have their order stripped or be turned into subqueries. I think the fix is to move the UNION output in __toString() so it is called before the parts processing ORDER BY and RANGEs.

Comments

hass’s picture

I'm using table sort in http://drupalcode.org/project/linkchecker.git/blob/refs/heads/7.x-1.x:/l... and this worked when developped it, but maybe i missed something... I guess this may be a major issue for me.

drewish’s picture

Status: Active » Needs review
StatusFileSize
new1.3 KB

Here's a test case to demonstrate the problem. Setting to needs review to demonstrate the failure.

Status: Needs review » Needs work

The last submitted patch, dbtng_1145076.patch, failed testing.

Saaraneth’s picture

Version: 8.x-dev » 7.x-dev

Drupal newb here and I'm getting the same problem. I have two select queries, both of which return 3 columns that I want to union, and then order by the first field. The system always puts ORDER BY before the second query, and I get a "General error: 1221 Incorrect usage of UNION and ORDER BY" exception.

jbrown’s picture

Version: 7.x-dev » 8.x-dev
Micha1111’s picture

same problem here
no solution for D7 ?

cbergmann’s picture

Version: 8.x-dev » 7.x-dev
StatusFileSize
new1.31 KB

One Sollution is to wrap the select into parentheses.

The attached patch is against Drupal 7. It wraps the "master" select and the union selects into parentheses.

cbergmann’s picture

Status: Needs work » Needs review
ionut_ottawa’s picture

This didn't work for me.

cbergmann’s picture

#7: core-union-order-by-1145076-7.patch queued for re-testing.
What was the problem? Was it a problem applying the patch?

David_Rothstein’s picture

Wrapping the queries in parentheses fixes the fatal error, but in most cases leaving the ORDER BY as part of the individual select statements doesn't do what you want (see e.g. http://dev.mysql.com/doc/refman/5.1/en/union.html - it doesn't affect the actual order of the final result set, but rather only is useful if you need a LIMIT on the individual selects also).

I think for this issue we should support the most common case which is to order the final query (and according to some comments I've seen that's the only one officially supported by the SQL standard, although I haven't bothered to verify).

So, the attached patch goes back to @drewish's original suggestion for how to fix this, and combines it with the tests from #2. I modified the tests a bit also, because it didn't seem like they were asserting the correct expected order?

This will need to be moved to Drupal 8, but here are patches for Drupal 7 for starters to let the testbot run against them first.

David_Rothstein’s picture

Version: 7.x-dev » 8.x-dev
Issue tags: +Needs backport to D7
StatusFileSize
new2.76 KB
new1.46 KB

Patches for Drupal 8.

nagiek’s picture

Can we include the the wrapping parentheses (mentioned in #11, for using LIMIT with two select queries) in the patch?

bigjim’s picture

I'm seeing a similar issue with LIMIT as reported originally by @drewish concerning ORDER BY statements, #11 fixes the issue for me in D7.

aangel’s picture

Yes, LIMIT is failing for me, too.

aangel’s picture

BTW, the patch doesn't work for me. DBTNG still putting the order by in the wrong place (with 7.15):

PDOException: SQLSTATE[HY000]: General error: 1221 Incorrect usage of UNION and ORDER BY: SELECT cb.title AS title, cb.changed_date AS changed_date FROM {a_blog} cb ORDER BY changed_date DESC UNION ALL SELECT cg.title AS title, cg.changed_date AS changed_date FROM {a_gallery} cg

johnnydarkko’s picture

Version: 8.x-dev » 7.x-dev
StatusFileSize
new1.49 KB

#7 worked for me; the patch applied the parenthesis around the queries as expected.
#11 didn't apply the parenthesis correctly and just applies an update to a test file.

But then I came across an issue when I tried to UNION another query that was already UNION'ed:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') UNION ( SELECT ' at line 9

Sorry, I don't have the same insight as @David_Rothstein but I created a new patch variant of @cbergmann's solution from #7. Still having the same issue with LIMIT--it only applies the LIMIT to the first query. This one's over my head personally, so hopefully this patch leads to the correct solution.

johnnydarkko’s picture

StatusFileSize
new1.37 KB

Sorry, the patch from #17 was bad.
Rerolled the patch here.

hass’s picture

Version: 7.x-dev » 8.x-dev
fuerst’s picture

FYI: Workaround is to put the UNIONed queries into a subquery like this:

SELECT column1, column2 FROM (
  SELECT column1, column2 FROM table1
  UNION
  SELECT column3, column4 FROM table2
) subquery
ORDER BY column1, column2;

In db_select() speech:

$table1 = db_select('table1', 't1')
  ->fields('t1', array('column1', 'column2'));

$table2 = db_select('table2', 't2')
  ->fields('t2', array('column3', 'column4'));

$query = Database::getConnection()
  ->select($table1->union($table2))
  ->fields(NULL, array('column1', 'column2'))
  ->orderBy('column1')
  ->orderBy('column2');
hass’s picture

Ah... This is why have not seen it yet... :-))) good to know...

amcgowanca’s picture

Issue summary: View changes

We've used the patches for D7 that have been submitted on a particular client project and it has resolved the issues that we ran into with unionizing two queries from different views. Thanks for submitting these patches, it'd be great to see these patches get put into core when ready.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs re-roll, +Needs tests

This would need to be rerolled against Drupal\Core\Database\Query\Select. It would also be great to have a simple union query test so this doesn't regress.

jhedstrom’s picture

Issue tags: -Needs re-roll +Needs reroll
David_Rothstein’s picture

Issue tags: -Needs reroll +Needs re-roll

The latest Drupal 8 patch (#12) already has a test, and the patch itself still applies fine to 8.0.x - however the test needs a reroll.

I guess we need some reviewers to compare the two approaches and help decide between them.

David_Rothstein’s picture

Issue tags: -Needs re-roll +Needs reroll

Oops.

rpayanm’s picture

Status: Needs work » Needs review
StatusFileSize
new2.74 KB

reroll from #12. Please review.

jhedstrom’s picture

Issue tags: -Needs reroll, -Needs tests
Micha1111’s picture

Is this ready but not committed ?

jhedstrom’s picture

Issue summary: View changes

I think this is RTBC. The test has been uploaded separately to illustrate the current failure. I've updated the issue summary with a beta phase evaluation.

jhedstrom queued 27: 1145076-27.patch for re-testing.

jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community

Still green. Moving to RTBC.

jhedstrom’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I tested sqlite and postgres - both pass. I wonder what happens if both queries have an order by?

star-szr’s picture

@alexpott it sounds like you're suggesting more test coverage, is that right?

ann b’s picture

I was hoping to create a similar query to the one below:

(select node.title as node_title, node.created as node_created
from table_of_nodes_1
order by node_created desc
limit 1 offset 0)
union
(select node.title as node_title, node.created as node_created 
from table_of_nodes_2
order by node_created desc
limit 1 offset 0)
order by node_created desc

I agree with comment #13. Drupal wrapping the select statements in parentheses as well as allowing optional order by clauses within each select statement and outside of the whole statement would be helpful. But I also understand that drupal can't support all possible sql statements. One solution is very well-documented here. I just created a sql view and made it available to the Views Module.

I'm also on Drupal 7 so I am unable to help test patch from comment #27.

q0rban’s picture

Title: UNION queries don't support ORDER BY claues » UNION queries don't support ORDER BY clauses
q0rban’s picture

The patch in #11 for Drupal 7 does not work in our testing of a UNIONed query with an order by. The patch in #7 does. The union being performed is on a View query, using hook_view_pre_execute().

somersoft’s picture

StatusFileSize
new2.8 KB

Perhaps the second parameter to SelectQuery::union() could be either a string, as it is currently, or an array with associative keys for 'type', and new optional ones for 'order' and 'range'? With the '(' and ')' surrounding the query string, this allows the call to fully define the union SQL independently of the rest of the query. Please note that

  1. only the 'order' and 'range' information for the last union call are used.
  2. for 'order', the associative key is the field name and value is the direction. This is the same after using SelectQuery::orderBy() to the query.
  3. for 'range', it has two associative parameters 'length' and optionally 'start'. These are same as when using SelectQuery::range().

I do appreciate that

  1. Adding the '(' and ')' will possibly break some sites where the queries are specially written to work without them.
  2. The suggested fix is not completely OOP and UNION handling is still a little odd.

I have attached a patch file for Drupal 7.42. This patch also is used in hook_views_pre_execute() in the module views_extra_handlers to combine the results from two or more views. Example of use in a patched views_extra_handlers_views_pre_execute().

/**
 * Implements hook_views_pre_execute().
 */
function views_extra_handlers_views_pre_execute(view &$view) {
  if(isset($view->field) && !empty($view->field)){
    foreach($view->field as $field_name => $field_obj){
      $handler = $field_obj->definition['handler'];
      if($handler == "views_extra_handlers_handler_field_query_alter"){
        if(!empty($field_obj->options['veh_union']['veh_union_view_name'])){
          $query1 = &$view->build_info['query'];

          $union_view_id = $field_obj->options['veh_union']['veh_union_view_name'];
          $union_view_display_id = $field_obj->options['veh_union']['veh_union_view_display'];
          $view2 = views_get_view($union_view_id);
          $view2->set_display($union_view_display_id);
          $view2->pre_execute($view->args);
          $view2->execute();
          $query2 = $view2->build_info['query'];

          // Verify that queries are very similar.
          //dpq($query1);
          //dpq($query2);
          $union_orderby = $field_obj->options['veh_union']['veh_union_orderby'];
          if (isset($union_orderby)) {
            $union_orderby_direction = $field_obj->options['veh_union']['veh_union_orderby_direction'];
            $type = array(
              'type' => 'UNION',
              'order' => array(
                $union_orderby => (isset($union_orderby_direction) ? $union_orderby_direction : 'ASC'),
              ),
            );
            $query1 = $query1->union($query2, $type);
          }
          else {
            // Matrimony.
            $query1 = $query1->union($query2, 'UNION');
          }
        }
      }
    }
  }
}

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

daffie’s picture

Status: Needs work » Needs review
StatusFileSize
new1.51 KB
new2.76 KB

alexpott said in comment #34:

I wonder what happens if both queries have an order by?

Lets look at the three supported databases. The manual from SQLite is pretty clear:

As the components of a compound SELECT must be simple SELECT statements, they may not contain ORDER BY or LIMIT clauses. ORDER BY and LIMIT clauses may only occur at the end of the entire compound SELECT, and then only if the final element of the compound is not a VALUES clause.

SQLite allows only to add ORDER BY at the end and is applied afer all UNION statements.

For PostgreSQL allows to add ORDER BY to an individual select statement if it enclosed in parentheses.

The manual of MySQL is stating:

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.

So for MySQL there is no use case because it is ignoring ORDER BY for individual select statements.

To conclude: lets not add ORDER BY clauses to individual select statements because our three supported databases do not like it, MySQL is ignoring it and SQLite has outlawed it completely.

I had to do a reroll because the SelectTest has been moved. I did not make any code changes.

The last submitted patch, 41: 1145076-41-test-only.patch, failed testing.

daffie’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

The test failures for the patch with the tests and the fix are all not related to the patch. So I will ignore them.

I made no code changes, except for the fact that the SelectTest file location has been changed.
In comment #41 have I explained why the the problem raised by alexpott is not relevant.
This issue is a bug and as such can be applaid to 8.1.x.
The complete patch looks good to me and for me it is RTBC.
It was already RTBC for user jhedstrom in comment #32.

Removed the Beta phase evaluation from the IS.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 41: 1145076-41.patch, failed testing.

The last submitted patch, 41: 1145076-41-test-only.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

Back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Given we've moved UNION above limit can we add test coverage for limit with a union.

daffie’s picture

Status: Needs work » Needs review
StatusFileSize
new1.5 KB
new2.53 KB
new3.78 KB

Added the requested test for a union with a order and a limit. The test is the same as the other added test with a limit added to it.

The last submitted patch, 48: 1145076-48-test-only.patch, failed testing.

jhedstrom’s picture

The new test looks good. One tiny nit, and then I think this is RTBC.

+++ b/core/tests/Drupal/KernelTests/Core/Database/SelectTest.php
@@ -352,6 +352,35 @@ function testUnionOrder() {
+   * Tests that we can UNION multiple Select queries together and set the ORDER with and a LIMIT.

Line exceeds 80 characters, so needs to be shorter.

daffie’s picture

StatusFileSize
new638 bytes
new2.54 KB
new3.79 KB

Ooops

The last submitted patch, 51: 1145076-51-tests-only.patch, failed testing.

jhedstrom’s picture

Sorry @daffie I should have been more clear--it's actually this standard I was referring to:

All summaries (first lines of docblocks) must be under 80 characters...

Meaning this first line should be a single sentence that is under 80 characters. Further detail can be provided after that if needed.

I couldn't find a phpcs standard for that though over in #2571965: [meta] Fix PHP coding standards in core, stage 1, so I don't know if it's enforced or not, but would be good to fix here regardless.

jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/tests/Drupal/KernelTests/Core/Database/SelectTest.php
@@ -352,7 +352,8 @@ function testUnionOrder() {
+   * Tests that we can UNION multiple Select queries together and set the ORDER
+   * with and a LIMIT.

Something like 'Tests UNION of multiple SELECT queries together with ORDER and LIMIT'. I'm going to mark this RTBC as that could be fixed on commit (or if @daffie gets back to this first).

daffie’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new2.51 KB
new3.76 KB
new620 bytes

@jhedstrom: Now I get it. Sorry, for being a bit slow. :) And thank you for your help!

The last submitted patch, 55: 1145076-55-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 55: 1145076-55.patch, failed testing.

The last submitted patch, 55: 1145076-55-test-only.patch, failed testing.

daffie’s picture

Status: Needs work » Needs review
jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC. Thanks @daffie!

alexpott’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

Committed and pushed 6d3fd71023d38616f4fdf7ae094a46b3ee0f7635 to 8.2.x and 62598ec to 8.1.x. Thanks!

Can someone open a new issue for the D7 backport and mark this issue as fixed?

  • alexpott committed 6d3fd71 on 8.2.x
    Issue #1145076 by daffie, David_Rothstein, johnnydarkko, cbergmann,...

  • alexpott committed 62598ec on 8.1.x
    Issue #1145076 by daffie, David_Rothstein, johnnydarkko, cbergmann,...
daffie’s picture

@alexpott: Why do you want to have this issue closed and opening a new one for D7. It is must more easy just to set this one back to D7. They get all the info about the problem direct from this issue.

alexpott’s picture

@daffie - this is the new policy. It exists for lots and lots of reasons - see https://www.drupal.org/core/backport-policy and #1145076: UNION queries don't support ORDER BY clauses

daffie’s picture

Status: Patch (to be ported) » Fixed
daffie’s picture

@alexpott: Thank you for the explanation.

hass’s picture

When has this policy changed? In past we always moved cases to D7 after it was fixed in D8. This way we are loosing track of the issue that moved from D7 to D8 to get fixed there first.

Libero82’s picture

Libero82’s picture

try this for union in ORDER BY

$query = db_select('node', 'n')
->fields('n', array('nid', 'title'))
->condition('nid', 92);
$query2 = db_select('comment', 'c')
->fields('c', array('cid', 'subject'))
->condition('cid', 5);
$query2->orderBy('title', 'DESC'); // this
$query->union($query2);

daffie’s picture

$query2 = db_select('comment', 'c')
->fields('c', array('cid', 'subject'))
->condition('cid', 5);
$query2->orderBy('title', 'DESC');

This will not work because the comment table does not have a column named "title".
Second the union operator return an unordered list. Whatever you put into an union operator you get an unordered list. You can however order the unordered list after the union operator. So moving $query2->orderBy('title', 'DESC'); to the bottom and changing it to $query instead of $query2 will work much better.

  • alexpott committed 6d3fd71 on 8.3.x
    Issue #1145076 by daffie, David_Rothstein, johnnydarkko, cbergmann,...

  • alexpott committed 6d3fd71 on 8.3.x
    Issue #1145076 by daffie, David_Rothstein, johnnydarkko, cbergmann,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.