Skip to content
Back to skills

Postgres Hotel Partitioning

ASecurity

This skill should be used when the user asks to "write a query for a hotel-partitioned table", "review this SQL for partition safety", "add a hotel_id filter", "check partition pruning", "write a migration for a partitioned table", or writes/reviews Go or SQL code touching any PostgreSQL table partitioned by hotel_id. Provides patterns for keeping queries, joins, and migrations partition-safe.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 16, 2026
ai-agentsrustgosqldatabaseperformance

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 16, 2026

npx -y skills add shennawardana23/skillme --skill postgres-hotel-partitioning --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Postgres Hotel Partitioning?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Postgres Hotel Partitioning
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shennawardana23-postgres-hotel-partitioning/badge)](https://www.skillsdirectory.com/skills/shennawardana23-postgres-hotel-partitioning)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: postgres-hotel-partitioning
description: This skill should be used when the user asks to "write a query for a hotel-partitioned table", "review this SQL for partition safety", "add a hotel_id filter", "check partition pruning", "write a migration for a partitioned table", or writes/reviews Go or SQL code touching any PostgreSQL table partitioned by hotel_id. Provides patterns for keeping queries, joins, and migrations partition-safe.
metadata:
  version: "0.1.0"
---

# PostgreSQL Hotel-Partitioned Tables

Many tables in this organization's PostgreSQL databases are partitioned by
`hotel_id` (declarative `PARTITION BY LIST (hotel_id)`). PostgreSQL's planner
only prunes partitions it can prove are irrelevant from the query's `WHERE`
clause at plan time — a query missing an explicit `hotel_id` predicate scans
every partition, not just the one relevant to the request. This is the
single most common correctness-and-performance bug against these tables:
apply the checks below to every query, join, and migration.

## The core rule

Every query against a partitioned-by-`hotel_id` table must include
`hotel_id = $N` (or `hotel_id = ANY($N)` for a known, bounded set) directly in
its `WHERE` clause — not buried inside a subquery the planner can't push
down, not applied only in application code after fetching rows.

```go
// BAD: no hotel_id predicate — scans every hotel's partition to find status matches.
const q = `SELECT * FROM reservations WHERE status = $1`

// GOOD: hotel_id is explicit, sits alongside the other predicate.
const q = `SELECT * FROM reservations WHERE hotel_id = $1 AND status = $2`
```

Always bind `hotel_id` as a parameter (`$1`, `:hotel_id`), never interpolate
it or any other value into the query string with `fmt.Sprintf` — that is a
SQL-injection vector independent of the partitioning concern.

## Joins across partitioned tables

A join between two tables partitioned by `hotel_id` must filter `hotel_id` on
**both** sides (or rely on the same bound parameter for both), otherwise the
planner cannot prune either side:

```sql
-- GOOD: hotel_id constrained on both tables in the join.
SELECT r.id, g.name
FROM reservations r
JOIN guests g ON g.id = r.guest_id AND g.hotel_id = r.hotel_id
WHERE r.hotel_id = $1 AND r.status = $2;
```

## Go query helpers

Prefer a thin wrapper that makes `hotelID` a required, typed parameter so it
cannot be forgotten at the call site — push the constraint into the
function signature rather than trusting every call site to remember it:

```go
// ByHotelAndStatus is the only way to query reservations by status —
// hotelID is a required parameter, not an optional filter callers can skip.
func (s *Store) ByHotelAndStatus(ctx context.Context, hotelID int64, status string) ([]Reservation, error) {
    const q = `SELECT id, hotel_id, status, checkin, checkout
               FROM reservations
               WHERE hotel_id = $1 AND status = $2`
    var out []Reservation
    if err := s.db.SelectContext(ctx, &out, q, hotelID, status); err != nil {
        return nil, fmt.Errorf("query reservations for hotel %d: %w", hotelID, err)
    }
    return out, nil
}
```

## Migrations

Declare the parent table with `PARTITION BY LIST (hotel_id)` and create one
partition per hotel (or a bucketed range if the hotel count is large enough
that per-hotel partitions become unwieldy — confirm the actual hotel count
before choosing bucketing over one-partition-per-hotel):

```sql
CREATE TABLE stays (
    id         bigint GENERATED ALWAYS AS IDENTITY,
    hotel_id   integer NOT NULL,
    guest_id   bigint NOT NULL,
    checkin    date NOT NULL,
    checkout   date NOT NULL,
    PRIMARY KEY (hotel_id, id)
) PARTITION BY LIST (hotel_id);

CREATE TABLE stays_101 PARTITION OF stays FOR VALUES IN (101);
CREATE TABLE stays_102 PARTITION OF stays FOR VALUES IN (102);
```

Note `hotel_id` must be part of every unique index and the primary key on a
list-partitioned table — PostgreSQL requires the partition key be included
in any unique constraint.

## Verifying partition pruning

Confirm pruning actually happens rather than assuming it from the query
shape — run `EXPLAIN (ANALYZE)` and check the plan lists only the expected
partition(s), not `Append` over every child:

```sql
EXPLAIN (ANALYZE) SELECT * FROM reservations WHERE hotel_id = 101 AND status = 'confirmed';
-- Expect: Index Scan on reservations_101 ...
-- Not:    Append -> Seq Scan on reservations_101 -> Seq Scan on reservations_102 -> ...
```

## Additional resources

For a walkthrough of the missing-filter bug pattern and how it shows up in
code review, consult `references/partitioning.md`.

Files in this skill

  • SKILL.md4.6 KB
  • evals/evals.json3.1 KB
  • references/partitioning.md2.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.