PostgreSQL:Select
lastmod 2026-02-26created 2021-03-0213 edits
Operator
| Operator | Description |
| | Equal |
| | Greater than |
| | Less than |
| | Greater than or equal |
| | Less than or equal |
| | Not equal |
| | Logical operator AND |
| | Logical operator OR |
| | Return true if a value matches any value in a list |
| | Return true if a value is between a range of values |
| | Return true if a value matches a pattern |
| | Return true if a value is NULL |
| | Negate the result of other operators |
Timestamp
Join
See SQL:Join for details.
LIMIT
OFFSET
LIMIT and OFFSET
Left join
Left join example:
SELECT
u.uid AS uid,
pm.project_uid AS project_uid,
gm.group_uid AS group_uid,
CASE
WHEN pm.permission_uid IS NOT NULL THEN pm.permission_uid
ELSE gm.permission_uid
END AS permission_uid
FROM
recc_user u
LEFT JOIN recc_project_member pm ON pm.user_uid=u.uid
LEFT JOIN recc_project p ON p.uid=pm.project_uid
LEFT JOIN recc_group_member gm ON gm.user_uid=u.uid
ORDER BY u.uid;
For reference, the CASE statement above that checks for NULL can be replaced with the COALESCE function.
NOT EXISTS
Often fastest in Postgres.
SELECT ip
FROM login_log l
WHERE NOT EXISTS (
SELECT -- SELECT list mostly irrelevant; can just be empty in Postgres
FROM ip_location
WHERE ip = l.ip
);
LEFT JOIN / IS NULL
Sometimes this is fastest. Often shortest. Often results in the same query plan as NOT EXISTS.
SELECT l.ip
FROM login_log l
LEFT JOIN ip_location i USING (ip) -- short for: ON i.ip = l.ip
WHERE i.ip IS NULL;
EXCEPT / EXCEPT ALL
Short. Not as easily integrated in more complex queries.
SELECT ip
FROM login_log
EXCEPT ALL -- "ALL" keeps duplicates and makes it faster
SELECT ip
FROM ip_location;
If you don't want to remove duplicates, use EXCEPT ALL.
NOT IN
Only good without NULL values or if you know to handle NULL properly. I would not use it for this purpose.
:Template:WARNING
SELECT ip
FROM login_log
WHERE ip NOT IN (
SELECT DISTINCT ip -- DISTINCT is optional
FROM ip_location
);
CASE ~~ WHEN ~~ THEN ~~ ELSE ~~ END
Syntax for using conditional expressions
Temporary table JOIN
select * from
(
<your first query here>
) tbl1
join (
<your second query here>
) tbl2
on tbl1.c_project_Id = tbl2.c_project_Id
and tbl1.c_projectphase_Id = tbl2.c_projectphase_Id -- you might add or
and tbl1.c_projecttask_Id = tbl2.c_projecttask_Id -- remove join criteria
and tbl1.m_product_Id = tbl2.m_product_Id -- here
Adding all columns of a specific table to the Select result
Use it like table.*:
FOR UPDATE
SELECT ... FOR UPDATE acquires a row-level lock on the selected rows. Other transactions trying to lock the same rows wait until the first transaction commits and then re-read the latest data.
Under PostgreSQL's default isolation level, READ COMMITTED, each transaction can see only data committed by other transactions. As a result, a TOCTOU (Time-of-Check to Time-of-Use) problem can occur when concurrent transactions check the same condition, and FOR UPDATE solves it.
Problem: Race Condition
When a constraint such as "the last one cannot be deleted" is implemented with a trigger, concurrent transactions cannot see each other's uncommitted changes, so the constraint can be bypassed.
Reproduction scenario
When owner A and owner B are the only 2 owners of the same organization and both try to leave at the same time:
| Time | Tx A (owner A leaves) | Tx B (owner B leaves) |
| t1 | | |
| t2 | | |
| t3 | COMMIT | COMMIT |
| Result | A deleted + B deleted → 0 owners → organization orphaned | |
At t2, Tx A's DELETE has not been committed yet, so Tx B concludes that A still exists.
Solution: apply FOR UPDATE
| Time | Tx A | Tx B |
| t1 | | |
| t2 | | |
| t3 | COMMIT (A deleted, lock released) | acquires lock, re-reads latest data |
| Result | A deleted + B deletion blocked → 1 owner kept | |
Syntax
-- 기본: SELECT한 모든 row에 lock
SELECT * FROM table_name
WHERE condition
FOR UPDATE;
-- OF: JOIN 시 특정 테이블의 row만 lock
SELECT * FROM table_a a
JOIN table_b b ON a.id = b.a_id
WHERE condition
FOR UPDATE OF a;
-- NOWAIT: lock 대기 없이 즉시 실패
SELECT * FROM table_name
WHERE condition
FOR UPDATE NOWAIT;
-- SKIP LOCKED: lock된 row를 건너뛰고 나머지만 반환 (큐 패턴)
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;