2

I have found many Questions and Answers about a SELECT excluding rows with a value "NOT IN" a sub-query (such as this). But how to exclude a list of values rather than a sub-query?

I want to search for rows whose timestamp is within a range but exclude some specific date-times. In English, that would be:

Select all the ORDER rows recorded between noon and 2 PM today except for the ones of these times: Today 12:34, Today 12:55, and Today 13:05.

SQL might be something like:

SELECT * 
FROM order_
WHERE recorded_ >= ?
AND recorded_ < ?
AND recorded_ NOT IN ( list of date-times… )
;

So two parts to this Question:

  • How to write the SQL to exclude rows having any of a list of values?
  • How to set an arbitrary number of arguments to a PreparedStatement in JDBC?
    (the arbitrary number being the count of the list of values to be excluded)
Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • your SQL will work to exclude values as described. http://stackoverflow.com/questions/178479/preparedstatement-in-clause-alternatives answers the NOT IN list. – Norbert Jul 09 '15 at 01:43
  • You want `recorded_ <> ALL( ? )`, with a param of type `timestamp[]`. Not sure how to feed this in via JDBC, though. – Nick Barnes Jul 09 '15 at 02:05
  • @NickBarnes: `NOT IN` is effectively the same as `<> ALL()`: http://stackoverflow.com/a/31192557/939860. The problem with both: they fail if NULL is involved on either side of the expression. The expression evaluates to NULL; but only TRUE passes a `WHERE` condition: http://stackoverflow.com/a/19528722/939860 – Erwin Brandstetter Jul 09 '15 at 03:36
  • As explained in the comments, `NOT IN (...)` is equivalent to `<> ALL (ARRAY[...])`. You can create an array in JDBC with `createArrayOf`, then pass that as a parameter. – Craig Ringer Jul 09 '15 at 06:33

2 Answers2

3

Pass array

A fast and NULL-safe alternative would be a LEFT JOIN to an unnested array:

SELECT o.*
FROM   order_ o
LEFT  JOIN unnest(?::timestamp[]) x(recorded_) USING (recorded_)
WHERE  o.recorded_ >= ?
AND    o.recorded_ <  ?
AND   x.recorded_ IS NULL;

This way you can prepare a single statement and pass any number of timestamps as array.

The explicit cast ::timestamp[] is only necessary if you cannot type your parameters (like you can in prepared statements). The array is passed as single text (or timestamp[]) literal:

'{2015-07-09 12:34, 2015-07-09 12:55, 2015-07-09 13:05}', ...

Or put CURRENT_DATE into the query and pass times to add like outlined by @drake . More about adding a time / interval to a date:

Pass individual values

You could also use a VALUES expression - or any other method to create an ad-hoc table of values.

SELECT o.*
FROM   order_ o
LEFT  JOIN (VALUES (?::timestamp), (?), (?) ) x(recorded_)
                                         USING (recorded_)
WHERE  o.recorded_ >= ?
AND    o.recorded_ <  ?
AND    x.recorded_ IS NULL;

And pass:

'2015-07-09 12:34', '2015-07-09 12:55', '2015-07-09 13:05', ...

This way you can only pass a predetermined number of timestamps.

Asides

For up to 100 parameters (or your setting of max_function_args), you could use a server-side function with a VARIADIC parameter:

I know that you are aware of timestamp characteristics, but for the general public: equality matches can be tricky for timestamps, since those can have up to 6 fractional digits for seconds and you need to match exactly.

Related

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
1
SELECT * 
FROM order_
WHERE recorded_ BETWEEN (CURRENT_DATE + time '12:00' AND CURRENT_DATE + time '14:00')
      AND recorded_ NOT IN (CURRENT_DATE + time '12:34', 
                            CURRENT_DATE + time '12:55',
                            CURRENT_DATE + time '13:05')
 ;
drake
  • 1,012
  • 2
  • 9
  • 17
  • I'm using Java and JDBC. Can I make the list in parens simply a list of java.sql.Timestamp instances? Like this: `NOT IN ( ? , ? , ? )` with `preparedStatement.setTimestamp( 3 , timestampObject1 ); ", repeating for 4, 5 and so on? – Basil Bourque Jul 09 '15 at 01:57