Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I force filter evaluation order in SQL(Postgres)?

Tags:

sql

postgresql

I have a table that, simplified, looks roughly like this:

id | type | header    | body
===========================================
 1 | A    | {type: A} | {content: "Hi"}
 2 | A    | {type: A} | {content: "Hello"}
 3 | B    | {type: B} | ["Hi","Hello"]

The following query gives me an error:

> select * from Table where header->>'type'='A' and body->>'content' like 'H%'
ERROR:  cannot extract field from a non-object

Which is fair enough, but so does this query:

> select * from (select * from Table where header->>'type'='A') where body->>'content' like 'H%'

Whereas these do not:

> select * from Table where type='A' and body->>'content' like 'H%'
> select * from Table where header->>'type'='A' and body->>'content'='Hello'

I have workaround for this specific case (where the 'like' predicate is improperly being given precedence), but what concerns me is that I apparently can't even rely on parentheses to control evaluation order, even when, such as in this case, it changes what constraints apply to the data. Is there any general way to do this?

like image 331
T.R. Avatar asked Aug 05 '14 00:08

T.R.


People also ask

Does the order of WHERE clause matter in PostgreSQL?

No, that order doesn't matter (or at least: shouldn't matter). Any decent query optimizer will look at all the parts of the WHERE clause and figure out the most efficient way to satisfy that query.

How do I sort in descending order in PostgreSQL?

When sorting your result set in descending order, you use the DESC attribute in your ORDER BY clause as follows: SELECT last_name, first_name, city FROM contacts WHERE first_name = 'Joe' ORDER BY last_name DESC; This PostgreSQL ORDER BY example would return all records sorted by the last_name field in descending order.

What is default order in PostgreSQL?

The ORDER BY clause in PostgreSQL is used together with the SELECT statement to sort table data. The table data can either be sorted in ascending or descending order. By default, the data is sorted in ascending order.


2 Answers

You should be able to force evaluation order with case:

select *
from Table
where (case when header->>'type'='A'
            then (case when body->>'content' like 'H%' then 1 end)
       end) = 1;

This is about the only time that I recommend putting case statements in the where clause.

You could also guarantee the order using a CTE:

with t as (
      select t.*
      from table t
      where header->>'type'='A'
     )
select t.*
from t
where body->>'content' like 'H%';

However, this incurs the extra overhead of materializing the intermediate result.

like image 191
Gordon Linoff Avatar answered Oct 09 '22 04:10

Gordon Linoff


(This is supplementary info; Gordon's answer should be marked correct).

In SQL, the DB is free to execute predicates in any order. There is no short circuiting. Parenthesised groups override default operator precedence to control which operators bind to which operands, but do not force execution order.

A subquery doesn't guarantee anything about execution order. It may, and should where possible, be optimized out by flattening it into the outer query.

The issue you're having is the same as for handling divide by zero, and has the same solutions.

The correct solution is to force execution order with CASE as Gordon shows. You can use a CTE (WITH) or the OFFSET 0 hack, but both will have performance consequences.

like image 33
Craig Ringer Avatar answered Oct 09 '22 04:10

Craig Ringer