'union all in SQL (Postgres) mess the order

I have a query which is order by date , there is the query I have simplified it a bit but basically is :

select * from 
(select  start_date, to_char(end_date,'YYYY-mm-dd') as end_date from date_table
order by start_date ,end_date ) 
where start_date is null or end_date is null

It shows prefect order

but I add

union all
select start_date, 'single missing day' as end_date  from 
calendar_dates
where db_date>'2017-12-12' and db_date<'2018-05-13'     

Then the whole order messed up. Why is that happened? Union or union all should just append the dataset from first query with the second, right? It should not mess the order in the first query, right?

I know this query doesn't makes any sense, but I have simplified it to show the syntax.



Solution 1:[1]

You can't predict what would be the order outcome by just assuming that UNION ALL will append queries in the order you write them.

The query planner will execute your queries in whatever order it sees it fit. That's why you have the ORDER BY clause. Use it !

For example, if you want to force the order of the first query, then the second, do :

select * from 
(select  1, start_date, to_char(end_date,'YYYY-mm-dd') as end_date from date_table
order by start_date ,end_date ) 
where start_date is null or end_date is null
union all
select 2, start_date, 'single missing day' as end_date  from 
calendar_dates
where db_date>'2017-12-12' and db_date<'2018-05-13' 
ORDER BY 1

Solution 2:[2]

You are mistaken. This query:

select d.*
from (select start_date, to_char(end_date,'YYYY-mm-dd') as end_date
      from date_table
      order by start_date, end_date
     ) d
where start_date is null or end_date is null

does not "show perfect order". I might just happen to produce the ordering that you want, but that is a coincidence. The only way to get results in a particular order is to use ORDER BY in the outermost SELECT. Period.

So, if you want results in a particular order, then use order by:

select d.*
from ((select d.start_date, to_char(end_date, 'YYYY-mm-dd') as end_date, 1 as ord
       from date_table d
       where d.start_date is null or d.end_date is null
       order by start_date, end_date
      ) union all
      (select cd.start_date, 'single missing day' as end_date, 2 as ord
       from calendar_dates cd
       where cd.db_date > '2017-12-12' and cd.db_date < '2018-05-13' 
      )
     ) d
order by ord, start_date;

Solution 3:[3]

UNION or UNION ALL will mess up the order in the first SELECT. Therefore, we can make a trick that we will re-order these columns in the Outer Select as below:

SELECT * FROM 
( 
     select colA, colB
          From TableA
          -- ORDER BY colA, colB --   
     UNION ALL   
     select colC, colD
         FROM TableB
         ORDER BY colC, colD 
) tb 
ORDER BY colA, colB

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 olleo
Solution 2 Gordon Linoff
Solution 3 Duc Toan Pham