70

I'm writing a function in PL/pgSQL, and I'm looking for the simplest way to check if a row exists.
Right now I'm SELECTing an integer into a boolean, which doesn't really work. I'm not experienced with PL/pgSQL enough yet to know the best way of doing this.

Here's part of my function:

DECLARE person_exists boolean;
BEGIN

person_exists := FALSE;

SELECT "person_id" INTO person_exists
  FROM "people" p
WHERE p.person_id = my_person_id
LIMIT 1;

IF person_exists THEN
  -- Do something
END IF;

END; $$ LANGUAGE plpgsql;

Update - I'm doing something like this for now:

DECLARE person_exists integer;
BEGIN

person_exists := 0;

SELECT count("person_id") INTO person_exists
  FROM "people" p
WHERE p.person_id = my_person_id
LIMIT 1;

IF person_exists < 1 THEN
  -- Do something
END IF;
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
nnyby
  • 4,748
  • 10
  • 49
  • 105

2 Answers2

194

Simpler, shorter, faster: EXISTS.

IF EXISTS (SELECT FROM people p WHERE p.person_id = my_person_id) THEN
  -- do something
END IF;

The query planner can stop at the first row found - as opposed to count(), which scans all (qualifying) rows regardless. Makes a big difference with big tables. The difference is small for a condition on a unique column: only one row qualifies and there is an index to look it up quickly.

Only the existence of at least one qualifying row matters. The SELECT list can be empty - in fact, that's shortest and cheapest. (Some other RDBMS don't allow an empty SELECT list on principal.)

Improved with @a_horse_with_no_name's comments.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Good point! (Although person_id is probably the primary key, so it would only "scan" a single table using an index lookup). –  Aug 09 '12 at 22:20
  • @a_horse_with_no_name: As of now (Postgres 9.1) `count()` *always* triggers a sequential scan. Try `EXPLAIN ANALYZE SELECT count(id) from tbl` with any table. More on [slow counting in the Postgres Wiki](http://wiki.postgresql.org/wiki/Slow_Counting). The new index-only scan of Postgres 9.2 are supposed to improve things, because it can (provided some conditions) utilize an index scan for `count(id)` - have yet to try and see it for myself, though .. – Erwin Brandstetter Aug 09 '12 at 22:27
  • 3
    A `count(*)` *with a condition* (especially not on the PK column) will **not** trigger a sequential scan. –  Aug 09 '12 at 22:32
  • 1
    @a_horse_with_no_name: You are right of course! I was thinking of a plain count - as you can see from my example in the comment. I improved my answer with your input, thanks. – Erwin Brandstetter Aug 09 '12 at 22:40
  • 1
    If you want to use this outside of a function the syntax for how to do so is here: http://stackoverflow.com/a/20957691/908677 – Elijah Lofgren Dec 14 '15 at 19:49
  • Is there a benefit of `IF EXISTS` over `SELECT ...; IF FOUND ...`? [example for `if found`](https://wiki.postgresql.org/wiki/A_Brief_Real-world_Trigger_Example) – Eugen Konkov Feb 18 '19 at 18:34
  • 2
    @EugenKonkov: I am leading with that: `Simpler, shorter, faster.` – Erwin Brandstetter Feb 20 '19 at 02:40
  • @ErwinBrandstetter: I'm using PostgreSQL 11.5 and I think we should have a brace to cover EXISTS. Without a brace, the code still run but we can't get `True`. So, the statement should be `IF (EXISTS (SELECT FROM people WHERE person_id = my_person_id)) THEN ...` – Thinh Phan Aug 31 '19 at 13:01
  • @ThinhPhan: This must be a misunderstanding. Outer parentheses are *not* required. See: *db<>fiddle [here](https://dbfiddle.uk/?rdbms=postgres_11&fiddle=b5759f14378bd04c2bd6dc40424ce2b6)*. – Erwin Brandstetter Aug 31 '19 at 23:02
  • @ErwinBrandstetter Sorry my bad. You are right. It's working now. Thank you so much. – Thinh Phan Sep 01 '19 at 23:51
  • @ErwinBrandstetter Would the following be possible? `DECLARE temperature integer = 0; BEGIN IF EXISTS(SELECT temperature INTO temperature FROM someTable WHERE x=y) THEN DO NOTHING END IF; ` Would that still fill temperature? If so could you add it to your answer? – Splitframe Oct 01 '21 at 12:18
  • @Splitframe: No, if you `SELECT INTO`, then just check `FOUND`. (It's not allowed in an `EXISTS` expression.) Also, `SELECT INTO` is only for a single qualifying row, `EXISTS` is for any number of qualifying rows. Consider a new question if anything is unclear ... – Erwin Brandstetter Oct 01 '21 at 12:26
  • Thank you for your answer! I was under the wrong impression that an empty `SELECT INTO` would trigger an exception. Probably due to some other error I had in the function. But it just silently fails (or rather successfully returns 0 rows) and, in the above example, temperature just stays 0 which is exactly what I wanted anyway. – Splitframe Oct 04 '21 at 11:04
6

Use count(*)

declare 
   cnt integer;
begin
  SELECT count(*) INTO cnt
  FROM people
  WHERE person_id = my_person_id;

IF cnt > 0 THEN
  -- Do something
END IF;

Edit (for the downvoter who didn't read the statement and others who might be doing something similar)

The solution is only effective because there is a where clause on a column (and the name of the column suggests that its the primary key - so the where clause is highly effective)

Because of that where clause there is no need to use a LIMIT or something else to test the presence of a row that is identified by its primary key. It is an effective way to test this.

  • 4
    Do not use COUNT for this purpose - it is performance issue - or you have to use derived table SELECT COUNT(*) FROM (SELECT * FROM people LIMIT 1) x – Pavel Stehule Aug 10 '12 at 04:23
  • 2
    @PavelStehule: even when there is a `where` condition on the **primary key**? I can't imagine how that would possibly be much slower than your statement. The execution plan is nearly identical for both solutions. –  Aug 10 '12 at 06:15
  • @a_horse_with_no_name when it filter to PK, then it is ok on 99%. In this case PL/pgSQL should to evaluate 2 SELECTs instead one. But this pattern is just risky. Some people don't do verification so filter is to PK – Pavel Stehule Aug 10 '12 at 06:19
  • @PavelStehule: I added an explanation for this. I compared the plans for Erwin's and mine solution and there is no (real) difference. Due to the `where` on an indexed column this *is* efficient. –  Aug 10 '12 at 06:25
  • a plan should be same - but PL/pgSQL is just glue for SQL - with count, plpgsql evaluate two SELECTs - 1) SELECT INTO var, 2) SELECT var > 0 – Pavel Stehule Aug 10 '12 at 06:29
  • @PavelStehule: are you saying that the `if cnt > 0` is actually running a select statement in the background? And if that is the case wouldn't that be the case with *any* solution - including the "inline" select from Erwin? –  Aug 10 '12 at 06:40
  • 4
    @a_horse_with_no_name, exactly - it is "trivial" SELECT (about 10x faster than normal SELECT), but it is SELECT still. if you like to see real face of plpgsql code, use #option dump -- see first code list from article http://postgres.cz/wiki/PL/pgSQL_efektivn%C4%9B (sorry, article is in czech, but samples are in English) - http://translate.google.com/translate?sl=auto&tl=en&u=http%3A%2F%2Fpostgres.cz%2Fwiki%2FPL%2FpgSQL_efektivn%25C4%259B – Pavel Stehule Aug 10 '12 at 07:14