0

Given an array literal as a parameter, and a table expression as (pseudo-code-like):

SELECT y.id
  FROM (VALUES (4)
             , (1)
             , (2)
             , (6)) y (id)
WHERE y.id = < if ANY('{4}') then 4 else y.id >
;

Is there a WHERE clause that can give y.id 4 if there's a match in the array, or every y.id if not, resulting in e.g.,

id
--
4

or

id
--
4
1
2
6

If the array literal did not contain 4.

Need to be compatible with Postgres version 12.

3
  • 2
    Sounds like you want WHERE y.id = ANY('{4}') OR NOT EXISTS (SELECT * FROM y WHERE y.id = ANY('{4}'))? Commented Sep 22, 2022 at 23:31
  • Is your actual source for the set a table or a freehand VALUES expression like in the example? (Or another array)? Can there be NULL values involved? How to deal with those? Show the exact table definition and which columns you want to return. Rough cardinalities of table and array? Indexes? It matters. Commented Sep 23, 2022 at 0:10
  • Thanks, the actual source is a table so these are good to consider. The suggestion from @Bergi applies well to that also. Commented Sep 23, 2022 at 15:51

1 Answer 1

2

To implement Bergi's clever suggestion, you need to wrap the set in a CTE to reference it twice. And provide the array twice:

WITH y(id) AS (
   VALUES
     (4)
   , (1)
   , (2)
   , (6)
   )
SELECT id
FROM   y
WHERE  y.id = ANY('{4}')  -- provide array here
   OR  NOT EXISTS (SELECT * FROM y WHERE y.id = ANY('{4}'));  -- and here

Or this variant with another CTE, providing the array just once:

WITH y(id) AS (
   VALUES
     (4)
   , (1)
   , (2)
   , (6)
   )
, match AS (
   SELECT * FROM y
   WHERE  id = ANY('{4}')  -- array here
   )
SELECT * FROM match
UNION ALL
SELECT * FROM y
WHERE  NOT EXISTS (SELECT FROM match);

If your source of rows is an actual table either gets simpler.

If performance is crucial, consider a PL/pgSQL function:

CREATE OR REPLACE FUNCTION func(_arr int[])
  RETURNS TABLE (id int)
  LANGUAGE plpgsql STABLE AS
$func$
BEGIN
   RETURN QUERY
   SELECT y.id
   FROM   y
   WHERE  y.id = ANY(_arr);
   
   IF NOT FOUND THEN
      RETURN QUERY
      SELECT y.id
      FROM   y;
   END IF;
END
$func$;

PL/pgSQL builds up the return-set by simply appending to it. The special variable FOUND shows if the last SQL command returned any rows. See:

So you don't need another filter condition, and the query plan(s) can be simpler. Should be faster. Especially if we find matches most of the time and return after the first query. (PL/pgSQL works with prepared statements internally, which may or may not be beneficial on top ...)

If you return big sets, the function might be slower again, because of the way it builds up the set before returning the complete result ...

Sign up to request clarification or add additional context in comments.

2 Comments

Oh I didn't know you could SELECT FROM without *, is there a difference? (I only remember not using SELECT 1 in postgres)
@Bergi: Oh, both are very different. But it doesn't matter for an EXISTS expression which only cares for the existence of rows. An empty SELECT list is shortest & cheapest there. See: stackoverflow.com/a/19364694/939860 (Other RDBMS may not be allow an empty SELECT list.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.