<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PostgreSQL Archives | Abdul Wahab Junaid</title>
	<atom:link href="https://awjunaid.com/category/postgresql/feed/" rel="self" type="application/rss+xml" />
	<link>https://awjunaid.com/category/postgresql/</link>
	<description>Offensive Security Researcher &#38; Quantum Cryptography Analyst</description>
	<lastBuildDate>Sat, 15 Aug 2026 04:32:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://i0.wp.com/awjunaid.com/wp-content/uploads/2023/06/cropped-1668274976669.jpeg?fit=32%2C32&#038;ssl=1</url>
	<title>PostgreSQL Archives | Abdul Wahab Junaid</title>
	<link>https://awjunaid.com/category/postgresql/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">220030102</site>	<item>
		<title>How to Use the NOTIFY Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-notify-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-notify-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:49:31 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7463</guid>

					<description><![CDATA[<p>If you&#8217;ve ever built an application that needs to react instantly when something changes in your database —&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-notify-command-in-postgresql/">How to Use the NOTIFY Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">If you&#8217;ve ever built an application that needs to react instantly when something changes in your database — a new order comes in, a chat message gets sent, a job finishes processing — you&#8217;ve probably reached for polling. You know the drill: hit the database every few seconds, check if anything&#8217;s new, repeat forever. It works, but it&#8217;s wasteful, laggy, and honestly kind of embarrassing once you learn there&#8217;s a better way.</p>



<p class="wp-block-paragraph">That better way, at least inside PostgreSQL, is the <code>NOTIFY</code> command. I want to walk you through exactly what it does, how to use it correctly, and where it fits into a real application, because once you understand it, you&#8217;ll probably find a dozen places in your own projects where it can replace clunky polling logic.</p>



<h2 class="wp-block-heading">What Is NOTIFY in PostgreSQL?</h2>



<p class="wp-block-paragraph"><code>NOTIFY</code> is a PostgreSQL command that sends a lightweight notification, along with an optional payload of text, to a named channel. Any other database session that has issued a <code>LISTEN</code> command on that same channel will receive the notification, almost instantly, without needing to ask for it.</p>



<p class="wp-block-paragraph">Think of it like a radio broadcast. <code>NOTIFY</code> is the transmitter, the channel name is the frequency, and any client tuned in with <code>LISTEN</code> picks up the signal. This is part of PostgreSQL&#8217;s built-in asynchronous messaging system, and it&#8217;s been around for a long time — it&#8217;s stable, well-tested, and doesn&#8217;t require any extensions or plugins to use.</p>



<p class="wp-block-paragraph">The key thing to understand is that <code>NOTIFY</code> doesn&#8217;t deliver data directly to a table or a queue that persists. It&#8217;s a fire-and-forget signal. If nobody is listening when you send it, the notification is simply gone. That&#8217;s an important distinction I&#8217;ll come back to later when I talk about use cases and limitations.</p>



<h2 class="wp-block-heading">Why NOTIFY Exists</h2>



<p class="wp-block-paragraph">PostgreSQL&#8217;s developers built <code>NOTIFY</code> and <code>LISTEN</code> to solve a very specific problem: how do you let one database connection tell other connections that something interesting just happened, without forcing everyone to constantly query the database to check?</p>



<p class="wp-block-paragraph">Before this feature existed (and still, in databases that lack it), developers used workarounds like:</p>



<ul class="wp-block-list">
<li>Polling a &#8220;last updated&#8221; timestamp column every few seconds</li>



<li>Running a cron job to check for new rows</li>



<li>Building external message queues just to relay simple state changes</li>
</ul>



<p class="wp-block-paragraph"><code>NOTIFY</code> cuts out all of that overhead for a large class of problems. It&#8217;s especially popular in combination with <code>LISTEN</code> for building real-time features: live dashboards, chat applications, cache invalidation systems, and job queue workers.</p>



<h2 class="wp-block-heading">Basic Syntax of NOTIFY</h2>



<p class="wp-block-paragraph">The syntax for <code>NOTIFY</code> is refreshingly simple:</p>



<pre class="wp-block-code"><code>NOTIFY channel_name;
</code></pre>



<p class="wp-block-paragraph">or, if you want to send a payload with your notification:</p>



<pre class="wp-block-code"><code>NOTIFY channel_name, 'payload text here';
</code></pre>



<p class="wp-block-paragraph">There&#8217;s also a function form, <code>pg_notify()</code>, which behaves the same way but is easier to use inside procedural code, like triggers or PL/pgSQL functions:</p>



<pre class="wp-block-code"><code>SELECT pg_notify('channel_name', 'payload text here');
</code></pre>



<p class="wp-block-paragraph">I actually prefer <code>pg_notify()</code> in most real-world code because it&#8217;s a function, not a bare SQL statement, which means you can call it dynamically with variables far more easily inside triggers and stored procedures.</p>



<h3 class="wp-block-heading">Parameters Explained</h3>



<p class="wp-block-paragraph">Let&#8217;s break down what each part actually means:</p>



<p class="wp-block-paragraph"><strong>channel_name</strong> This is an identifier, not a string literal, when you use the plain <code>NOTIFY</code> syntax. It follows the same naming rules as other PostgreSQL identifiers — letters, digits, underscores, and it can&#8217;t start with a digit. If you use <code>pg_notify()</code>, the channel name is passed as a string literal instead, which gives you more flexibility, including the ability to build channel names dynamically.</p>



<p class="wp-block-paragraph"><strong>payload</strong> This is an optional string, limited to 8000 bytes in most PostgreSQL versions. It&#8217;s plain text — PostgreSQL doesn&#8217;t parse or validate it in any special way, so you&#8217;re free to send anything: a JSON string, a row ID, a status code, whatever your application needs. Many developers send small JSON payloads here so listening clients can decide what to do without querying the database again.</p>



<p class="wp-block-paragraph">If you don&#8217;t specify a payload, it defaults to an empty string.</p>



<h2 class="wp-block-heading">A Practical Example: Basic NOTIFY and LISTEN</h2>



<p class="wp-block-paragraph">Let&#8217;s see this in action. Open two separate <code>psql</code> sessions (or two connections from your application) to really understand how this works.</p>



<p class="wp-block-paragraph"><strong>Session 1 — the listener:</strong></p>



<pre class="wp-block-code"><code>LISTEN order_updates;
</code></pre>



<p class="wp-block-paragraph"><strong>Session 2 — the notifier:</strong></p>



<pre class="wp-block-code"><code>NOTIFY order_updates, 'Order #10432 has shipped';
</code></pre>



<p class="wp-block-paragraph">Switch back to Session 1, and if you&#8217;re using <code>psql</code>, you&#8217;ll immediately see something like:</p>



<pre class="wp-block-code"><code>Asynchronous notification "order_updates" with payload "Order #10432 has shipped" received from server process with PID 24187.
</code></pre>



<p class="wp-block-paragraph">That&#8217;s it. No polling, no delay worth mentioning. The listening session was told, in real time, that something happened.</p>



<h2 class="wp-block-heading">Using NOTIFY Inside a Trigger</h2>



<p class="wp-block-paragraph">The real power of <code>NOTIFY</code> shows up when you combine it with triggers. Instead of manually calling <code>NOTIFY</code> every time you insert a row, you let PostgreSQL do it automatically whenever a table changes.</p>



<p class="wp-block-paragraph">Here&#8217;s a working example for an orders table:</p>



<pre class="wp-block-code"><code>CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_name TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ DEFAULT now()
);

CREATE OR REPLACE FUNCTION notify_new_order()
RETURNS TRIGGER AS $$
BEGIN
    PERFORM pg_notify(
        'new_order_channel',
        json_build_object(
            'id', NEW.id,
            'customer_name', NEW.customer_name,
            'status', NEW.status
        )::text
    );
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notify_new_order
AFTER INSERT ON orders
FOR EACH ROW
EXECUTE FUNCTION notify_new_order();
</code></pre>



<p class="wp-block-paragraph">Now, any time a new row is inserted into <code>orders</code>, a JSON payload describing that row gets broadcast to <code>new_order_channel</code>. Any application listening on that channel — say, a Node.js backend using the <code>pg</code> library, or a Python service using <code>psycopg2</code> — receives that payload instantly and can act on it, whether that&#8217;s pushing a WebSocket update to a browser or kicking off a background job.</p>



<h2 class="wp-block-heading">Common Use Cases for NOTIFY</h2>



<p class="wp-block-paragraph">Over the years, developers have found a handful of scenarios where <code>NOTIFY</code> really shines:</p>



<ol class="wp-block-list">
<li><strong>Cache invalidation</strong> — When a row changes, notify a cache layer so it knows to refresh or evict stale entries, instead of relying on a fixed TTL.</li>



<li><strong>Real-time dashboards</strong> — Send notifications when metrics or statuses change so a dashboard can update live instead of refreshing on a timer.</li>



<li><strong>Job queue signaling</strong> — Rather than having worker processes poll a jobs table every second, have them <code>LISTEN</code> on a channel, and <code>NOTIFY</code> them the moment a new job is inserted. They still typically re-check the table (since payloads aren&#8217;t guaranteed to be delivered to a worker that wasn&#8217;t listening), but the responsiveness improves dramatically.</li>



<li><strong>Chat and messaging apps</strong> — A classic use case. Insert a message, trigger a notification, and connected clients get it immediately.</li>



<li><strong>Multi-service coordination</strong> — When you have several services connected to the same PostgreSQL database and need a lightweight way for one to signal another without introducing a separate message broker like RabbitMQ or Kafka.</li>
</ol>



<h2 class="wp-block-heading">Limitations You Need to Know About</h2>



<p class="wp-block-paragraph">I don&#8217;t want to oversell <code>NOTIFY</code>, because it&#8217;s not a replacement for a proper message queue in every situation. Here&#8217;s what you need to keep in mind:</p>



<p class="wp-block-paragraph"><strong>Notifications aren&#8217;t persisted.</strong> If a listener isn&#8217;t connected and actively listening at the moment <code>NOTIFY</code> fires, that notification is lost forever. There&#8217;s no replay, no history, nothing to catch up on. If your application absolutely cannot afford to miss an event, you need a durable queue (or at minimum, a fallback polling mechanism as a safety net).</p>



<p class="wp-block-paragraph"><strong>Payload size is limited.</strong> The 8000-byte cap on payloads (this can vary slightly by PostgreSQL build, but it&#8217;s a hard limit) means you can&#8217;t stuff large amounts of data into a notification. Send an ID or small JSON object, not entire documents.</p>



<p class="wp-block-paragraph"><strong>Notifications are delivered only within the same database.</strong> If you have multiple databases on the same PostgreSQL server, a <code>NOTIFY</code> in one won&#8217;t reach a <code>LISTEN</code> in another.</p>



<p class="wp-block-paragraph"><strong>Delivery happens at transaction commit, not immediately at the NOTIFY call.</strong> If you call <code>NOTIFY</code> inside a transaction and then roll back, the notification never gets sent. If the transaction commits, PostgreSQL delivers it right after. This is actually a really useful property — it means listeners never get notified about changes that ultimately got rolled back — but it can surprise people who expect instant delivery inside a long transaction.</p>



<p class="wp-block-paragraph"><strong>Duplicate notifications within a transaction are collapsed.</strong> If you call <code>NOTIFY channel, 'payload'</code> multiple times with the exact same channel and payload inside a single transaction, PostgreSQL only sends it once. If the payloads differ, each is sent separately.</p>



<h2 class="wp-block-heading">Troubleshooting Common NOTIFY Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;My listener isn&#8217;t receiving anything.&#8221;</strong> The most common cause is that the listening connection wasn&#8217;t actually listening at the time the notification was sent, or it&#8217;s a completely separate database connection pool where each query grabs a random connection from the pool (common with ORMs). <code>LISTEN</code> is tied to a specific database session — if your connection pool hands out a different connection for every query, your <code>LISTEN</code> won&#8217;t stick. You need a dedicated, long-lived connection for listening.</p>



<p class="wp-block-paragraph"><strong>&#8220;I sent NOTIFY but nothing happened until much later.&#8221;</strong> Check whether you&#8217;re inside a long-running transaction. Remember, delivery is deferred until commit. If you&#8217;re debugging in <code>psql</code> and forgot you&#8217;re inside a <code>BEGIN</code> block, that&#8217;s often the culprit.</p>



<p class="wp-block-paragraph"><strong>&#8220;My application driver isn&#8217;t surfacing notifications.&#8221;</strong> Different client libraries handle this differently. In <code>psycopg2</code> for Python, you need to poll the connection object for notifications after executing queries, or use a dedicated polling loop with <code>select()</code>. In <code>node-postgres</code>, you attach an event listener to the client&#8217;s <code>notification</code> event. Always check your specific driver&#8217;s documentation, since the low-level protocol detail of receiving <code>NOTIFY</code> messages is not automatically surfaced everywhere.</p>



<p class="wp-block-paragraph"><strong>&#8220;Payload got truncated.&#8221;</strong> You&#8217;ve likely exceeded the byte limit. Switch to sending a lightweight reference — like a row ID — and have the listener query the database for full details rather than cramming everything into the payload.</p>



<h2 class="wp-block-heading">Best Practices for Using NOTIFY</h2>



<p class="wp-block-paragraph">After using this feature across several production systems, here&#8217;s what I&#8217;ve settled on as good practice:</p>



<ul class="wp-block-list">
<li><strong>Keep payloads small and structured.</strong> JSON with just the essentials (ID, event type, timestamp) works well and keeps parsing simple on the receiving end.</li>



<li><strong>Treat NOTIFY as &#8220;hey, something happened&#8221; rather than the source of truth.</strong> Have the listener re-query the database for authoritative state rather than trusting the payload blindly, especially for anything business-critical.</li>



<li><strong>Use a dedicated connection for LISTEN.</strong> Don&#8217;t rely on a pooled connection that gets recycled between queries. Most languages have a way to open a raw, persistent connection specifically for this purpose.</li>



<li><strong>Combine with a polling fallback for critical workflows.</strong> If a missed notification would be a real problem (like a payment processing job), don&#8217;t rely solely on <code>NOTIFY</code>. Use it as an optimization on top of a periodic safety-net poll, not as your only mechanism.</li>



<li><strong>Namespace your channels.</strong> In larger applications, it&#8217;s easy to end up with channel name collisions. Prefixing channels like <code>orders_new</code>, <code>orders_updated</code>, <code>chat_message_sent</code> keeps things organized and avoids accidental cross-talk between unrelated features.</li>



<li><strong>Document your channels somewhere.</strong> Because channel names are just strings scattered through your codebase, it&#8217;s worth keeping a short reference (even a comment block or a wiki page) listing every channel in use, what triggers it, and what payload format to expect.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>NOTIFY</code> is one of those PostgreSQL features that feels almost too simple for how useful it is. It won&#8217;t replace a dedicated message broker for high-volume, mission-critical event streaming, but for a huge range of everyday problems — live updates, cache invalidation, lightweight job signaling — it does the job with zero extra infrastructure. Pair it with <code>LISTEN</code>, understand its delivery guarantees (or lack thereof), and it can genuinely simplify your architecture instead of adding another moving part to maintain.</p>



<p class="wp-block-paragraph">If you&#8217;re building anything that currently polls the database on a timer, it&#8217;s worth asking yourself: could <code>NOTIFY</code> do this instead? More often than you&#8217;d expect, the answer is yes.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-notify-command-in-postgresql/">How to Use the NOTIFY Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-notify-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7463</post-id>	</item>
		<item>
		<title>How to Use the LISTEN Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-listen-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-listen-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:47:54 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7460</guid>

					<description><![CDATA[<p>There&#8217;s a moment every backend developer hits eventually: you&#8217;re polling your database every couple of seconds to check&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-listen-command-in-postgresql/">How to Use the LISTEN Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">There&#8217;s a moment every backend developer hits eventually: you&#8217;re polling your database every couple of seconds to check for new data, and it just feels wrong. It works, sure, but it&#8217;s wasteful, it adds latency, and it makes your database do a bunch of unnecessary work just to tell you &#8220;nope, nothing new&#8221; over and over again.</p>



<p class="wp-block-paragraph">PostgreSQL has a built-in answer to this, and it&#8217;s the <code>LISTEN</code> command. I&#8217;m going to walk you through exactly how it works, how to pair it with <code>NOTIFY</code>, and how to actually use it in real applications without running into the gotchas that trip up a lot of people the first time they try it.</p>



<h2 class="wp-block-heading">What Does LISTEN Do?</h2>



<p class="wp-block-paragraph"><code>LISTEN</code> registers the current database session as a subscriber to a named notification channel. Once you run it, that session will receive any messages sent to that channel via the <code>NOTIFY</code> command, for as long as the session stays connected.</p>



<p class="wp-block-paragraph">It&#8217;s the receiving half of PostgreSQL&#8217;s built-in publish/subscribe system. <code>NOTIFY</code> sends messages, <code>LISTEN</code> receives them. Neither one does anything useful without the other.</p>



<p class="wp-block-paragraph">What makes this special is that it&#8217;s asynchronous and near-instant. You don&#8217;t need to run a <code>SELECT</code> query in a loop to check for updates — PostgreSQL pushes the notification to you the moment it happens (well, technically the moment the notifying transaction commits, but more on that shortly).</p>



<h2 class="wp-block-heading">Basic Syntax</h2>



<p class="wp-block-paragraph">The syntax couldn&#8217;t be simpler:</p>



<pre class="wp-block-code"><code>LISTEN channel_name;
</code></pre>



<p class="wp-block-paragraph"><code>channel_name</code> is an identifier — the same rules apply as with table or column names. It can&#8217;t start with a digit, and if you want to use characters outside the standard identifier rules (like spaces or mixed case that needs to be preserved), you&#8217;d wrap it in double quotes:</p>



<pre class="wp-block-code"><code>LISTEN "My Channel";
</code></pre>



<p class="wp-block-paragraph">But honestly, I&#8217;d recommend sticking to simple lowercase names with underscores, like <code>order_updates</code> or <code>job_queue_new</code>, just to keep things predictable across your codebase.</p>



<p class="wp-block-paragraph">To stop listening, there&#8217;s a matching command:</p>



<pre class="wp-block-code"><code>UNLISTEN channel_name;
</code></pre>



<p class="wp-block-paragraph">Or, to stop listening to everything at once:</p>



<pre class="wp-block-code"><code>UNLISTEN *;
</code></pre>



<h2 class="wp-block-heading">How LISTEN Actually Works Under the Hood</h2>



<p class="wp-block-paragraph">When you issue <code>LISTEN</code>, PostgreSQL records that your current backend process (your session) is subscribed to that channel. This subscription lives only as long as your session is connected. Close the connection, and you&#8217;re automatically unsubscribed — there&#8217;s no persistence across reconnects. If your app restarts or the connection drops, you need to issue <code>LISTEN</code> again once reconnected.</p>



<p class="wp-block-paragraph">When another session runs <code>NOTIFY channel_name</code> (or <code>pg_notify()</code>), and that transaction commits successfully, PostgreSQL delivers the notification to every currently listening session on that channel, including — importantly — the session that sent it, if it happens to also be listening on the same channel.</p>



<p class="wp-block-paragraph">The delivery itself happens via PostgreSQL&#8217;s underlying connection protocol. Your client library needs to actively check for and surface these asynchronous messages; they don&#8217;t just magically appear as query results. This is the part that confuses people most often, so let&#8217;s spend some real time on it.</p>



<h2 class="wp-block-heading">Trying LISTEN in psql</h2>



<p class="wp-block-paragraph">The quickest way to see this work is with two <code>psql</code> windows open side by side.</p>



<p class="wp-block-paragraph"><strong>Terminal 1:</strong></p>



<pre class="wp-block-code"><code>LISTEN chat_messages;
</code></pre>



<p class="wp-block-paragraph">You won&#8217;t see any output yet — you&#8217;re just now subscribed.</p>



<p class="wp-block-paragraph"><strong>Terminal 2:</strong></p>



<pre class="wp-block-code"><code>NOTIFY chat_messages, 'Hello from another session!';
</code></pre>



<p class="wp-block-paragraph"><strong>Back in Terminal 1</strong>, run any trivial query (like <code>SELECT 1;</code>) or just wait — <code>psql</code> checks for notifications between commands — and you&#8217;ll see:</p>



<pre class="wp-block-code"><code>Asynchronous notification "chat_messages" with payload "Hello from another session!" received from server process with PID 18820.
</code></pre>



<p class="wp-block-paragraph">That&#8217;s PostgreSQL&#8217;s asynchronous messaging working exactly as designed.</p>



<h2 class="wp-block-heading">Using LISTEN From Application Code</h2>



<p class="wp-block-paragraph">This is where things get more interesting, because every language and driver handles the mechanics of receiving notifications a bit differently. Let&#8217;s go through a few common ones.</p>



<h3 class="wp-block-heading">Node.js with node-postgres (pg)</h3>



<pre class="wp-block-code"><code>const { Client } = require('pg');

const client = new Client({
  connectionString: 'postgres://user:password@localhost/mydb'
});

client.connect();

client.query('LISTEN order_updates');

client.on('notification', (msg) =&gt; {
  console.log('Channel:', msg.channel);
  console.log('Payload:', msg.payload);
  // Handle the notification here
});
</code></pre>



<p class="wp-block-paragraph">The key detail: you need to keep this client connection open and dedicated. Don&#8217;t grab it from a general-purpose connection pool that recycles connections between unrelated queries, because your <code>LISTEN</code> subscription will vanish the moment that connection is returned to the pool and reused (or worse, closed).</p>



<h3 class="wp-block-heading">Python with psycopg2</h3>



<pre class="wp-block-code"><code>import psycopg2
import select

conn = psycopg2.connect("dbname=mydb user=myuser")
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)

cur = conn.cursor()
cur.execute("LISTEN order_updates;")

print("Waiting for notifications...")

while True:
    if select.select(&#91;conn], &#91;], &#91;], 5) == (&#91;], &#91;], &#91;]):
        continue
    else:
        conn.poll()
        while conn.notifies:
            notify = conn.notifies.pop(0)
            print(f"Got NOTIFY: {notify.channel} -&gt; {notify.payload}")
</code></pre>



<p class="wp-block-paragraph">Notice the <code>select()</code> call — this is how Python efficiently waits for the socket to have data available, rather than busy-looping and burning CPU.</p>



<h3 class="wp-block-heading">Python with asyncpg (async)</h3>



<pre class="wp-block-code"><code>import asyncio
import asyncpg

async def handle_notification(connection, pid, channel, payload):
    print(f"Received: {channel} -&gt; {payload}")

async def main():
    conn = await asyncpg.connect("postgresql://user:password@localhost/mydb")
    await conn.add_listener('order_updates', handle_notification)
    await asyncio.sleep(3600)  # keep the connection alive to listen

asyncio.run(main())
</code></pre>



<p class="wp-block-paragraph"><code>asyncpg</code> handles a lot of the plumbing for you, which is one reason it&#8217;s a popular choice for real-time PostgreSQL-driven applications in Python.</p>



<h2 class="wp-block-heading">A Real Example: LISTEN for a Job Queue Worker</h2>



<p class="wp-block-paragraph">Let&#8217;s build something more realistic. Suppose you have a <code>jobs</code> table, and worker processes should wake up immediately when new jobs arrive rather than polling every few seconds.</p>



<p class="wp-block-paragraph"><strong>Setup:</strong></p>



<pre class="wp-block-code"><code>CREATE TABLE jobs (
    id SERIAL PRIMARY KEY,
    payload JSONB NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ DEFAULT now()
);

CREATE OR REPLACE FUNCTION notify_job_inserted()
RETURNS TRIGGER AS $$
BEGIN
    PERFORM pg_notify('job_inserted', NEW.id::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notify_job_inserted
AFTER INSERT ON jobs
FOR EACH ROW
EXECUTE FUNCTION notify_job_inserted();
</code></pre>



<p class="wp-block-paragraph"><strong>Worker (conceptual pseudocode using any driver):</strong></p>



<pre class="wp-block-code"><code>LISTEN job_inserted;

loop forever:
    wait for notification (with a timeout, e.g. 30 seconds, as a safety net)
    if notification received OR timeout elapsed:
        SELECT * FROM jobs WHERE status = 'pending' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
        process the job if one was found
</code></pre>



<p class="wp-block-paragraph">Notice that even though <code>LISTEN</code> wakes the worker up instantly, the worker still queries the table for the actual work — it doesn&#8217;t trust the notification payload as the sole source of truth. This pattern (notify-to-wake, then query-to-confirm) is by far the most robust way to use <code>LISTEN</code> for queue-like workloads, because it protects you from missed notifications, duplicate deliveries, or notifications lost during a brief disconnect.</p>



<h2 class="wp-block-heading">Common Use Cases</h2>



<ul class="wp-block-list">
<li><strong>Real-time UI updates</strong> — Backend listens for database changes and pushes them to connected clients over WebSockets.</li>



<li><strong>Distributed cache invalidation</strong> — Multiple app servers listen on a shared channel so they all evict stale cache entries at once.</li>



<li><strong>Background job wake-ups</strong> — As shown above, listening lets workers avoid constant polling while still remaining safe against missed events.</li>



<li><strong>Multi-tenant event routing</strong> — Applications with per-tenant channels (e.g., <code>tenant_42_updates</code>) can isolate notification streams cleanly.</li>



<li><strong>Leader election or coordination signals</strong> — Lightweight coordination between service instances without needing a separate coordination service.</li>
</ul>



<h2 class="wp-block-heading">Troubleshooting LISTEN Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;I ran LISTEN but I&#8217;m not getting anything.&#8221;</strong> Ninety percent of the time, this is a connection pooling issue. Your <code>LISTEN</code> subscription is tied to one specific database connection. If your application framework uses a connection pool (which most ORMs do by default), the connection you used to run <code>LISTEN</code> might get returned to the pool and handed to a completely different part of your app for an unrelated query, silently breaking your subscription. Always open a separate, dedicated, long-lived connection specifically for listening.</p>



<p class="wp-block-paragraph"><strong>&#8220;My connection keeps dropping and I stop receiving notifications.&#8221;</strong> Networks aren&#8217;t perfect, and database connections can drop due to timeouts, restarts, or load balancer behavior. Build reconnection logic that automatically re-issues <code>LISTEN</code> after any reconnect. Don&#8217;t assume your subscription persists across a dropped connection — it doesn&#8217;t.</p>



<p class="wp-block-paragraph"><strong>&#8220;I&#8217;m missing notifications that happened while my app was restarting.&#8221;</strong> This is expected behavior, not a bug. <code>LISTEN</code>/<code>NOTIFY</code> has no memory or replay mechanism. If you need guaranteed delivery even across downtime, you need a durable queue or, at the very least, a &#8220;catch-up&#8221; query on startup that checks for anything you might have missed.</p>



<p class="wp-block-paragraph"><strong>&#8220;Too many idle connections just for listening.&#8221;</strong> If you have many independent features all needing to listen for different things, consider consolidating into fewer long-lived connections that each subscribe to multiple channels, rather than opening a separate connection per feature. <code>LISTEN</code> supports subscribing to more than one channel per session — just call it multiple times.</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Use one dedicated connection per listening process</strong>, separate from your regular query connection pool.</li>



<li><strong>Always build in reconnection logic</strong> that re-issues <code>LISTEN</code> commands after a dropped connection.</li>



<li><strong>Don&#8217;t treat notifications as guaranteed delivery.</strong> Use them as a wake-up trigger, then confirm state with an actual query, especially for anything important.</li>



<li><strong>Set a reasonable timeout when waiting for notifications</strong> so your process periodically checks state anyway, as a safety net against missed events.</li>



<li><strong>Keep channel names organized and documented</strong>, especially as your application grows and more features start listening on different channels.</li>



<li><strong>Monitor your listening connections.</strong> Since these are typically long-lived, idle connections, keep an eye on connection counts and make sure you&#8217;re not accidentally leaking listener connections that never get cleaned up.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>LISTEN</code> is deceptively simple on the surface — one line of SQL — but using it well in a real application means understanding its session-bound nature, its lack of persistence, and how your specific client library surfaces asynchronous notifications. Once you&#8217;ve got that down, it becomes an incredibly cheap way to make your application feel responsive and real-time, without bolting on an entirely separate messaging system. Combined with <code>NOTIFY</code>, it&#8217;s one of PostgreSQL&#8217;s most underrated features, and it&#8217;s sitting right there in every PostgreSQL install, ready to use for free.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-listen-command-in-postgresql/">How to Use the LISTEN Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-listen-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7460</post-id>	</item>
		<item>
		<title>How to Use the RELEASE SAVEPOINT Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-release-savepoint-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-release-savepoint-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:46:01 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7457</guid>

					<description><![CDATA[<p>If you&#8217;ve spent any time working with transactions in PostgreSQL, you&#8217;ve probably come across SAVEPOINT — that handy&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-release-savepoint-command-in-postgresql/">How to Use the RELEASE SAVEPOINT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">If you&#8217;ve spent any time working with transactions in PostgreSQL, you&#8217;ve probably come across <code>SAVEPOINT</code> — that handy little checkpoint you can set inside a transaction so you can roll back part of your work without losing everything. But there&#8217;s a companion command that doesn&#8217;t get nearly as much attention: <code>RELEASE SAVEPOINT</code>. It&#8217;s easy to forget about, but understanding it properly will make you a lot more confident writing complex, multi-step transactions.</p>



<p class="wp-block-paragraph">I want to walk you through what <code>RELEASE SAVEPOINT</code> actually does, why it matters, and how to use it correctly in real-world scenarios — including some of the subtle behaviors that trip people up.</p>



<h2 class="wp-block-heading">What Is RELEASE SAVEPOINT?</h2>



<p class="wp-block-paragraph"><code>RELEASE SAVEPOINT</code> destroys a previously defined savepoint within the current transaction. Once released, that savepoint no longer exists, and you can&#8217;t roll back to it anymore. Importantly, releasing a savepoint does <strong>not</strong> undo any of the work done since that savepoint was created — it simply forgets the checkpoint itself. All the changes made after the savepoint remain part of the transaction, pending the eventual <code>COMMIT</code> or <code>ROLLBACK</code> of the whole transaction.</p>



<p class="wp-block-paragraph">This trips a lot of people up initially because the name sounds like it might discard changes, similar to how <code>ROLLBACK TO SAVEPOINT</code> works. It doesn&#8217;t. Think of a savepoint as a bookmark in a book. <code>ROLLBACK TO SAVEPOINT</code> takes you back to that bookmarked page and throws away everything you wrote after it. <code>RELEASE SAVEPOINT</code> just removes the bookmark — the pages you wrote stay exactly where they are.</p>



<h2 class="wp-block-heading">Why Does RELEASE SAVEPOINT Exist?</h2>



<p class="wp-block-paragraph">Savepoints, by their nature, consume a small amount of resources within a transaction — PostgreSQL has to track them internally. In long-running or deeply nested transactions with many savepoints, releasing ones you no longer need helps keep things tidy and avoids exceeding internal limits on savepoint nesting.</p>



<p class="wp-block-paragraph">More practically, <code>RELEASE SAVEPOINT</code> is useful for signaling intent in your code: &#8220;I successfully completed this step, I don&#8217;t need to be able to roll back to before it anymore, let&#8217;s move forward.&#8221; This is especially valuable in application code that wraps multi-step operations in nested transaction blocks, where each step might succeed or fail independently.</p>



<h2 class="wp-block-heading">Basic Syntax</h2>



<pre class="wp-block-code"><code>SAVEPOINT savepoint_name;
-- do some work
RELEASE SAVEPOINT savepoint_name;
</code></pre>



<p class="wp-block-paragraph">You can also shorten it — PostgreSQL accepts:</p>



<pre class="wp-block-code"><code>RELEASE savepoint_name;
</code></pre>



<p class="wp-block-paragraph">The word <code>SAVEPOINT</code> in <code>RELEASE SAVEPOINT</code> is technically optional in PostgreSQL&#8217;s implementation, though I&#8217;d recommend keeping it for readability, especially if other people will be reading your SQL scripts.</p>



<h3 class="wp-block-heading">Parameters</h3>



<p class="wp-block-paragraph"><strong>savepoint_name</strong> An identifier you chose when you created the savepoint with <code>SAVEPOINT savepoint_name</code>. It must match an existing, currently active savepoint in the current transaction, or PostgreSQL will throw an error telling you it doesn&#8217;t exist.</p>



<p class="wp-block-paragraph">If you have multiple savepoints with the same name (which PostgreSQL does allow, believe it or not), <code>RELEASE SAVEPOINT</code> releases the most recently created one matching that name, along with any savepoints created after it.</p>



<h2 class="wp-block-heading">A Basic Example</h2>



<p class="wp-block-paragraph">Let&#8217;s walk through a simple, complete example using <code>psql</code>.</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO accounts (name, balance) VALUES ('Alice', 1000);

SAVEPOINT before_bonus;

UPDATE accounts SET balance = balance + 50 WHERE name = 'Alice';

RELEASE SAVEPOINT before_bonus;

COMMIT;
</code></pre>



<p class="wp-block-paragraph">In this example, we insert a new account, set a savepoint, apply a bonus update, and then release the savepoint because we&#8217;re satisfied everything worked correctly. The final <code>COMMIT</code> then makes all of it — the insert and the update — permanent. Releasing the savepoint here doesn&#8217;t discard the bonus update; it just means we&#8217;re no longer holding onto the ability to roll back to the point right before it.</p>



<h2 class="wp-block-heading">A More Practical Example: Nested Savepoints</h2>



<p class="wp-block-paragraph">Where <code>RELEASE SAVEPOINT</code> really becomes useful is in more complex transactions involving multiple steps, some of which might legitimately fail and need partial rollback, without scrapping the entire transaction.</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO orders (customer_id, status) VALUES (42, 'processing');

SAVEPOINT payment_step;

-- Attempt to deduct payment
UPDATE accounts SET balance = balance - 100 WHERE customer_id = 42;

-- Suppose we check the balance and it's fine, so we keep the change
RELEASE SAVEPOINT payment_step;

SAVEPOINT inventory_step;

-- Attempt to reduce stock
UPDATE inventory SET stock = stock - 1 WHERE product_id = 7;

-- Suppose this succeeded too
RELEASE SAVEPOINT inventory_step;

COMMIT;
</code></pre>



<p class="wp-block-paragraph">Now imagine the inventory step actually failed because stock hit zero and a <code>CHECK</code> constraint blocked the update. In that scenario, instead of releasing <code>inventory_step</code>, you&#8217;d issue:</p>



<pre class="wp-block-code"><code>ROLLBACK TO SAVEPOINT inventory_step;
</code></pre>



<p class="wp-block-paragraph">This would undo the failed inventory update while keeping the order insert and the payment deduction intact, letting you decide what to do next — maybe notify the customer of a stock issue — without losing the earlier successful work.</p>



<h2 class="wp-block-heading">RELEASE SAVEPOINT vs ROLLBACK TO SAVEPOINT</h2>



<p class="wp-block-paragraph">This distinction genuinely confuses a lot of people who are newer to PostgreSQL transactions, so let&#8217;s be very explicit about it:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Command</th><th>What it does</th></tr></thead><tbody><tr><td><code>SAVEPOINT name</code></td><td>Creates a checkpoint you can return to later</td></tr><tr><td><code>RELEASE SAVEPOINT name</code></td><td>Forgets the checkpoint; keeps all changes made after it</td></tr><tr><td><code>ROLLBACK TO SAVEPOINT name</code></td><td>Undoes all changes made after the checkpoint, but keeps the checkpoint itself active so you can try again</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">One extra detail worth knowing: after a <code>ROLLBACK TO SAVEPOINT</code>, the savepoint itself still exists and can be rolled back to again, or explicitly released later if you&#8217;re done with it. <code>RELEASE SAVEPOINT</code>, on the other hand, permanently removes the savepoint from existence — you cannot roll back to a released savepoint.</p>



<h2 class="wp-block-heading">Using RELEASE SAVEPOINT in Application Code (ORMs and Nested Transactions)</h2>



<p class="wp-block-paragraph">If you&#8217;ve ever used nested transactions in an ORM — Django&#8217;s <code>atomic()</code> blocks, SQLAlchemy&#8217;s nested sessions, Rails&#8217; nested transactions — you&#8217;ve actually been using <code>SAVEPOINT</code> and <code>RELEASE SAVEPOINT</code> behind the scenes, even if you never typed those words yourself.</p>



<p class="wp-block-paragraph">For example, in SQLAlchemy:</p>



<pre class="wp-block-code"><code>with session.begin():
    # outer transaction begins (BEGIN)
    account.balance -= 100
    
    with session.begin_nested():
        # SAVEPOINT created here
        inventory.stock -= 1
        # if this block exits normally, RELEASE SAVEPOINT happens automatically
    
    # if an exception had occurred in the nested block,
    # ROLLBACK TO SAVEPOINT would have happened instead
</code></pre>



<p class="wp-block-paragraph">Understanding what&#8217;s happening under the hood here — that a normal exit from a nested block triggers <code>RELEASE SAVEPOINT</code>, while an exception triggers <code>ROLLBACK TO SAVEPOINT</code> — makes debugging weird transaction behavior far easier, because you can reason about it in terms of raw SQL rather than framework magic.</p>



<h2 class="wp-block-heading">Common Use Cases for RELEASE SAVEPOINT</h2>



<ol class="wp-block-list">
<li><strong>Multi-step business transactions</strong> where each step should be individually recoverable, but successful steps shouldn&#8217;t be undone if a later step fails.</li>



<li><strong>Batch processing with partial failure tolerance</strong>, where you savepoint before each item, release on success, and roll back to the savepoint on failure — allowing the loop to continue processing remaining items instead of aborting the whole batch.</li>



<li><strong>ORMs and framework-managed nested transactions</strong>, as shown above — even if you&#8217;re not writing the SQL by hand, understanding this helps you debug transaction issues in your application logs.</li>



<li><strong>Testing and exploratory changes</strong>, where you savepoint before trying something risky, then either release it (keep the change) or roll back (discard it) based on the outcome.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;ERROR: savepoint &#8216;x&#8217; does not exist&#8221;</strong> This means you&#8217;re trying to release a savepoint that was never created in the current transaction, was already released, or was already rolled back past (rolling back to an earlier savepoint destroys any savepoints created after it). Double check the exact name and that you&#8217;re still within the same transaction block.</p>



<p class="wp-block-paragraph"><strong>&#8220;My changes disappeared even though I used RELEASE, not ROLLBACK TO.&#8221;</strong> Releasing a savepoint never discards changes on its own. If your changes vanished, check whether the outer transaction itself was rolled back or never committed. <code>RELEASE SAVEPOINT</code> only affects the savepoint bookkeeping — it has zero effect on the actual data changes.</p>



<p class="wp-block-paragraph"><strong>&#8220;Too many savepoints&#8221; or performance degradation in long transactions.</strong> While PostgreSQL supports many nested savepoints, each one does add a small amount of internal overhead. If you&#8217;re programmatically creating a savepoint per row in a very large loop and never releasing them, memory and performance can suffer. Release savepoints as soon as you&#8217;re confident you don&#8217;t need to roll back to them, rather than letting them pile up for the entire transaction&#8217;s lifetime.</p>



<p class="wp-block-paragraph"><strong>&#8220;Can I release a savepoint that&#8217;s not the most recent one?&#8221;</strong> Yes — releasing an older savepoint also implicitly releases any savepoints created after it. This makes sense once you think about it as a stack: you can&#8217;t reach back and remove a middle bookmark while leaving the ones after it valid, because those later savepoints logically depend on state that existed after the earlier one.</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Give savepoints clear, descriptive names</strong> related to the operation they precede, like <code>before_payment</code> or <code>before_stock_update</code>, rather than generic names like <code>sp1</code>. It makes debugging transaction logs far easier.</li>



<li><strong>Release savepoints as soon as you&#8217;re done with them</strong>, rather than holding onto every savepoint until the final commit. This keeps transaction state lean, especially in loops or batch operations.</li>



<li><strong>Don&#8217;t confuse RELEASE with COMMIT.</strong> Releasing a savepoint has no effect on transaction durability — only an actual <code>COMMIT</code> makes your changes permanent. If your outer transaction later rolls back entirely, all released-savepoint changes disappear too.</li>



<li><strong>Use savepoints for genuinely risky or optional steps</strong>, not as a substitute for proper application-level error handling. They&#8217;re a tool for partial recovery within a transaction, not a replacement for validating your data before you even start.</li>



<li><strong>When using an ORM, understand what nested transaction blocks translate to under the hood.</strong> It&#8217;ll save you a lot of confusion when debugging unexpected rollback behavior.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>RELEASE SAVEPOINT</code> is one of those commands that seems minor until you&#8217;re deep in a complex, multi-step transaction and need fine-grained control over what gets kept and what gets undone. It doesn&#8217;t discard anything on its own — it simply lets go of a checkpoint you no longer need, while everything you&#8217;ve done since that checkpoint stays intact, waiting for the final <code>COMMIT</code> or <code>ROLLBACK</code> of the whole transaction. Once you&#8217;ve internalized that distinction from <code>ROLLBACK TO SAVEPOINT</code>, you&#8217;ll find savepoints — and releasing them at the right time — become a genuinely powerful tool for writing robust, recoverable transaction logic.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-release-savepoint-command-in-postgresql/">How to Use the RELEASE SAVEPOINT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-release-savepoint-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7457</post-id>	</item>
		<item>
		<title>How to Use the SAVEPOINT Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-savepoint-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-savepoint-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:44:37 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7454</guid>

					<description><![CDATA[<p>Transactions are one of those database concepts that seem straightforward until you actually need fine-grained control over them.&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-savepoint-command-in-postgresql/">How to Use the SAVEPOINT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Transactions are one of those database concepts that seem straightforward until you actually need fine-grained control over them. You wrap a few statements in <code>BEGIN</code> and <code>COMMIT</code>, and it&#8217;s all or nothing — either everything succeeds, or you roll back the entire thing. But what happens when you want something in between? What if step three of five fails, and you&#8217;d like to undo just that step without throwing away the successful work from steps one and two?</p>



<p class="wp-block-paragraph">That&#8217;s exactly the problem <code>SAVEPOINT</code> solves. I want to walk you through what it is, how it works, and how to use it in real transactions, because once it clicks, it becomes one of those tools you reach for constantly without even thinking about it.</p>



<h2 class="wp-block-heading">What Is a SAVEPOINT?</h2>



<p class="wp-block-paragraph">A <code>SAVEPOINT</code> is a named checkpoint you create inside a transaction. Once set, you can later roll back to that exact point — undoing everything that happened after it — while keeping the transaction itself alive and keeping everything that happened before the savepoint intact. It&#8217;s essentially a bookmark you can jump back to without abandoning the whole transaction.</p>



<p class="wp-block-paragraph">This is different from a full <code>ROLLBACK</code>, which throws away the entire transaction, savepoints and all, and returns the database to the state it was in before <code>BEGIN</code> was ever issued. A savepoint gives you a middle ground: partial, targeted undo, within the context of one larger unit of work.</p>



<h2 class="wp-block-heading">Why SAVEPOINT Matters</h2>



<p class="wp-block-paragraph">Imagine you&#8217;re processing a multi-step operation — say, transferring money between accounts, updating inventory, and logging an audit record — all as one logical transaction. If the audit logging step fails for some unrelated reason (maybe a constraint violation), do you really want to lose the successful money transfer and inventory update too? Probably not. With savepoints, you can isolate the risky or optional step, and if it fails, roll back just that piece while preserving everything else, then decide how to proceed.</p>



<p class="wp-block-paragraph">This becomes especially valuable in:</p>



<ul class="wp-block-list">
<li>Batch processing where individual items might fail without invalidating the whole batch</li>



<li>Complex multi-table operations where partial success is meaningful</li>



<li>Application frameworks and ORMs that implement &#8220;nested transactions&#8221; (which are really just savepoints under the hood)</li>



<li>Interactive or exploratory database work where you want a safety net before trying something risky</li>
</ul>



<h2 class="wp-block-heading">Basic Syntax</h2>



<pre class="wp-block-code"><code>BEGIN;

-- some statements

SAVEPOINT savepoint_name;

-- more statements

ROLLBACK TO SAVEPOINT savepoint_name;
-- or
RELEASE SAVEPOINT savepoint_name;

COMMIT;
</code></pre>



<h3 class="wp-block-heading">Parameters</h3>



<p class="wp-block-paragraph"><strong>savepoint_name</strong> An identifier for the savepoint, following standard PostgreSQL naming rules (letters, digits, underscores, can&#8217;t start with a digit). You choose this name, and you&#8217;ll use it later to either roll back to it or release it. PostgreSQL does allow you to reuse the same name multiple times within a transaction — in that case, operations referring to that name apply to the most recently created savepoint with it.</p>



<p class="wp-block-paragraph">A <code>SAVEPOINT</code> can only be used inside an explicit transaction block (started with <code>BEGIN</code>). Outside of a transaction, <code>SAVEPOINT</code> will produce an error, since there&#8217;s no ongoing transaction to checkpoint within.</p>



<h2 class="wp-block-heading">A Simple, Complete Example</h2>



<p class="wp-block-paragraph">Let&#8217;s walk through this step by step in <code>psql</code>.</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO accounts (name, balance) VALUES ('Bob', 500);

SAVEPOINT before_withdrawal;

UPDATE accounts SET balance = balance - 1000 WHERE name = 'Bob';

-- Oops, that would put Bob into negative balance, let's undo it
ROLLBACK TO SAVEPOINT before_withdrawal;

-- Balance is back to 500 here, but the INSERT of Bob is still intact
UPDATE accounts SET balance = balance - 100 WHERE name = 'Bob';

COMMIT;
</code></pre>



<p class="wp-block-paragraph">Walk through the logic: we insert a new account for Bob with a balance of 500. We set a savepoint. We attempt a withdrawal of 1000, which would leave Bob with a negative balance — let&#8217;s say your application logic catches this as a problem. We roll back to the savepoint, which undoes just that bad withdrawal, while Bob&#8217;s original insert remains untouched. Then we apply a smaller, valid withdrawal of 100, and commit the whole transaction. The final result: Bob exists with a balance of 400 (500 minus the successful 100 withdrawal), and the failed 1000 withdrawal never happened at all.</p>



<h2 class="wp-block-heading">Nested Savepoints</h2>



<p class="wp-block-paragraph">You can create multiple savepoints within a single transaction, effectively building a stack of checkpoints:</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO orders (customer_id, status) VALUES (1, 'new');

SAVEPOINT sp1;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 10;

SAVEPOINT sp2;
UPDATE accounts SET balance = balance - 50 WHERE customer_id = 1;

-- Suppose the payment step fails validation
ROLLBACK TO SAVEPOINT sp2;

-- The inventory update from before sp2 is still intact
-- Only the payment attempt after sp2 was undone

COMMIT;
</code></pre>



<p class="wp-block-paragraph">Rolling back to <code>sp2</code> undoes only what happened after it (the account balance update), while everything before it — including the inventory change made after <code>sp1</code> but before <code>sp2</code> — remains part of the transaction. If you then rolled back further, to <code>sp1</code>, that would also undo the inventory update, leaving only the initial order insert intact.</p>



<p class="wp-block-paragraph">This stacking behavior is genuinely powerful for structuring complex, multi-stage transactions where different stages have different levels of &#8220;riskiness.&#8221;</p>



<h2 class="wp-block-heading">SAVEPOINT in a Real-World Batch Processing Scenario</h2>



<p class="wp-block-paragraph">Here&#8217;s a pattern I use fairly often: processing a batch of records where I want individual failures to not derail the entire batch.</p>



<pre class="wp-block-code"><code>BEGIN;

-- Pseudocode loop (this part would be in application code, not raw SQL)
FOR each item IN batch:
    SAVEPOINT item_savepoint;
    
    BEGIN
        INSERT INTO processed_items (item_id, status) VALUES (item.id, 'processed');
        UPDATE inventory SET stock = stock - item.quantity WHERE product_id = item.product_id;
        RELEASE SAVEPOINT item_savepoint;
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK TO SAVEPOINT item_savepoint;
            INSERT INTO failed_items (item_id, error_message) VALUES (item.id, SQLERRM);
    END;

COMMIT;
</code></pre>



<p class="wp-block-paragraph">If this were written as a PL/pgSQL function, it might actually look like this:</p>



<pre class="wp-block-code"><code>CREATE OR REPLACE FUNCTION process_batch(batch_ids INT&#91;])
RETURNS void AS $$
DECLARE
    item_id INT;
BEGIN
    FOREACH item_id IN ARRAY batch_ids
    LOOP
        BEGIN
            SAVEPOINT item_savepoint;
            
            INSERT INTO processed_items (item_id, status)
            VALUES (item_id, 'processed');
            
            UPDATE inventory
            SET stock = stock - 1
            WHERE product_id = item_id;
            
            RELEASE SAVEPOINT item_savepoint;
        EXCEPTION
            WHEN OTHERS THEN
                ROLLBACK TO SAVEPOINT item_savepoint;
                INSERT INTO failed_items (item_id, error_message)
                VALUES (item_id, SQLERRM);
        END;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
</code></pre>



<p class="wp-block-paragraph">This is a genuinely common and useful pattern in PL/pgSQL: PostgreSQL actually implements exception handling in PL/pgSQL blocks using savepoints internally, so every <code>BEGIN ... EXCEPTION ... END</code> block you write in a PL/pgSQL function is, under the hood, using the same savepoint mechanism we&#8217;re talking about here.</p>



<h2 class="wp-block-heading">Common Use Cases</h2>



<ol class="wp-block-list">
<li><strong>Batch operations with partial failure tolerance</strong> — as shown above, letting individual item failures get logged and skipped without aborting the entire batch.</li>



<li><strong>Exception handling inside PL/pgSQL functions</strong> — every <code>EXCEPTION</code> block in PL/pgSQL relies on savepoints under the hood.</li>



<li><strong>ORM &#8220;nested transactions&#8221;</strong> — frameworks like Django, SQLAlchemy, and Rails implement nested transaction support using savepoints, so understanding this command helps you reason about what your ORM is actually doing.</li>



<li><strong>Testing risky operations interactively</strong> — set a savepoint before trying an experimental change in a <code>psql</code> session, and roll back to it if the results aren&#8217;t what you expected, without losing your entire session&#8217;s work.</li>



<li><strong>Multi-stage business logic</strong> — complex workflows involving multiple related updates where certain stages are more failure-prone than others.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;ERROR: SAVEPOINT can only be used in transaction blocks&#8221;</strong> You tried to create a savepoint outside of an explicit <code>BEGIN</code>. Every statement in PostgreSQL technically runs inside an implicit transaction, but savepoints require an explicit, ongoing transaction block that you control with <code>BEGIN</code> and <code>COMMIT</code>/<code>ROLLBACK</code>. Wrap your work in <code>BEGIN</code> first.</p>



<p class="wp-block-paragraph"><strong>&#8220;My transaction is stuck in a failed state and I can&#8217;t run any more commands.&#8221;</strong> This is one of the most common PostgreSQL gotchas. If a statement inside your transaction throws an error and you don&#8217;t roll back to a savepoint (or roll back entirely), PostgreSQL puts the whole transaction into an aborted state. Every subsequent command will fail with &#8220;current transaction is aborted, commands ignored until end of transaction block&#8221; until you either issue a <code>ROLLBACK</code> (ending the whole transaction) or <code>ROLLBACK TO SAVEPOINT</code> (if you had one set before the error). This is exactly why wrapping risky statements with a preceding <code>SAVEPOINT</code> is so valuable — it gives you a recovery point instead of losing the entire transaction to one bad statement.</p>



<p class="wp-block-paragraph"><strong>&#8220;Savepoint doesn&#8217;t exist&#8221; errors.</strong> Make sure you haven&#8217;t already released or rolled back past that savepoint earlier in the same transaction. Once you roll back to an earlier savepoint, any savepoints created after it are gone too.</p>



<p class="wp-block-paragraph"><strong>Performance concerns with many savepoints.</strong> Each active savepoint does carry a small amount of overhead. If you&#8217;re setting savepoints in a tight loop over a huge number of rows, consider whether you actually need per-row savepoints, or whether batching (savepoint every N rows) would be more efficient while still giving you reasonable failure isolation.</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Always pair SAVEPOINT with a clear plan for both outcomes</strong> — know in advance whether you&#8217;ll <code>RELEASE</code> or <code>ROLLBACK TO</code> it, and under what conditions.</li>



<li><strong>Use descriptive names</strong> rather than generic ones like <code>sp1</code>, <code>sp2</code> — future you (or a teammate) debugging a complex transaction log will thank you.</li>



<li><strong>Don&#8217;t overuse savepoints for trivial operations.</strong> They add value when there&#8217;s a genuine risk of partial failure. For simple, low-risk statements, they&#8217;re unnecessary overhead.</li>



<li><strong>Remember that a savepoint doesn&#8217;t survive outside its transaction.</strong> Once you <code>COMMIT</code> or <code>ROLLBACK</code> the outer transaction, all savepoints within it are gone, regardless of whether you released them.</li>



<li><strong>Understand that PL/pgSQL exception blocks use savepoints automatically.</strong> If you&#8217;re writing functions with <code>EXCEPTION</code> handling, you&#8217;re already using this mechanism — knowing that helps you reason about performance and behavior more accurately.</li>



<li><strong>Combine with proper application-level retry logic</strong> for genuinely transient failures (like serialization errors), rather than relying on savepoints alone to handle every kind of failure gracefully.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>SAVEPOINT</code> gives you something a plain <code>BEGIN</code>/<code>COMMIT</code>/<code>ROLLBACK</code> transaction can&#8217;t: the ability to undo part of your work without losing all of it. Whether you&#8217;re processing a batch where individual failures shouldn&#8217;t derail the whole run, writing PL/pgSQL functions with proper exception handling, or just want a safety net while experimenting in a live session, savepoints give you precise, granular control over your transactions. Once you get comfortable creating them, rolling back to them, and releasing them, you&#8217;ll find they quietly become one of the most useful tools in your PostgreSQL toolkit.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-savepoint-command-in-postgresql/">How to Use the SAVEPOINT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-savepoint-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7454</post-id>	</item>
		<item>
		<title>How to Use the ROLLBACK Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-rollback-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-rollback-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:42:40 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7451</guid>

					<description><![CDATA[<p>Every developer has that moment of panic — you run an UPDATE or DELETE statement, hit enter, and&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-rollback-command-in-postgresql/">How to Use the ROLLBACK Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Every developer has that moment of panic — you run an <code>UPDATE</code> or <code>DELETE</code> statement, hit enter, and then immediately realize you forgot the <code>WHERE</code> clause, or got the condition wrong. If you&#8217;re working inside a transaction at that moment, <code>ROLLBACK</code> is what saves you. It&#8217;s the command that undoes everything you&#8217;ve done since the transaction began, as if none of it ever happened.</p>



<p class="wp-block-paragraph">I want to walk you through exactly how <code>ROLLBACK</code> works in PostgreSQL, when to use it, and some of the details that matter more than people realize once you&#8217;re working with real applications instead of just running one-off queries.</p>



<h2 class="wp-block-heading">What Is ROLLBACK?</h2>



<p class="wp-block-paragraph"><code>ROLLBACK</code> ends the current transaction and discards all the changes made within it. Nothing you did since the transaction started — whether that&#8217;s inserts, updates, deletes, or schema changes — gets applied to the database. It&#8217;s as though the transaction never happened at all.</p>



<p class="wp-block-paragraph">This is the counterpart to <code>COMMIT</code>, which ends a transaction by making all its changes permanent. Together, <code>BEGIN</code>, <code>COMMIT</code>, and <code>ROLLBACK</code> form the foundation of transactional control in PostgreSQL, letting you group multiple statements into one atomic unit: either everything succeeds together, or nothing does.</p>



<h2 class="wp-block-heading">Why ROLLBACK Matters</h2>



<p class="wp-block-paragraph">Databases without proper transaction support (or applications that don&#8217;t use transactions correctly) are fragile. A network hiccup, an application crash, or a bug partway through a multi-step operation can leave your data in an inconsistent state — half-updated, with no clean way to recover. Transactions, and the ability to <code>ROLLBACK</code>, protect you from exactly that.</p>



<p class="wp-block-paragraph">Beyond error recovery, <code>ROLLBACK</code> is also genuinely useful as a deliberate tool:</p>



<ul class="wp-block-list">
<li>Testing changes safely in a live session before committing to them</li>



<li>Aborting a batch operation partway through if something looks wrong</li>



<li>Automatically undoing everything when an application-level exception occurs mid-transaction</li>



<li>Recovering gracefully from constraint violations or serialization failures</li>
</ul>



<h2 class="wp-block-heading">Basic Syntax</h2>



<pre class="wp-block-code"><code>BEGIN;

-- some statements

ROLLBACK;
</code></pre>



<p class="wp-block-paragraph">That&#8217;s really it in its simplest form. PostgreSQL also accepts <code>ROLLBACK WORK;</code> and <code>ROLLBACK TRANSACTION;</code> as equivalent, more verbose synonyms, if you prefer that style or you&#8217;re working with SQL that needs to match conventions from other databases.</p>



<p class="wp-block-paragraph">There&#8217;s also <code>ROLLBACK TO SAVEPOINT</code>, which is a related but distinct command for partial rollbacks — I&#8217;ll touch on that briefly, though it deserves its own deeper treatment given how much nuance it has.</p>



<pre class="wp-block-code"><code>ROLLBACK TO SAVEPOINT savepoint_name;
</code></pre>



<p class="wp-block-paragraph">This form doesn&#8217;t end the transaction — it just undoes everything back to a specific checkpoint within it, letting the transaction continue.</p>



<h2 class="wp-block-heading">A Basic, Complete Example</h2>



<p class="wp-block-paragraph">Let&#8217;s see this in action with <code>psql</code>.</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO products (name, price) VALUES ('Wireless Mouse', 25.99);

SELECT * FROM products WHERE name = 'Wireless Mouse';
-- You'll see the row, even though it's not committed yet

ROLLBACK;

SELECT * FROM products WHERE name = 'Wireless Mouse';
-- The row is gone, as if the INSERT never happened
</code></pre>



<p class="wp-block-paragraph">Notice that within the transaction, before rolling back, the <code>SELECT</code> shows the inserted row — that&#8217;s because your own session can always see its own uncommitted changes. But once you issue <code>ROLLBACK</code>, that insert is completely discarded, and the row disappears as though it never existed.</p>



<h2 class="wp-block-heading">ROLLBACK After an Error</h2>



<p class="wp-block-paragraph">One of the most important things to understand about PostgreSQL transactions is what happens when a statement fails partway through. Unlike some databases that let you keep running subsequent statements after an error, PostgreSQL puts the entire transaction into an &#8220;aborted&#8221; state the moment any statement inside it fails.</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO accounts (name, balance) VALUES ('Charlie', 100);

UPDATE accounts SET balance = balance / 0 WHERE name = 'Charlie';
-- ERROR: division by zero

SELECT * FROM accounts WHERE name = 'Charlie';
-- ERROR: current transaction is aborted, commands ignored until end of transaction block

ROLLBACK;
-- Now you can start fresh
</code></pre>



<p class="wp-block-paragraph">Once that division-by-zero error hits, PostgreSQL refuses to run any further commands in that transaction — not even a harmless <code>SELECT</code> — until you explicitly issue <code>ROLLBACK</code> (or <code>ROLLBACK TO SAVEPOINT</code>, if you had one set before the failing statement). This is a very deliberate design choice: PostgreSQL won&#8217;t let you keep building on top of a transaction that&#8217;s already in an inconsistent, error-triggered state.</p>



<p class="wp-block-paragraph">This is exactly why many application frameworks and ORMs automatically issue a <code>ROLLBACK</code> the moment any exception is raised inside a transaction block — it&#8217;s the only way to get the connection back to a usable state.</p>



<h2 class="wp-block-heading">ROLLBACK in Application Code</h2>



<p class="wp-block-paragraph">Almost every application framework handles this for you, but it&#8217;s worth understanding what&#8217;s actually happening underneath.</p>



<p class="wp-block-paragraph"><strong>Python (psycopg2):</strong></p>



<pre class="wp-block-code"><code>import psycopg2

conn = psycopg2.connect("dbname=mydb user=myuser")
cur = conn.cursor()

try:
    cur.execute("INSERT INTO orders (customer_id, total) VALUES (%s, %s)", (1, 250.00))
    cur.execute("UPDATE inventory SET stock = stock - 1 WHERE product_id = %s", (7,))
    conn.commit()
except Exception as e:
    conn.rollback()
    print(f"Transaction failed and was rolled back: {e}")
</code></pre>



<p class="wp-block-paragraph"><strong>Node.js (pg):</strong></p>



<pre class="wp-block-code"><code>const client = await pool.connect();

try {
  await client.query('BEGIN');
  await client.query('INSERT INTO orders (customer_id, total) VALUES ($1, $2)', &#91;1, 250.00]);
  await client.query('UPDATE inventory SET stock = stock - 1 WHERE product_id = $1', &#91;7]);
  await client.query('COMMIT');
} catch (err) {
  await client.query('ROLLBACK');
  console.error('Transaction failed and was rolled back:', err);
} finally {
  client.release();
}
</code></pre>



<p class="wp-block-paragraph">The pattern is the same across virtually every language: wrap your statements in a try block, commit at the end if everything succeeded, and roll back in the catch/except block if anything went wrong. This is the fundamental building block of safe, consistent database operations in application code.</p>



<h2 class="wp-block-heading">ROLLBACK TO SAVEPOINT: Partial Rollbacks</h2>



<p class="wp-block-paragraph">While a full <code>ROLLBACK</code> discards the entire transaction, sometimes you only want to undo part of it. That&#8217;s where savepoints come in, paired with <code>ROLLBACK TO SAVEPOINT</code>.</p>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO orders (customer_id, status) VALUES (1, 'pending');

SAVEPOINT before_payment;

UPDATE accounts SET balance = balance - 500 WHERE customer_id = 1;
-- Suppose this triggers a check constraint violation because balance would go negative

ROLLBACK TO SAVEPOINT before_payment;

-- The order insert is still intact, only the failed payment attempt was undone
UPDATE orders SET status = 'payment_failed' WHERE customer_id = 1;

COMMIT;
</code></pre>



<p class="wp-block-paragraph">This gives you a middle ground between &#8220;undo everything&#8221; and &#8220;undo nothing&#8221; — genuinely useful for complex, multi-step transactions where you want fine-grained control over what survives a failure.</p>



<h2 class="wp-block-heading">Common Use Cases for ROLLBACK</h2>



<ol class="wp-block-list">
<li><strong>Error recovery in application transactions</strong> — the most common use case by far. Any multi-statement operation should be wrapped in a transaction with proper rollback handling on failure.</li>



<li><strong>Interactive testing in a live session</strong> — start a transaction, try a risky change, inspect the results, and roll back if you don&#8217;t like what you see, without any permanent effect on the database.</li>



<li><strong>Aborting batch jobs mid-run</strong> — if a batch process detects something seriously wrong partway through (like unexpected data volume or a sanity check failure), rolling back the whole transaction can be safer than trying to undo individual statements manually.</li>



<li><strong>Serialization failure recovery</strong> — under stricter isolation levels (like <code>SERIALIZABLE</code>), PostgreSQL can abort a transaction due to a detected conflict with another concurrent transaction. The correct response is to roll back and retry the whole transaction.</li>



<li><strong>Database migrations gone wrong</strong> — running schema changes inside a transaction (PostgreSQL supports transactional DDL, unlike many other databases) means you can roll back an entire failed migration cleanly.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common ROLLBACK Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;ERROR: current transaction is aborted, commands ignored until end of transaction block&#8221;</strong> This means an earlier statement in your transaction failed, and PostgreSQL is refusing further commands until you roll back (fully, or to a savepoint set before the failure). This is expected behavior, not a bug — just issue <code>ROLLBACK</code> and start over, or use savepoints proactively before risky statements so you have a recovery point.</p>



<p class="wp-block-paragraph"><strong>&#8220;I rolled back but my sequence values (like SERIAL id) still jumped.&#8221;</strong> This is a common point of confusion. Sequences used for auto-incrementing columns are <strong>not</strong> transactional in PostgreSQL — they&#8217;re deliberately designed this way for performance, so that concurrent transactions don&#8217;t block each other waiting for the next sequence value. If you <code>INSERT</code> a row (consuming a sequence value) and then roll back, that specific ID number is &#8220;used up&#8221; and won&#8217;t be reused, even though the row itself never actually got saved. This is completely normal and not something to worry about — gaps in ID sequences are expected and harmless.</p>



<p class="wp-block-paragraph"><strong>&#8220;My application seems to hang after an error, and nothing else runs.&#8221;</strong> Check whether your connection is stuck in an aborted transaction state, and your application code isn&#8217;t issuing a <code>ROLLBACK</code> before trying to run more queries on that same connection. This is a very common bug in hand-rolled transaction handling — always make sure your error-handling path actually calls rollback.</p>



<p class="wp-block-paragraph"><strong>&#8220;Can I roll back after COMMIT?&#8221;</strong> No. Once a transaction commits, it&#8217;s permanent — there&#8217;s no built-in <code>UNCOMMIT</code>. Your only recovery options at that point are things like restoring from a backup, using point-in-time recovery if you have WAL archiving set up, or manually writing corrective statements (and wrapping those in their own transaction, of course).</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Always wrap multi-statement operations in explicit transactions</strong>, and make sure your error handling path actually issues <code>ROLLBACK</code> — don&#8217;t just let connections hang in an aborted state.</li>



<li><strong>Use savepoints for partial rollback needs</strong> rather than restructuring your entire transaction around all-or-nothing behavior when that&#8217;s not really what you need.</li>



<li><strong>Don&#8217;t worry about sequence gaps caused by rollbacks.</strong> They&#8217;re a normal, expected side effect of how sequences work, not a sign of data corruption.</li>



<li><strong>Keep transactions as short as reasonably possible.</strong> Long-running transactions that might need to roll back hold locks and resources longer, which can affect concurrent access from other sessions.</li>



<li><strong>Test your rollback logic, not just your happy path.</strong> It&#8217;s easy to test that a successful transaction commits correctly and forget to verify that a failed one actually rolls back cleanly, especially in application code with nested try/catch logic.</li>



<li><strong>Remember that DDL is transactional in PostgreSQL.</strong> Unlike MySQL, you can wrap <code>CREATE TABLE</code>, <code>ALTER TABLE</code>, and other schema changes inside a transaction and roll them back if something goes wrong partway through a migration — this is a genuine advantage worth using deliberately.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>ROLLBACK</code> is one of the most important safety mechanisms PostgreSQL gives you. It turns &#8220;I made a mistake&#8221; from a potential disaster into a non-event, as long as you&#8217;re working inside a transaction. Whether you&#8217;re recovering from an unexpected error in application code, testing something risky in a live session, or cleanly aborting a batch job that&#8217;s gone sideways, understanding exactly how and when to use <code>ROLLBACK</code> — including its partial form with savepoints — is fundamental to writing reliable, safe database code.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-rollback-command-in-postgresql/">How to Use the ROLLBACK Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-rollback-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7451</post-id>	</item>
		<item>
		<title>How to Use the COMMIT Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-commit-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-commit-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:41:29 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7448</guid>

					<description><![CDATA[<p>If BEGIN opens the door to a transaction and ROLLBACK is the emergency exit, then COMMIT is what&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-commit-command-in-postgresql/">How to Use the COMMIT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">If <code>BEGIN</code> opens the door to a transaction and <code>ROLLBACK</code> is the emergency exit, then <code>COMMIT</code> is what actually locks everything in. It&#8217;s the command that takes all the work you&#8217;ve done inside a transaction and makes it permanent, durable, and visible to every other connection to the database. It sounds simple, and in a lot of ways it is, but there&#8217;s more nuance to it than most people realize once you start working with real, concurrent applications.</p>



<p class="wp-block-paragraph">Let&#8217;s go through exactly what <code>COMMIT</code> does, how to use it properly, and the details that actually matter in production systems.</p>



<h2 class="wp-block-heading">What Is COMMIT?</h2>



<p class="wp-block-paragraph"><code>COMMIT</code> ends the current transaction and saves all the changes made during it permanently to the database. Once a transaction is committed, its effects are durable — they survive crashes, restarts, power failures, anything short of actual data corruption or hardware failure (assuming you have proper write-ahead logging configured, which PostgreSQL does by default).</p>



<p class="wp-block-paragraph">This durability guarantee is part of what&#8217;s known as ACID compliance — Atomicity, Consistency, Isolation, Durability — and <code>COMMIT</code> is specifically the moment where the &#8220;Durability&#8221; part kicks in. Before <code>COMMIT</code>, your changes exist only within your session; other connections can&#8217;t see them, and a crash would wipe them out entirely. After <code>COMMIT</code>, they&#8217;re locked in.</p>



<h2 class="wp-block-heading">Why COMMIT Matters</h2>



<p class="wp-block-paragraph">Every statement in PostgreSQL technically runs inside a transaction, even if you never explicitly type <code>BEGIN</code>. If you run a single <code>INSERT</code> statement outside of any explicit transaction block, PostgreSQL wraps it in an implicit transaction and commits it automatically the moment it succeeds. This is often called &#8220;autocommit mode,&#8221; and it&#8217;s the default behavior in most PostgreSQL client tools.</p>



<p class="wp-block-paragraph">But the moment you explicitly start a transaction with <code>BEGIN</code>, autocommit is suspended for that session until you issue <code>COMMIT</code> or <code>ROLLBACK</code>. This gives you the power to group multiple statements into one atomic unit — but it also means those changes sit in limbo, invisible to everyone else, until you explicitly commit them. Understanding this distinction is fundamental to writing correct, concurrent-safe applications.</p>



<h2 class="wp-block-heading">Basic Syntax</h2>



<pre class="wp-block-code"><code>BEGIN;

-- one or more statements

COMMIT;
</code></pre>



<p class="wp-block-paragraph">PostgreSQL also accepts the more verbose synonyms <code>COMMIT WORK;</code> and <code>COMMIT TRANSACTION;</code>, which behave identically. Use whichever matches your team&#8217;s conventions — I generally just use the plain <code>COMMIT;</code> since it&#8217;s shorter and equally clear.</p>



<h2 class="wp-block-heading">A Basic, Complete Example</h2>



<pre class="wp-block-code"><code>BEGIN;

INSERT INTO customers (name, email) VALUES ('Diana Prince', 'diana@example.com');
UPDATE inventory SET stock = stock - 1 WHERE product_id = 5;

COMMIT;
</code></pre>



<p class="wp-block-paragraph">Here, both statements — the customer insert and the inventory update — become permanent together, at the moment <code>COMMIT</code> runs. If anything had gone wrong between <code>BEGIN</code> and <code>COMMIT</code> (say, a constraint violation on the inventory update), neither statement would have taken effect, assuming you handled the error with a <code>ROLLBACK</code> instead of trying to push through to <code>COMMIT</code>.</p>



<p class="wp-block-paragraph">This all-or-nothing behavior — atomicity — is the whole point of grouping statements into a transaction in the first place.</p>



<h2 class="wp-block-heading">Autocommit vs Explicit Transactions</h2>



<p class="wp-block-paragraph">It&#8217;s worth being very clear about the default behavior, because it surprises people coming from certain other tools or languages.</p>



<p class="wp-block-paragraph"><strong>Without an explicit BEGIN:</strong></p>



<pre class="wp-block-code"><code>INSERT INTO logs (message) VALUES ('User logged in');
-- This commits immediately, on its own, the moment it succeeds
</code></pre>



<p class="wp-block-paragraph"><strong>With an explicit BEGIN:</strong></p>



<pre class="wp-block-code"><code>BEGIN;
INSERT INTO logs (message) VALUES ('User logged in');
-- Not committed yet! Other sessions can't see this row.
COMMIT;
-- NOW it's committed and visible to everyone
</code></pre>



<p class="wp-block-paragraph">Most application frameworks and ORMs manage this for you, often defaulting to autocommit-per-statement unless you explicitly open a transaction block through the framework&#8217;s API (like Django&#8217;s <code>atomic()</code>, or a manual <code>BEGIN</code>/<code>COMMIT</code> pair through a raw connection). Understanding which mode you&#8217;re in at any given point in your code is important — bugs where developers assume they&#8217;re inside a transaction, when actually every statement is autocommitting individually, are a genuinely common source of data inconsistency issues.</p>



<h2 class="wp-block-heading">COMMIT in Application Code</h2>



<p class="wp-block-paragraph"><strong>Python (psycopg2):</strong></p>



<pre class="wp-block-code"><code>import psycopg2

conn = psycopg2.connect("dbname=mydb user=myuser")
cur = conn.cursor()

try:
    cur.execute("INSERT INTO orders (customer_id, total) VALUES (%s, %s)", (3, 89.99))
    cur.execute("UPDATE inventory SET stock = stock - 1 WHERE product_id = %s", (12,))
    conn.commit()
    print("Transaction committed successfully")
except Exception as e:
    conn.rollback()
    print(f"Error occurred, transaction rolled back: {e}")
</code></pre>



<p class="wp-block-paragraph">Note that <code>psycopg2</code> connections default to <strong>not</strong> autocommitting by default when you use them this way — each connection starts an implicit transaction on the first statement, and you need to explicitly call <code>.commit()</code> for changes to persist. This differs from raw <code>psql</code>&#8216;s default autocommit-per-statement behavior, which is exactly the kind of driver-specific detail worth double-checking whenever you pick up a new client library.</p>



<p class="wp-block-paragraph"><strong>Node.js (pg):</strong></p>



<pre class="wp-block-code"><code>const client = await pool.connect();

try {
  await client.query('BEGIN');
  await client.query('INSERT INTO orders (customer_id, total) VALUES ($1, $2)', &#91;3, 89.99]);
  await client.query('UPDATE inventory SET stock = stock - 1 WHERE product_id = $1', &#91;12]);
  await client.query('COMMIT');
} catch (err) {
  await client.query('ROLLBACK');
  throw err;
} finally {
  client.release();
}
</code></pre>



<p class="wp-block-paragraph">Here, <code>node-postgres</code> defaults to autocommit-per-statement unless you explicitly issue <code>BEGIN</code>, which is why the code above explicitly starts the transaction before running the grouped statements.</p>



<h2 class="wp-block-heading">COMMIT and Isolation Levels</h2>



<p class="wp-block-paragraph"><code>COMMIT</code> behaves a bit differently in practice depending on your transaction&#8217;s isolation level, especially under <code>SERIALIZABLE</code> or <code>REPEATABLE READ</code>. Under these stricter isolation levels, PostgreSQL can actually refuse to commit a transaction if it detects that committing would violate the isolation guarantee — for example, if another concurrent transaction modified data your transaction depended on in a conflicting way.</p>



<pre class="wp-block-code"><code>BEGIN ISOLATION LEVEL SERIALIZABLE;

SELECT balance FROM accounts WHERE id = 1;
-- application logic decides to update based on this value
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

COMMIT;
-- ERROR: could not serialize access due to concurrent update
</code></pre>



<p class="wp-block-paragraph">When this happens, your application needs to catch that specific error and retry the entire transaction from the beginning (starting a fresh <code>BEGIN</code>), rather than assuming the <code>COMMIT</code> succeeded. This is a well-known pattern when working with <code>SERIALIZABLE</code> isolation, and it&#8217;s worth building retry logic around it if you use that isolation level for correctness-critical operations.</p>



<h2 class="wp-block-heading">Common Use Cases for Explicit COMMIT</h2>



<ol class="wp-block-list">
<li><strong>Multi-statement operations that need atomicity</strong> — transferring funds between two accounts, creating an order along with its line items, updating multiple related tables that must stay in sync.</li>



<li><strong>Batch data loading</strong> — grouping many inserts into a single transaction and committing once at the end is typically far faster than autocommitting every single row individually, because it avoids the overhead of a separate commit (and associated disk flush) per statement.</li>



<li><strong>Schema migrations</strong> — since PostgreSQL supports transactional DDL, wrapping a multi-step migration in a transaction lets you commit the entire migration atomically, or roll it all back if any step fails.</li>



<li><strong>Coordinating with NOTIFY</strong> — as covered elsewhere, <code>NOTIFY</code> messages are only delivered after the transaction that sent them commits, which is a deliberate design choice tying notification delivery to actual, durable data changes.</li>
</ol>



<h2 class="wp-block-heading">Performance Considerations Around COMMIT</h2>



<p class="wp-block-paragraph">Each <code>COMMIT</code> involves PostgreSQL flushing the relevant write-ahead log (WAL) records to disk, to guarantee durability. This is not free — if you&#8217;re inserting a huge number of rows and committing after every single one, you&#8217;re paying that disk flush cost repeatedly, which adds up fast.</p>



<pre class="wp-block-code"><code>-- Slow: commits after every insert (if autocommitting each statement individually)
INSERT INTO events (data) VALUES ('event1');
INSERT INTO events (data) VALUES ('event2');
-- ... thousands more, each one committing separately

-- Much faster: batch into one transaction, commit once
BEGIN;
INSERT INTO events (data) VALUES ('event1');
INSERT INTO events (data) VALUES ('event2');
-- ... thousands more
COMMIT;
</code></pre>



<p class="wp-block-paragraph">For very large batch loads, PostgreSQL&#8217;s <code>COPY</code> command is even faster than batched <code>INSERT</code> statements, but the general principle holds: minimizing the number of commits for bulk operations meaningfully improves throughput.</p>



<p class="wp-block-paragraph">That said, don&#8217;t swing too far in the other direction and wrap enormous amounts of unrelated work into one giant transaction just to minimize commits — very long-running transactions hold locks and prevent certain kinds of vacuum cleanup from proceeding, which can cause its own performance problems. It&#8217;s a balance, and the right batch size depends on your specific workload.</p>



<h2 class="wp-block-heading">Troubleshooting Common COMMIT Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;My changes aren&#8217;t showing up in another session/tool.&#8221;</strong> Check whether you&#8217;ve actually committed. It&#8217;s an extremely common mistake to run a transaction, look at the result within the same session (where you can see your own uncommitted changes), and assume it&#8217;s saved — only to have another connection, or a service restart, reveal that nothing was actually persisted because <code>COMMIT</code> was never called.</p>



<p class="wp-block-paragraph"><strong>&#8220;ERROR: could not serialize access due to concurrent update&#8221; on COMMIT.</strong> This happens under <code>SERIALIZABLE</code> isolation when PostgreSQL detects a conflict with another transaction. The correct response is to retry the entire transaction from scratch, not just re-run the <code>COMMIT</code>.</p>



<p class="wp-block-paragraph"><strong>&#8220;My application seems to hang, and I suspect a transaction was never committed or rolled back.&#8221;</strong> Long-running, uncommitted transactions hold locks that can block other operations, and they prevent <code>VACUUM</code> from cleaning up dead rows properly. Check <code>pg_stat_activity</code> for connections sitting in an <code>idle in transaction</code> state — that&#8217;s a strong sign of a transaction that was opened but never properly committed or rolled back, often due to a bug in error-handling logic.</p>



<p class="wp-block-paragraph"><strong>&#8220;Autocommit behavior differs between psql and my application driver.&#8221;</strong> This genuinely varies by tool. <code>psql</code> defaults to autocommit per statement unless you explicitly run <code>BEGIN</code>. Many drivers (like <code>psycopg2</code>) default to opening an implicit transaction on first statement and require an explicit <code>commit()</code> call. Always check your specific driver&#8217;s documentation rather than assuming behavior carries over from one tool to another.</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Always explicitly commit or roll back transactions you open</strong> — don&#8217;t leave connections sitting idle in an open transaction state, which can hold locks and block other work.</li>



<li><strong>Batch bulk operations into fewer, larger transactions</strong> rather than committing after every single statement, for meaningfully better performance on large data loads.</li>



<li><strong>Don&#8217;t make transactions too large either.</strong> Extremely long-running transactions can cause lock contention and interfere with vacuum processing. Find a reasonable middle ground for your workload.</li>



<li><strong>Build retry logic around SERIALIZABLE isolation failures</strong>, since a failed commit under that isolation level requires restarting the entire transaction, not just retrying the commit.</li>



<li><strong>Monitor for &#8220;idle in transaction&#8221; sessions</strong> in production, since they&#8217;re a common sign of a bug where a transaction was opened but never properly closed with <code>COMMIT</code> or <code>ROLLBACK</code>.</li>



<li><strong>Understand your specific driver&#8217;s default transaction behavior</strong> rather than assuming it matches <code>psql</code>&#8216;s autocommit-per-statement default.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>COMMIT</code> might look like a one-word formality at the end of a transaction, but it&#8217;s the moment where your changes go from tentative and invisible to permanent and durable. Understanding exactly when it&#8217;s needed — and when PostgreSQL is quietly committing on your behalf through autocommit mode — is fundamental to writing correct, safe, and reasonably performant database code. Get comfortable with grouping related statements into explicit transactions, committing them deliberately, and you&#8217;ll avoid a whole category of subtle data consistency bugs that are otherwise easy to fall into.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-commit-command-in-postgresql/">How to Use the COMMIT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-commit-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7448</post-id>	</item>
		<item>
		<title>How to Use the BEGIN Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-begin-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-begin-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:39:56 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7445</guid>

					<description><![CDATA[<p>Every transaction has to start somewhere, and in PostgreSQL, that starting point is the BEGIN command. It&#8217;s short,&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-begin-command-in-postgresql/">How to Use the BEGIN Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Every transaction has to start somewhere, and in PostgreSQL, that starting point is the <code>BEGIN</code> command. It&#8217;s short, it&#8217;s simple to type, and it&#8217;s easy to overlook just how much control it hands you over how your database operations behave. Once you understand <code>BEGIN</code> properly — including its optional parameters for isolation levels and access modes — you gain a much finer degree of control over correctness and concurrency in your applications.</p>



<p class="wp-block-paragraph">Let&#8217;s go through exactly what <code>BEGIN</code> does, how to use its various options, and where it fits into real-world PostgreSQL work.</p>



<h2 class="wp-block-heading">What Does BEGIN Do?</h2>



<p class="wp-block-paragraph"><code>BEGIN</code> starts a new transaction block. Every SQL statement you run after it becomes part of that one transaction, and none of the changes become permanent or visible to other database sessions until you explicitly issue <code>COMMIT</code>. If something goes wrong, or you simply change your mind, <code>ROLLBACK</code> discards everything done since <code>BEGIN</code> was issued.</p>



<p class="wp-block-paragraph">Without an explicit <code>BEGIN</code>, PostgreSQL runs each individual statement in its own implicit transaction, automatically committing it the moment it succeeds — this is often called autocommit mode, and it&#8217;s the default in most client tools like <code>psql</code>. <code>BEGIN</code> is what interrupts that default behavior and gives you explicit control over grouping multiple statements together as one atomic unit.</p>



<h2 class="wp-block-heading">Basic Syntax</h2>



<p class="wp-block-paragraph">The simplest form is just:</p>



<pre class="wp-block-code"><code>BEGIN;
</code></pre>



<p class="wp-block-paragraph">PostgreSQL also accepts <code>BEGIN WORK;</code> and <code>BEGIN TRANSACTION;</code> as fully equivalent, more verbose alternatives — use whichever fits your team&#8217;s style.</p>



<p class="wp-block-paragraph">But <code>BEGIN</code> also accepts optional parameters that let you fine-tune the behavior of the transaction you&#8217;re about to start:</p>



<pre class="wp-block-code"><code>BEGIN &#91; WORK | TRANSACTION ] &#91; transaction_mode &#91;, ...] ]
</code></pre>



<p class="wp-block-paragraph">Where <code>transaction_mode</code> can be one of:</p>



<pre class="wp-block-code"><code>ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
READ WRITE | READ ONLY
&#91; NOT ] DEFERRABLE
</code></pre>



<p class="wp-block-paragraph">Let&#8217;s go through each of these, because they genuinely matter for correctness in concurrent applications.</p>



<h2 class="wp-block-heading">Isolation Levels Explained</h2>



<p class="wp-block-paragraph">Isolation level controls how much a transaction is affected by, or protected from, concurrent changes made by other transactions running at the same time. PostgreSQL supports four standard isolation levels, though it&#8217;s worth knowing that PostgreSQL implements <code>READ UNCOMMITTED</code> identically to <code>READ COMMITTED</code> — it doesn&#8217;t actually allow dirty reads, unlike some other database systems.</p>



<p class="wp-block-paragraph"><strong>READ COMMITTED (the default)</strong> Each statement within the transaction sees a snapshot of the database as it was at the moment that specific statement began. This means two <code>SELECT</code> statements run at different points in the same transaction can see different data, if another transaction committed changes in between them.</p>



<pre class="wp-block-code"><code>BEGIN ISOLATION LEVEL READ COMMITTED;
-- or simply BEGIN; since this is the default
SELECT balance FROM accounts WHERE id = 1;
-- ... time passes, another transaction commits a change to this row ...
SELECT balance FROM accounts WHERE id = 1;
-- This second SELECT may show the updated value
COMMIT;
</code></pre>



<p class="wp-block-paragraph"><strong>REPEATABLE READ</strong> The entire transaction sees one consistent snapshot of the database, taken at the moment the transaction&#8217;s first statement runs. Every subsequent <code>SELECT</code> within that transaction sees that same snapshot, regardless of what other transactions commit in the meantime.</p>



<pre class="wp-block-code"><code>BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1;
-- ... another transaction commits a change to this row ...
SELECT balance FROM accounts WHERE id = 1;
-- This shows the SAME value as before, ignoring the other transaction's commit
COMMIT;
</code></pre>



<p class="wp-block-paragraph"><strong>SERIALIZABLE</strong> The strictest level. PostgreSQL behaves as if transactions were running one at a time, in some serial order, even though they&#8217;re actually running concurrently. If PostgreSQL detects that this guarantee can&#8217;t be maintained because of a genuine conflict, it will abort one of the conflicting transactions with a serialization error, and the application needs to retry.</p>



<pre class="wp-block-code"><code>BEGIN ISOLATION LEVEL SERIALIZABLE;
-- perform reads and writes
COMMIT;
-- may fail with: ERROR: could not serialize access due to read/write dependencies
</code></pre>



<p class="wp-block-paragraph">Choosing the right isolation level is a genuine trade-off: stricter levels give you stronger correctness guarantees but can result in more transaction retries under heavy concurrent load. Most applications do fine with the default <code>READ COMMITTED</code>, but financial systems, inventory management, and anything requiring strict consistency guarantees often need <code>REPEATABLE READ</code> or <code>SERIALIZABLE</code>.</p>



<h2 class="wp-block-heading">READ WRITE vs READ ONLY</h2>



<p class="wp-block-paragraph">By default, transactions are <code>READ WRITE</code>, meaning they can perform inserts, updates, deletes, and any other data-modifying operations. You can explicitly mark a transaction as <code>READ ONLY</code> if you know in advance it will only be querying data:</p>



<pre class="wp-block-code"><code>BEGIN READ ONLY;

SELECT * FROM orders WHERE status = 'pending';

COMMIT;
</code></pre>



<p class="wp-block-paragraph">Attempting to run any data-modifying statement inside a <code>READ ONLY</code> transaction will result in an error. This isn&#8217;t just documentation-as-code — PostgreSQL can, in some cases, use this information for optimization, and it also acts as a helpful safety net, preventing accidental writes in code paths that are only supposed to read data.</p>



<h2 class="wp-block-heading">Combining Options</h2>



<p class="wp-block-paragraph">You can combine multiple transaction modes in a single <code>BEGIN</code> statement:</p>



<pre class="wp-block-code"><code>BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY;
</code></pre>



<p class="wp-block-paragraph">This starts a transaction with both strict serializable isolation and read-only enforcement, which is a common combination for generating consistent reports that need to reflect a truly point-in-time, non-conflicting view of the data.</p>



<h2 class="wp-block-heading">Setting the Default Isolation Level</h2>



<p class="wp-block-paragraph">If you find yourself always wanting a particular isolation level, you don&#8217;t have to specify it in every <code>BEGIN</code> statement. You can set it at the session level:</p>



<pre class="wp-block-code"><code>SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
</code></pre>



<p class="wp-block-paragraph">Or configure it as a database-wide or role-specific default in <code>postgresql.conf</code> or via <code>ALTER ROLE</code>/<code>ALTER DATABASE</code>, if your application consistently needs something other than the standard <code>READ COMMITTED</code> default.</p>



<h2 class="wp-block-heading">A Practical Example: Transferring Funds Between Accounts</h2>



<p class="wp-block-paragraph">This is the classic example for a reason — it perfectly illustrates why <code>BEGIN</code> and transactions matter.</p>



<pre class="wp-block-code"><code>BEGIN;

UPDATE accounts SET balance = balance - 200 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

COMMIT;
</code></pre>



<p class="wp-block-paragraph">Without wrapping these two statements in <code>BEGIN</code>/<code>COMMIT</code>, there would be a window between the two <code>UPDATE</code> statements where money has left account 1 but hasn&#8217;t yet arrived in account 2 — visible to any other concurrent query, and dangerous if the application crashed between the two statements. With <code>BEGIN</code>, both changes become part of one atomic operation: either both happen, or neither does, and no other session can see a half-completed transfer.</p>



<h2 class="wp-block-heading">BEGIN in Application Code</h2>



<p class="wp-block-paragraph"><strong>Python (psycopg2):</strong></p>



<pre class="wp-block-code"><code>import psycopg2

conn = psycopg2.connect("dbname=mydb user=myuser")
conn.autocommit = False  # psycopg2 begins an implicit transaction on first statement by default anyway

cur = conn.cursor()
try:
    cur.execute("UPDATE accounts SET balance = balance - 200 WHERE id = 1")
    cur.execute("UPDATE accounts SET balance = balance + 200 WHERE id = 2")
    conn.commit()
except Exception as e:
    conn.rollback()
    raise
</code></pre>



<p class="wp-block-paragraph"><strong>Node.js (pg):</strong></p>



<pre class="wp-block-code"><code>const client = await pool.connect();

try {
  await client.query('BEGIN');
  await client.query('UPDATE accounts SET balance = balance - 200 WHERE id = $1', &#91;1]);
  await client.query('UPDATE accounts SET balance = balance + 200 WHERE id = $1', &#91;2]);
  await client.query('COMMIT');
} catch (err) {
  await client.query('ROLLBACK');
  throw err;
} finally {
  client.release();
}
</code></pre>



<p class="wp-block-paragraph">Notice that in the Node.js example, <code>BEGIN</code> is issued as an explicit query, since <code>node-postgres</code> doesn&#8217;t automatically wrap statements in a transaction — you need to opt in yourself.</p>



<h2 class="wp-block-heading">Common Use Cases for BEGIN</h2>



<ol class="wp-block-list">
<li><strong>Multi-statement atomic operations</strong> — anything where multiple related changes need to succeed or fail together, like the funds transfer example above.</li>



<li><strong>Consistent multi-step reporting</strong> — using <code>BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY</code> to generate a report from a single, unchanging snapshot of the data, even if the query involves multiple <code>SELECT</code> statements against different tables.</li>



<li><strong>Preventing race conditions in concurrent writes</strong> — using <code>SERIALIZABLE</code> isolation for operations where correctness under concurrent access is critical, like inventory reservation systems.</li>



<li><strong>Grouping schema migrations</strong> — since PostgreSQL supports transactional DDL, wrapping a multi-step migration in <code>BEGIN</code>/<code>COMMIT</code> lets the whole thing succeed or fail as one unit.</li>



<li><strong>Interactive, exploratory work in psql</strong> — starting a transaction before trying something you&#8217;re not 100% sure about, so you can inspect the results and roll back if needed.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common BEGIN Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;WARNING: there is already a transaction in progress&#8221;</strong> This happens when you call <code>BEGIN</code> while already inside an open transaction. PostgreSQL doesn&#8217;t support true nested transactions this way — if you need nested-like behavior, use <code>SAVEPOINT</code> instead. This warning is generally harmless (PostgreSQL just ignores the redundant <code>BEGIN</code>), but it&#8217;s usually a sign of a bug in your transaction management logic that&#8217;s worth fixing.</p>



<p class="wp-block-paragraph"><strong>&#8220;My transaction seems to see stale data.&#8221;</strong> Check your isolation level. Under <code>REPEATABLE READ</code> or <code>SERIALIZABLE</code>, your transaction deliberately continues to see the snapshot from when it started, even as other transactions commit changes elsewhere. This is often exactly the guarantee you want, but it can be confusing if you expected <code>READ COMMITTED</code>-style behavior (seeing the latest committed data on every statement) instead.</p>



<p class="wp-block-paragraph"><strong>&#8220;ERROR: could not serialize access due to concurrent update&#8221;</strong> This is expected behavior under <code>SERIALIZABLE</code> (and sometimes <code>REPEATABLE READ</code>) isolation when PostgreSQL detects a conflict. The fix isn&#8217;t to avoid <code>BEGIN</code> — it&#8217;s to build retry logic in your application that catches this specific error and restarts the entire transaction from a fresh <code>BEGIN</code>.</p>



<p class="wp-block-paragraph"><strong>Connections sitting &#8220;idle in transaction.&#8221;</strong> This happens when <code>BEGIN</code> is called but neither <code>COMMIT</code> nor <code>ROLLBACK</code> ever follows, often due to an unhandled exception in application code. Long idle-in-transaction sessions hold locks and can block vacuuming — check <code>pg_stat_activity</code> if you suspect this is happening in production.</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Keep transactions as short as practical.</strong> The longer a transaction stays open after <code>BEGIN</code>, the longer it holds locks and blocks certain maintenance operations.</li>



<li><strong>Choose the isolation level deliberately, not by default.</strong> <code>READ COMMITTED</code> is fine for most everyday operations, but know when your use case genuinely needs the stronger guarantees of <code>REPEATABLE READ</code> or <code>SERIALIZABLE</code>.</li>



<li><strong>Mark read-only transactions as READ ONLY explicitly</strong> when you know in advance no writes will happen — it&#8217;s a useful safety net against accidental writes.</li>



<li><strong>Always pair BEGIN with proper commit/rollback handling in application code.</strong> Never leave a code path where an exception could leave a transaction open indefinitely.</li>



<li><strong>Build retry logic for SERIALIZABLE transactions</strong>, since serialization failures are an expected, normal part of using that isolation level under concurrent load, not a sign of something broken.</li>



<li><strong>Avoid nesting BEGIN calls.</strong> Use <code>SAVEPOINT</code> if you need nested-transaction-like behavior within a single outer transaction.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>BEGIN</code> is deceptively simple to type but genuinely powerful in what it enables: grouping statements into atomic units, and controlling exactly how isolated your transaction is from concurrent activity elsewhere in the database. Getting comfortable with its optional isolation level and access mode parameters — not just the bare <code>BEGIN;</code> — will give you real, practical control over correctness in systems where multiple things are happening to your data at once, which, in any production application, is basically all the time.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-begin-command-in-postgresql/">How to Use the BEGIN Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-begin-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7445</post-id>	</item>
		<item>
		<title>How to Use the REVOKE Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-revoke-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-revoke-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:38:26 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7442</guid>

					<description><![CDATA[<p>Managing who can do what in a database is one of those responsibilities that&#8217;s easy to ignore right&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-revoke-command-in-postgresql/">How to Use the REVOKE Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Managing who can do what in a database is one of those responsibilities that&#8217;s easy to ignore right up until it becomes a serious problem — an intern with more access than they should have, an old service account that still has write permissions on tables it hasn&#8217;t touched in years, or a departing employee whose database privileges never actually got cleaned up. <code>REVOKE</code> is the command PostgreSQL gives you to take permissions away, cleanly and precisely, and it deserves a lot more attention than it usually gets.</p>



<p class="wp-block-paragraph">I want to walk you through exactly how <code>REVOKE</code> works, its syntax, the different privilege types you can revoke, and how to actually use it to keep a PostgreSQL database properly locked down.</p>



<h2 class="wp-block-heading">What Is REVOKE?</h2>



<p class="wp-block-paragraph"><code>REVOKE</code> removes previously granted privileges from a role (which could represent a user or a group) on a specific database object — a table, schema, function, sequence, database, or several other object types. It&#8217;s the direct counterpart to <code>GRANT</code>, which is how privileges get assigned in the first place.</p>



<p class="wp-block-paragraph">Privileges in PostgreSQL control what actions a role is allowed to perform: reading data (<code>SELECT</code>), modifying it (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>), changing the structure of objects, executing functions, and more. <code>REVOKE</code> lets you take any of these back, either partially or entirely, from any role that currently holds them.</p>



<h2 class="wp-block-heading">Why REVOKE Matters</h2>



<p class="wp-block-paragraph">Access control isn&#8217;t a &#8220;set it once and forget it&#8221; task. Roles change over time — someone moves to a different team, a service gets decommissioned, a temporary contractor&#8217;s project wraps up. If you only ever grant privileges and never revoke them, your database&#8217;s actual security posture drifts further and further from what it should be, and you end up with what&#8217;s sometimes called &#8220;privilege creep&#8221; — accounts quietly accumulating more access than they need, none of which anyone remembers granting or why.</p>



<p class="wp-block-paragraph"><code>REVOKE</code> is how you correct that drift. It&#8217;s also essential for implementing the principle of least privilege: giving each role exactly the access it needs to do its job, nothing more.</p>



<h2 class="wp-block-heading">Basic Syntax</h2>



<pre class="wp-block-code"><code>REVOKE &#91; GRANT OPTION FOR ]
    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    &#91;, ...] | ALL &#91; PRIVILEGES ] }
    ON { &#91; TABLE ] table_name &#91;, ...]
         | ALL TABLES IN SCHEMA schema_name &#91;, ...] }
    FROM role_name &#91;, ...]
    &#91; CASCADE | RESTRICT ];
</code></pre>



<p class="wp-block-paragraph">That&#8217;s the table-privilege form specifically, but <code>REVOKE</code> works similarly across many object types. Let&#8217;s break down the key pieces.</p>



<h3 class="wp-block-heading">Parameters Explained</h3>



<p class="wp-block-paragraph"><strong>Privilege type</strong> The specific action you&#8217;re taking away — <code>SELECT</code> for read access, <code>INSERT</code> for adding rows, <code>UPDATE</code> for modifying existing rows, <code>DELETE</code> for removing rows, <code>TRUNCATE</code> for clearing entire tables, <code>REFERENCES</code> for creating foreign keys pointing at this table, and <code>TRIGGER</code> for creating triggers on it. You can revoke one, several, or use <code>ALL PRIVILEGES</code> to remove everything at once.</p>



<p class="wp-block-paragraph"><strong>ON</strong> Specifies which object (or objects) you&#8217;re revoking privileges on — a specific table, all tables in a schema, a function, a sequence, a database, and more, depending on the privilege type.</p>



<p class="wp-block-paragraph"><strong>FROM</strong> The role (or roles) losing the privilege.</p>



<p class="wp-block-paragraph"><strong>CASCADE / RESTRICT</strong> These control what happens to privileges that were granted onward by the role you&#8217;re revoking from. If role A granted a privilege to role B, and you revoke that privilege from role A, what happens to B&#8217;s privilege, which depended on A having it in the first place? <code>RESTRICT</code> (the default) will refuse the revoke if doing so would leave dependent grants dangling, forcing you to handle those explicitly. <code>CASCADE</code> will automatically revoke those dependent grants too.</p>



<p class="wp-block-paragraph"><strong>GRANT OPTION FOR</strong> Used when you want to revoke only a role&#8217;s ability to grant a privilege to others, while leaving the underlying privilege itself intact. More on this below.</p>



<h2 class="wp-block-heading">A Basic Example</h2>



<p class="wp-block-paragraph">Let&#8217;s say you previously granted a role broad access, and now you want to scale it back.</p>



<pre class="wp-block-code"><code>-- Previously granted:
GRANT SELECT, INSERT, UPDATE ON orders TO app_user;

-- Now revoking UPDATE, keeping SELECT and INSERT:
REVOKE UPDATE ON orders FROM app_user;
</code></pre>



<p class="wp-block-paragraph">After this, <code>app_user</code> can still read from and insert into the <code>orders</code> table, but can no longer modify existing rows.</p>



<h2 class="wp-block-heading">Revoking All Privileges</h2>



<p class="wp-block-paragraph">If you want to strip a role of everything on a given object, <code>ALL PRIVILEGES</code> is the cleanest way:</p>



<pre class="wp-block-code"><code>REVOKE ALL PRIVILEGES ON orders FROM app_user;
</code></pre>



<p class="wp-block-paragraph">Or, more explicitly across every table in a schema:</p>



<pre class="wp-block-code"><code>REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM app_user;
</code></pre>



<p class="wp-block-paragraph">Keep in mind that this doesn&#8217;t prevent the role from being granted access again in the future by someone with appropriate permissions — it just removes what&#8217;s currently in effect.</p>



<h2 class="wp-block-heading">Revoking Privileges on Different Object Types</h2>



<p class="wp-block-paragraph"><code>REVOKE</code> isn&#8217;t limited to tables. Here are some other common forms you&#8217;ll run into.</p>



<p class="wp-block-paragraph"><strong>Revoking database-level connect privilege:</strong></p>



<pre class="wp-block-code"><code>REVOKE CONNECT ON DATABASE analytics FROM contractor_role;
</code></pre>



<p class="wp-block-paragraph"><strong>Revoking schema usage:</strong></p>



<pre class="wp-block-code"><code>REVOKE USAGE ON SCHEMA reporting FROM contractor_role;
</code></pre>



<p class="wp-block-paragraph"><strong>Revoking execute privilege on a function:</strong></p>



<pre class="wp-block-code"><code>REVOKE EXECUTE ON FUNCTION calculate_payroll(int) FROM temp_worker;
</code></pre>



<p class="wp-block-paragraph"><strong>Revoking privileges on a sequence:</strong></p>



<pre class="wp-block-code"><code>REVOKE USAGE, SELECT ON SEQUENCE orders_id_seq FROM app_user;
</code></pre>



<p class="wp-block-paragraph"><strong>Revoking role membership</strong> (this uses a slightly different form since it&#8217;s about role membership rather than object privileges):</p>



<pre class="wp-block-code"><code>REVOKE analytics_team FROM jane_doe;
</code></pre>



<p class="wp-block-paragraph">This last example removes <code>jane_doe</code> from the <code>analytics_team</code> role, taking away whatever privileges that group role conferred to its members.</p>



<h2 class="wp-block-heading">Understanding GRANT OPTION FOR</h2>



<p class="wp-block-paragraph">This one&#8217;s subtle but important. When you grant a privilege <code>WITH GRANT OPTION</code>, you&#8217;re not just giving the role the privilege itself — you&#8217;re also letting that role grant the same privilege to others.</p>



<pre class="wp-block-code"><code>GRANT SELECT ON orders TO team_lead WITH GRANT OPTION;
</code></pre>



<p class="wp-block-paragraph">Now <code>team_lead</code> can both <code>SELECT</code> from <code>orders</code> and grant <code>SELECT</code> on <code>orders</code> to other roles. If you later decide <code>team_lead</code> shouldn&#8217;t be able to hand out this privilege anymore, but should keep their own access, use:</p>



<pre class="wp-block-code"><code>REVOKE GRANT OPTION FOR SELECT ON orders FROM team_lead;
</code></pre>



<p class="wp-block-paragraph">This removes only the ability to re-grant, while <code>team_lead</code> retains their own <code>SELECT</code> access. Compare that to a plain <code>REVOKE SELECT ON orders FROM team_lead</code>, which would take away everything — both the privilege itself and the ability to grant it onward.</p>



<h2 class="wp-block-heading">A Real-World Example: Cleaning Up an Offboarded Employee</h2>



<p class="wp-block-paragraph">Here&#8217;s a practical scenario that comes up constantly in real database administration — someone leaves the team, and you need to make sure their access is fully cleaned up.</p>



<pre class="wp-block-code"><code>-- Revoke table-level privileges across the schema
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM departing_employee;
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM departing_employee;

-- Revoke schema-level access
REVOKE ALL PRIVILEGES ON SCHEMA public FROM departing_employee;

-- Revoke database connect privilege
REVOKE CONNECT ON DATABASE production FROM departing_employee;

-- Revoke role memberships
REVOKE analytics_team, reporting_team FROM departing_employee;

-- Finally, if you're removing the role entirely (after confirming it owns no objects)
DROP ROLE departing_employee;
</code></pre>



<p class="wp-block-paragraph">Note that <code>DROP ROLE</code> will actually fail if the role still owns any database objects or has privileges granted that would leave orphaned dependencies — so working through a proper <code>REVOKE</code> cleanup first (or reassigning ownership with <code>REASSIGN OWNED BY</code>) is usually a necessary step before you can cleanly drop the role.</p>



<h2 class="wp-block-heading">Common Use Cases for REVOKE</h2>



<ol class="wp-block-list">
<li><strong>Offboarding</strong> — removing access when an employee or contractor leaves, as shown above.</li>



<li><strong>Scaling back over-provisioned access</strong> — correcting situations where a role was granted broader privileges than it actually needs.</li>



<li><strong>Temporary access cleanup</strong> — revoking privileges that were granted for a specific short-term project once that project wraps up.</li>



<li><strong>Security incident response</strong> — quickly cutting off a compromised or suspicious account&#8217;s access to sensitive data.</li>



<li><strong>Enforcing least privilege during regular audits</strong> — periodically reviewing and tightening up privilege grants across your database as part of routine security hygiene.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common REVOKE Issues</h2>



<p class="wp-block-paragraph"><strong>&#8220;REVOKE doesn&#8217;t seem to have any effect.&#8221;</strong> Check whether the role has the privilege through a different path — for example, via membership in a group role that itself has the privilege, rather than a direct grant to the individual role. Revoking a directly-granted privilege from a user won&#8217;t remove access they still have indirectly through a group role membership. You&#8217;d need to revoke from the group role, or remove the user from that group role.</p>



<p class="wp-block-paragraph"><strong>&#8220;ERROR: dependent privileges exist&#8221;</strong> This happens when you try to revoke a privilege that other grants depend on (via <code>WITH GRANT OPTION</code> chains), without specifying <code>CASCADE</code>. Either use <code>CASCADE</code> to remove the dependent grants automatically, or manually revoke them first if you want more control over exactly what gets removed.</p>



<p class="wp-block-paragraph"><strong>&#8220;I revoked a privilege but the role can still access the data.&#8221;</strong> Remember that superusers and role owners bypass normal privilege checks entirely — <code>REVOKE</code> has no effect on a superuser&#8217;s access, since superusers can access everything regardless of granted privileges. Also double check whether the role owns the table in question; object owners always retain full privileges on objects they own, regardless of <code>REVOKE</code> statements, unless ownership itself is transferred.</p>



<p class="wp-block-paragraph"><strong>&#8220;Can&#8217;t drop a role because it still has privileges or owns objects.&#8221;</strong> Use <code>REASSIGN OWNED BY old_role TO new_role;</code> to transfer ownership of objects, and <code>DROP OWNED BY old_role;</code> to clean up remaining privileges and objects before attempting <code>DROP ROLE</code>.</p>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Prefer granting privileges to group roles, not individual users</strong>, and manage access by adding or removing users from those group roles. This makes both <code>GRANT</code> and <code>REVOKE</code> operations far simpler to reason about and audit.</li>



<li><strong>Regularly audit privileges</strong>, especially in larger teams, using PostgreSQL&#8217;s <code>information_schema.role_table_grants</code> and related views to see exactly what&#8217;s been granted to whom.</li>



<li><strong>Be deliberate with CASCADE.</strong> It&#8217;s powerful but can remove more than you expect if there are chains of grants you didn&#8217;t fully account for. Consider reviewing dependent grants before using it in production.</li>



<li><strong>Follow the principle of least privilege from the start</strong>, granting only what&#8217;s needed rather than granting broadly and relying on <code>REVOKE</code> to clean up later. It&#8217;s much easier to grant additional access when needed than to hunt down and revoke over-provisioned access after the fact.</li>



<li><strong>Build offboarding into a documented, repeatable process</strong>, ideally scripted, so that revoking access for a departing team member isn&#8217;t something that depends on someone remembering every place that person was granted privileges.</li>



<li><strong>Remember object ownership and superuser status bypass REVOKE.</strong> If your goal is truly removing all access, check ownership and role attributes (<code>\du</code> in <code>psql</code>), not just granted privileges.</li>
</ul>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph"><code>REVOKE</code> doesn&#8217;t get talked about nearly as much as <code>GRANT</code>, but a database&#8217;s actual security posture depends just as much on what you take away as what you hand out in the first place. Whether you&#8217;re cleaning up after an offboarded employee, tightening an over-provisioned service account, or just doing routine access hygiene, understanding exactly how <code>REVOKE</code> interacts with group roles, grant chains, and object ownership is what separates a database that&#8217;s actually secure from one that just looks secure on the surface.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-revoke-command-in-postgresql/">How to Use the REVOKE Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-revoke-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7442</post-id>	</item>
		<item>
		<title>How to Use the GRANT Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-grant-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-grant-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:32:52 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7439</guid>

					<description><![CDATA[<p>If you&#8217;ve ever managed a PostgreSQL database with more than one user, you&#8217;ve probably run into the question&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-grant-command-in-postgresql/">How to Use the GRANT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">If you&#8217;ve ever managed a PostgreSQL database with more than one user, you&#8217;ve probably run into the question of who should be allowed to do what. Maybe you have an analyst who only needs to read data, or an application role that needs to insert and update rows but should never be allowed to drop a table. This is exactly the problem the GRANT command solves. In this guide, I&#8217;ll walk you through everything you need to know about GRANT in PostgreSQL, from the basic syntax to real-world examples, common mistakes, and best practices I&#8217;ve picked up over years of working with production databases.</p>



<h2 class="wp-block-heading">What Is the GRANT Command?</h2>



<p class="wp-block-paragraph">GRANT is a PostgreSQL command used to give specific privileges to a role (which could be a user or a group) on a database object. Database objects include tables, views, sequences, functions, schemas, and even entire databases. Without GRANT, a newly created role in PostgreSQL has almost no privileges beyond what&#8217;s granted by default to the <code>PUBLIC</code> pseudo-role, so this command is essential for setting up any kind of meaningful access control.</p>



<p class="wp-block-paragraph">Think of GRANT as the mechanism that turns PostgreSQL&#8217;s role system from an abstract idea into something functional. You can create as many roles as you like, but until you grant them privileges, they can&#8217;t actually interact with your data in useful ways.</p>



<h2 class="wp-block-heading">Basic Syntax of GRANT</h2>



<p class="wp-block-paragraph">The general syntax for granting privileges on a table looks like this:</p>



<pre class="wp-block-code"><code>GRANT privilege_type &#91;, ...] 
ON object_type object_name 
TO role_name &#91;, ...] 
&#91;WITH GRANT OPTION];
</code></pre>



<p class="wp-block-paragraph">Let&#8217;s break this down piece by piece:</p>



<ul class="wp-block-list">
<li><strong>privilege_type</strong>: This is the specific action you&#8217;re allowing, such as <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>, <code>TRUNCATE</code>, <code>REFERENCES</code>, or <code>TRIGGER</code> for tables. Different object types support different privilege types.</li>



<li><strong>object_type object_name</strong>: This specifies what you&#8217;re granting access to, like <code>TABLE employees</code>, <code>SCHEMA public</code>, or <code>DATABASE mydb</code>.</li>



<li><strong>role_name</strong>: The role (user or group) receiving the privilege.</li>



<li><strong>WITH GRANT OPTION</strong>: An optional clause that allows the receiving role to grant the same privilege to other roles.</li>
</ul>



<p class="wp-block-paragraph">Here&#8217;s a simple example:</p>



<pre class="wp-block-code"><code>GRANT SELECT ON employees TO analyst_role;
</code></pre>



<p class="wp-block-paragraph">This gives the <code>analyst_role</code> permission to read data from the <code>employees</code> table, nothing more.</p>



<h2 class="wp-block-heading">Types of Privileges You Can Grant</h2>



<p class="wp-block-paragraph">PostgreSQL supports a fairly rich set of privileges depending on the object type. Here&#8217;s a rundown of the most commonly used ones:</p>



<h3 class="wp-block-heading">Table-Level Privileges</h3>



<ul class="wp-block-list">
<li><code>SELECT</code> — read rows from a table</li>



<li><code>INSERT</code> — add new rows</li>



<li><code>UPDATE</code> — modify existing rows</li>



<li><code>DELETE</code> — remove rows</li>



<li><code>TRUNCATE</code> — quickly remove all rows</li>



<li><code>REFERENCES</code> — create foreign key constraints that reference this table</li>



<li><code>TRIGGER</code> — create triggers on the table</li>
</ul>



<h3 class="wp-block-heading">Schema-Level Privileges</h3>



<ul class="wp-block-list">
<li><code>CREATE</code> — create new objects within the schema</li>



<li><code>USAGE</code> — access objects within the schema (this one trips up a lot of beginners, more on that below)</li>
</ul>



<h3 class="wp-block-heading">Database-Level Privileges</h3>



<ul class="wp-block-list">
<li><code>CONNECT</code> — connect to the database</li>



<li><code>CREATE</code> — create new schemas within the database</li>



<li><code>TEMP</code> — create temporary tables</li>
</ul>



<h3 class="wp-block-heading">Function Privileges</h3>



<ul class="wp-block-list">
<li><code>EXECUTE</code> — run the function</li>
</ul>



<h3 class="wp-block-heading">Sequence Privileges</h3>



<ul class="wp-block-list">
<li><code>USAGE</code> — use <code>nextval()</code> and <code>currval()</code></li>



<li><code>SELECT</code> — read the current value</li>



<li><code>UPDATE</code> — change the sequence value</li>
</ul>



<h2 class="wp-block-heading">Granting Privileges on Different Object Types</h2>



<h3 class="wp-block-heading">Granting on Tables</h3>



<pre class="wp-block-code"><code>GRANT SELECT, INSERT, UPDATE ON orders TO app_user;
</code></pre>



<p class="wp-block-paragraph">This is probably the most common use case you&#8217;ll see day-to-day. It allows <code>app_user</code> to read, add, and modify rows in the <code>orders</code> table, but not delete them.</p>



<p class="wp-block-paragraph">If you want to grant all standard privileges at once, you can use the <code>ALL PRIVILEGES</code> shortcut:</p>



<pre class="wp-block-code"><code>GRANT ALL PRIVILEGES ON orders TO admin_user;
</code></pre>



<h3 class="wp-block-heading">Granting on Multiple Tables</h3>



<p class="wp-block-paragraph">Rather than running the same GRANT statement over and over for every table, you can grant on all tables in a schema:</p>



<pre class="wp-block-code"><code>GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting_role;
</code></pre>



<p class="wp-block-paragraph">This is a huge time-saver when you have dozens or hundreds of tables and want to give a role blanket read access.</p>



<h3 class="wp-block-heading">Granting on Schemas</h3>



<p class="wp-block-paragraph">A very common beginner mistake is granting table privileges without also granting <code>USAGE</code> on the containing schema. If a role doesn&#8217;t have <code>USAGE</code> on a schema, it can&#8217;t even see the objects inside it, no matter what table-level privileges it has.</p>



<pre class="wp-block-code"><code>GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_user;
</code></pre>



<h3 class="wp-block-heading">Granting on Databases</h3>



<pre class="wp-block-code"><code>GRANT CONNECT ON DATABASE mydb TO app_user;
</code></pre>



<p class="wp-block-paragraph">This allows a role to connect to a specific database. Without this, even if a role has passwords and other privileges configured, it simply won&#8217;t be able to open a connection to that database.</p>



<h3 class="wp-block-heading">Granting on Sequences</h3>



<p class="wp-block-paragraph">Sequences are often overlooked, but if your table has a <code>SERIAL</code> or <code>IDENTITY</code> column, the underlying sequence needs its own grant if you want the role to insert rows successfully:</p>



<pre class="wp-block-code"><code>GRANT USAGE, SELECT ON SEQUENCE orders_id_seq TO app_user;
</code></pre>



<h3 class="wp-block-heading">Granting on Functions</h3>



<pre class="wp-block-code"><code>GRANT EXECUTE ON FUNCTION calculate_total(integer) TO app_user;
</code></pre>



<h3 class="wp-block-heading">Granting to Multiple Roles at Once</h3>



<pre class="wp-block-code"><code>GRANT SELECT ON orders TO analyst_role, reporting_role, audit_role;
</code></pre>



<h2 class="wp-block-heading">Using Default Privileges</h2>



<p class="wp-block-paragraph">One of the more advanced but genuinely useful features related to GRANT is <code>ALTER DEFAULT PRIVILEGES</code>. Normally, GRANT only applies to objects that already exist. If you create a new table tomorrow, your previous grants won&#8217;t automatically apply to it. That&#8217;s where default privileges come in:</p>



<pre class="wp-block-code"><code>ALTER DEFAULT PRIVILEGES IN SCHEMA public 
GRANT SELECT ON TABLES TO reporting_role;
</code></pre>



<p class="wp-block-paragraph">With this in place, any new table created in the <code>public</code> schema will automatically grant <code>SELECT</code> to <code>reporting_role</code> the moment it&#8217;s created. This is incredibly useful in environments where tables are created programmatically or as part of migrations, and you don&#8217;t want to remember to run GRANT every single time.</p>



<h2 class="wp-block-heading">The WITH GRANT OPTION Clause</h2>



<p class="wp-block-paragraph">Sometimes you want a role to not just have a privilege, but to also be able to pass that privilege on to others. That&#8217;s what <code>WITH GRANT OPTION</code> is for:</p>



<pre class="wp-block-code"><code>GRANT SELECT ON employees TO team_lead WITH GRANT OPTION;
</code></pre>



<p class="wp-block-paragraph">Now <code>team_lead</code> can run their own GRANT statements to give <code>SELECT</code> on <code>employees</code> to other roles. Use this carefully, because it decentralizes control over your permission model, and it can become hard to track who granted what to whom.</p>



<h2 class="wp-block-heading">Checking Existing Privileges</h2>



<p class="wp-block-paragraph">Before and after running GRANT statements, it&#8217;s smart to verify what privileges actually exist. You can query the <code>information_schema</code> for this:</p>



<pre class="wp-block-code"><code>SELECT grantee, privilege_type 
FROM information_schema.role_table_grants 
WHERE table_name = 'employees';
</code></pre>



<p class="wp-block-paragraph">Or use the <code>\dp</code> meta-command in <code>psql</code>:</p>



<pre class="wp-block-code"><code>\dp employees
</code></pre>



<p class="wp-block-paragraph">This shows you the access privileges for the table in a compact format, listing which roles have which privileges.</p>



<h2 class="wp-block-heading">Practical Real-World Example</h2>



<p class="wp-block-paragraph">Let&#8217;s say you&#8217;re setting up a typical three-tier access model for a company database: an admin role, an application role, and a read-only reporting role.</p>



<pre class="wp-block-code"><code>-- Create the roles first
CREATE ROLE db_admin;
CREATE ROLE app_role LOGIN PASSWORD 'secure_password';
CREATE ROLE reporting_role LOGIN PASSWORD 'another_password';

-- Give admin full control
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO db_admin;

-- App role gets read/write but not destructive privileges
GRANT USAGE ON SCHEMA public TO app_role;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app_role;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app_role;

-- Reporting role only reads
GRANT USAGE ON SCHEMA public TO reporting_role;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting_role;

-- Make sure future tables also follow this pattern
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE ON TABLES TO app_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO reporting_role;
</code></pre>



<p class="wp-block-paragraph">This pattern scales well and is a good starting template for most small to medium applications.</p>



<h2 class="wp-block-heading">Common Use Cases for GRANT</h2>



<ol class="wp-block-list">
<li><strong>Application accounts</strong>: giving a backend service the minimum privileges it needs (usually SELECT, INSERT, UPDATE, and occasionally DELETE).</li>



<li><strong>Read-only analytics users</strong>: connecting BI tools like Metabase, Tableau, or Looker with a role that can only run SELECT queries.</li>



<li><strong>Third-party integrations</strong>: exposing a limited slice of your data to external contractors without giving them full database access.</li>



<li><strong>Team-based access</strong>: separating developers, DBAs, and support staff into different roles with different privilege sets.</li>



<li><strong>Auditing roles</strong>: creating a role that can read system catalogs and logs without touching application data.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common GRANT Issues</h2>



<h3 class="wp-block-heading">&#8220;Permission Denied for Table&#8221; Even After Granting</h3>



<p class="wp-block-paragraph">This is almost always a schema <code>USAGE</code> issue. Double-check that the role has <code>USAGE</code> on the schema containing the table:</p>



<pre class="wp-block-code"><code>GRANT USAGE ON SCHEMA public TO your_role;
</code></pre>



<h3 class="wp-block-heading">Grants Not Applying to New Tables</h3>



<p class="wp-block-paragraph">Remember, GRANT is not retroactive and it&#8217;s not automatically forward-looking either. If you want new tables to inherit privileges, you need <code>ALTER DEFAULT PRIVILEGES</code>, as shown earlier.</p>



<h3 class="wp-block-heading">Role Can Connect But Can&#8217;t See Any Tables</h3>



<p class="wp-block-paragraph">Check that <code>CONNECT</code> was granted on the database, and <code>USAGE</code> on the schema. It&#8217;s easy to grant table-level privileges and forget these two prerequisites.</p>



<h3 class="wp-block-heading">Sequence Errors on Insert</h3>



<p class="wp-block-paragraph">If a role can insert into a table but gets an error related to sequences, it usually means the sequence backing the SERIAL column wasn&#8217;t granted:</p>



<pre class="wp-block-code"><code>GRANT USAGE, SELECT ON SEQUENCE table_name_id_seq TO your_role;
</code></pre>



<h3 class="wp-block-heading">Privileges Granted to the Wrong Role Name</h3>



<p class="wp-block-paragraph">PostgreSQL role names are case-sensitive when quoted and case-insensitive when unquoted (they get folded to lowercase). If you created a role with mixed case using quotes, like <code>"AppUser"</code>, you must always refer to it with the exact same quoting in your GRANT statements.</p>



<h2 class="wp-block-heading">Best Practices for Using GRANT</h2>



<ul class="wp-block-list">
<li><strong>Follow the principle of least privilege</strong>: only grant what a role truly needs. It&#8217;s much easier to add a privilege later than to figure out what to revoke from an overly permissive role.</li>



<li><strong>Use roles as groups</strong>: instead of granting privileges directly to individual login roles, create group roles (like <code>readonly</code> or <code>app_writer</code>) and grant privileges to those, then add individual users as members. This makes management far simpler at scale.</li>



<li><strong>Document your grants</strong>: keep a script or migration file that tracks every GRANT statement you run, so you have a single source of truth for your permission model.</li>



<li><strong>Use default privileges for consistency</strong>: especially in schemas where tables are created frequently, <code>ALTER DEFAULT PRIVILEGES</code> saves you from privilege drift.</li>



<li><strong>Audit regularly</strong>: periodically run queries against <code>information_schema.role_table_grants</code> to review who has access to what, and revoke anything that&#8217;s no longer needed.</li>



<li><strong>Avoid overusing WITH GRANT OPTION</strong>: it&#8217;s convenient but can quickly make your access control model harder to reason about.</li>



<li><strong>Pair GRANT with REVOKE</strong>: whenever you change a role&#8217;s responsibilities, remember that GRANT only adds privileges. If a role no longer needs a privilege, you must explicitly REVOKE it.</li>
</ul>



<h2 class="wp-block-heading">GRANT vs. Role Membership</h2>



<p class="wp-block-paragraph">It&#8217;s worth clarifying a point of confusion for people newer to PostgreSQL&#8217;s permission system: GRANT is used both for privileges on objects (like SELECT on a table) and for role membership (like adding a user to a group role). The syntax looks similar but does different things:</p>



<pre class="wp-block-code"><code>-- Granting an object privilege
GRANT SELECT ON employees TO analyst_role;

-- Granting role membership
GRANT analyst_role TO jane_doe;
</code></pre>



<p class="wp-block-paragraph">In the second example, <code>jane_doe</code> becomes a member of <code>analyst_role</code>, inheriting whatever privileges that role has (assuming the role was created with <code>INHERIT</code>, which is the default). This dual use of GRANT is powerful once you understand it: you can build a hierarchy of group roles with specific privilege sets, then simply add and remove individual users from those groups as their responsibilities change, rather than managing privileges per-user.</p>



<pre class="wp-block-code"><code>CREATE ROLE readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;

CREATE ROLE jane_doe LOGIN PASSWORD 'x';
GRANT readonly TO jane_doe;
</code></pre>



<p class="wp-block-paragraph">Now <code>jane_doe</code> automatically has read access to everything <code>readonly</code> can read, and if you need to revoke her access later, you just remove her from the role rather than hunting down every individual grant.</p>



<h2 class="wp-block-heading">Granting Column-Level Privileges</h2>



<p class="wp-block-paragraph">GRANT doesn&#8217;t have to apply to an entire table. You can restrict privileges to specific columns, which is useful when a role needs to update most of a table but shouldn&#8217;t touch certain sensitive fields:</p>



<pre class="wp-block-code"><code>GRANT SELECT (id, first_name, last_name), UPDATE (first_name, last_name) 
ON employees 
TO hr_assistant_role;
</code></pre>



<p class="wp-block-paragraph">With this grant, <code>hr_assistant_role</code> can read and update names, but has no access at all to other columns like salary or social security number, even though it has some privileges on the table as a whole.</p>



<h2 class="wp-block-heading">Granting on Specific Rows with Row-Level Security</h2>



<p class="wp-block-paragraph">GRANT itself operates at the object and column level, but PostgreSQL also supports row-level security (RLS) policies for finer-grained control over which rows a role can see or modify. This is a separate feature from GRANT, but the two work together: a role first needs table-level privileges via GRANT, and then RLS policies further restrict which specific rows those privileges apply to.</p>



<pre class="wp-block-code"><code>ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY region_policy ON orders
FOR SELECT
USING (region = current_setting('app.current_region'));

GRANT SELECT ON orders TO regional_sales_role;
</code></pre>



<p class="wp-block-paragraph">Without both the GRANT and the enabled policy, access won&#8217;t work as expected, so it&#8217;s worth remembering these are complementary layers rather than alternatives to each other.</p>



<h2 class="wp-block-heading">Frequently Asked Questions</h2>



<p class="wp-block-paragraph"><strong>Does GRANT apply retroactively to objects that already exist plus future ones?</strong></p>



<p class="wp-block-paragraph">No. A standard GRANT statement only applies to the objects that exist at the time you run it. For future objects, you need <code>ALTER DEFAULT PRIVILEGES</code>, which was covered earlier in this guide.</p>



<p class="wp-block-paragraph"><strong>What happens if I grant the same privilege twice?</strong></p>



<p class="wp-block-paragraph">Nothing bad. PostgreSQL doesn&#8217;t create duplicate entries or throw an error; the privilege is simply already there, so running the same GRANT statement again is harmless.</p>



<p class="wp-block-paragraph"><strong>How do I remove a privilege I granted by mistake?</strong></p>



<p class="wp-block-paragraph">Use REVOKE, which is the direct counterpart to GRANT:</p>



<pre class="wp-block-code"><code>REVOKE SELECT ON employees FROM analyst_role;
</code></pre>



<p class="wp-block-paragraph"><strong>Can I see the exact SQL to grant privileges matching an existing role?</strong></p>



<p class="wp-block-paragraph">Yes, tools like <code>pg_dump --schema-only</code> will include GRANT statements for existing objects, which is a handy way to audit or replicate a permission setup between environments.</p>



<p class="wp-block-paragraph"><strong>Does granting a privilege on a schema automatically grant it on the tables inside?</strong></p>



<p class="wp-block-paragraph">No. Schema-level privileges like USAGE and CREATE are separate from table-level privileges like SELECT and INSERT. You need both: USAGE on the schema to see what&#8217;s inside, and explicit grants on the individual tables (or ALL TABLES IN SCHEMA) for actual data access.</p>



<p class="wp-block-paragraph"><strong>Can I grant privileges to PUBLIC instead of a specific role?</strong></p>



<p class="wp-block-paragraph">Yes, <code>GRANT SELECT ON employees TO PUBLIC;</code> grants the privilege to every role in the database, present and future. This is convenient for genuinely public reference data, but should be used sparingly, since it&#8217;s easy to forget that PUBLIC grants exist and accidentally expose more than intended. It&#8217;s generally safer to be explicit about which roles get which privileges rather than relying on PUBLIC as a catch-all.</p>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph">The GRANT command is one of the foundational tools for managing access control in PostgreSQL, and understanding it well will save you a lot of headaches as your database and team grow. Start with the principle of least privilege, use group roles to keep things organized, and don&#8217;t forget the often-missed pieces like schema <code>USAGE</code> and sequence privileges. Once you get comfortable with these patterns, setting up secure, well-organized access control in PostgreSQL becomes second nature.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-grant-command-in-postgresql/">How to Use the GRANT Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-grant-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7439</post-id>	</item>
		<item>
		<title>How to Use the DROP VIEW Command in PostgreSQL</title>
		<link>https://awjunaid.com/postgresql/how-to-use-the-drop-view-command-in-postgresql/</link>
					<comments>https://awjunaid.com/postgresql/how-to-use-the-drop-view-command-in-postgresql/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 06:31:14 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7436</guid>

					<description><![CDATA[<p>Views are one of the handiest tools in PostgreSQL for simplifying complex queries and presenting data in a&#8230;</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-drop-view-command-in-postgresql/">How to Use the DROP VIEW Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Views are one of the handiest tools in PostgreSQL for simplifying complex queries and presenting data in a cleaner way. But views, like any other database object, sometimes need to be cleaned up. Maybe a view is no longer needed, maybe it&#8217;s been replaced by a better-designed one, or maybe it&#8217;s just cluttering up your schema. That&#8217;s where the DROP VIEW command comes in. In this article, I&#8217;ll cover everything from the basic syntax to advanced scenarios involving dependencies, cascading drops, and troubleshooting.</p>



<h2 class="wp-block-heading">What Is a View, Briefly</h2>



<p class="wp-block-paragraph">Before diving into DROP VIEW, it helps to remember what a view actually is. A view is essentially a stored SQL query that you can treat like a virtual table. When you query a view, PostgreSQL runs the underlying query behind the scenes and returns the result. Views don&#8217;t store data themselves (unless you&#8217;re using a materialized view, which is a different concept), so removing a view doesn&#8217;t delete any actual data, it just removes the saved query definition.</p>



<h2 class="wp-block-heading">Basic Syntax of DROP VIEW</h2>



<p class="wp-block-paragraph">The basic syntax looks like this:</p>



<pre class="wp-block-code"><code>DROP VIEW &#91;IF EXISTS] view_name &#91;, ...] &#91;CASCADE | RESTRICT];
</code></pre>



<p class="wp-block-paragraph">Here&#8217;s what each part means:</p>



<ul class="wp-block-list">
<li><strong>IF EXISTS</strong>: prevents an error if the view doesn&#8217;t exist, which is great for scripts that need to run safely multiple times.</li>



<li><strong>view_name</strong>: the name of the view (or views, comma-separated) you want to remove.</li>



<li><strong>CASCADE</strong>: automatically drops any objects that depend on the view, such as other views built on top of it.</li>



<li><strong>RESTRICT</strong>: the default behavior, which prevents the drop if any other object depends on the view.</li>
</ul>



<h2 class="wp-block-heading">A Simple Example</h2>



<p class="wp-block-paragraph">Let&#8217;s say you created a view earlier to summarize monthly sales:</p>



<pre class="wp-block-code"><code>CREATE VIEW monthly_sales_summary AS
SELECT date_trunc('month', order_date) AS month, SUM(total_amount) AS total_sales
FROM orders
GROUP BY 1;
</code></pre>



<p class="wp-block-paragraph">If you no longer need it, you can drop it like this:</p>



<pre class="wp-block-code"><code>DROP VIEW monthly_sales_summary;
</code></pre>



<p class="wp-block-paragraph">That&#8217;s it. The view definition is removed, and any queries relying on it will now fail unless you recreate it.</p>



<h2 class="wp-block-heading">Using IF EXISTS to Avoid Errors</h2>



<p class="wp-block-paragraph">If you&#8217;re not sure whether a view exists, or you&#8217;re writing a migration script that might run in different environments, use <code>IF EXISTS</code>:</p>



<pre class="wp-block-code"><code>DROP VIEW IF EXISTS monthly_sales_summary;
</code></pre>



<p class="wp-block-paragraph">Without <code>IF EXISTS</code>, trying to drop a view that doesn&#8217;t exist throws an error like:</p>



<pre class="wp-block-code"><code>ERROR: view "monthly_sales_summary" does not exist
</code></pre>



<p class="wp-block-paragraph">With <code>IF EXISTS</code>, PostgreSQL just prints a notice and moves on, which is much friendlier for automated scripts.</p>



<h2 class="wp-block-heading">Dropping Multiple Views at Once</h2>



<p class="wp-block-paragraph">You can drop several views in a single statement by separating their names with commas:</p>



<pre class="wp-block-code"><code>DROP VIEW IF EXISTS monthly_sales_summary, yearly_sales_summary, quarterly_sales_summary;
</code></pre>



<p class="wp-block-paragraph">This is convenient during cleanup operations when you&#8217;re removing a batch of related views at once, rather than writing separate statements for each.</p>



<h2 class="wp-block-heading">Understanding CASCADE and RESTRICT</h2>



<p class="wp-block-paragraph">This is where DROP VIEW gets a little more nuanced, and where a lot of people run into trouble.</p>



<p class="wp-block-paragraph">By default, PostgreSQL uses <code>RESTRICT</code> behavior, meaning it will refuse to drop a view if something else depends on it. For example, if you have a view called <code>active_customers</code> and another view called <code>active_customers_with_orders</code> that&#8217;s built on top of it, trying to drop <code>active_customers</code> without CASCADE will give you an error:</p>



<pre class="wp-block-code"><code>DROP VIEW active_customers;
</code></pre>



<pre class="wp-block-code"><code>ERROR: cannot drop view active_customers because other objects depend on it
DETAIL: view active_customers_with_orders depends on view active_customers
HINT: Use DROP ... CASCADE to drop the dependent objects too.
</code></pre>



<p class="wp-block-paragraph">If you actually want to remove both the view and everything depending on it, you use CASCADE:</p>



<pre class="wp-block-code"><code>DROP VIEW active_customers CASCADE;
</code></pre>



<p class="wp-block-paragraph">Be very careful with CASCADE. It doesn&#8217;t just warn you about dependent objects, it actually deletes them. If <code>active_customers_with_orders</code> was an important view that other parts of your application relied on, CASCADE will remove it silently as part of the operation, and you&#8217;ll only find out when something downstream breaks.</p>



<h2 class="wp-block-heading">Checking Dependencies Before Dropping</h2>



<p class="wp-block-paragraph">Before running a CASCADE drop, it&#8217;s wise to check what actually depends on the view. You can query the system catalogs for this:</p>



<pre class="wp-block-code"><code>SELECT dependent_ns.nspname AS dependent_schema,
       dependent_view.relname AS dependent_view
FROM pg_depend 
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid 
JOIN pg_class AS dependent_view ON pg_rewrite.ev_class = dependent_view.oid 
JOIN pg_class AS source_view ON pg_depend.refobjid = source_view.oid 
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
WHERE source_view.relname = 'active_customers';
</code></pre>



<p class="wp-block-paragraph">This query lists any views (or other objects) that depend on <code>active_customers</code>, so you know exactly what will be affected before you decide whether to use CASCADE or handle each dependent object manually.</p>



<h2 class="wp-block-heading">Dropping a View and Recreating It</h2>



<p class="wp-block-paragraph">A very common pattern in development is dropping and recreating a view when its definition changes. You can do this in two ways:</p>



<p class="wp-block-paragraph"><strong>Option 1: Drop then create</strong></p>



<pre class="wp-block-code"><code>DROP VIEW IF EXISTS monthly_sales_summary;

CREATE VIEW monthly_sales_summary AS
SELECT date_trunc('month', order_date) AS month, SUM(total_amount) AS total_sales
FROM orders
GROUP BY 1;
</code></pre>



<p class="wp-block-paragraph"><strong>Option 2: CREATE OR REPLACE VIEW</strong></p>



<pre class="wp-block-code"><code>CREATE OR REPLACE VIEW monthly_sales_summary AS
SELECT date_trunc('month', order_date) AS month, SUM(total_amount) AS total_sales, COUNT(*) AS order_count
FROM orders
GROUP BY 1;
</code></pre>



<p class="wp-block-paragraph">The second option is often preferable because it preserves permissions granted on the view and doesn&#8217;t require dropping dependent objects, as long as you&#8217;re only adding columns and not removing or renaming existing ones. If you try to remove a column or change a column&#8217;s data type with <code>CREATE OR REPLACE VIEW</code>, PostgreSQL will throw an error, and you&#8217;ll need to actually drop and recreate the view instead.</p>



<h2 class="wp-block-heading">Dropping Materialized Views</h2>



<p class="wp-block-paragraph">It&#8217;s worth noting that DROP VIEW only works on regular views. If you&#8217;re working with a materialized view (created with <code>CREATE MATERIALIZED VIEW</code>), you need a different command:</p>



<pre class="wp-block-code"><code>DROP MATERIALIZED VIEW IF EXISTS my_materialized_view;
</code></pre>



<p class="wp-block-paragraph">Trying to use <code>DROP VIEW</code> on a materialized view will give you an error telling you it&#8217;s the wrong object type.</p>



<h2 class="wp-block-heading">Permissions Required to Drop a View</h2>



<p class="wp-block-paragraph">To drop a view, you generally need to be the owner of the view, or be a superuser, or have been granted appropriate permissions. If you try to drop a view you don&#8217;t own and don&#8217;t have privileges for, you&#8217;ll see something like:</p>



<pre class="wp-block-code"><code>ERROR: must be owner of view monthly_sales_summary
</code></pre>



<p class="wp-block-paragraph">If you need to drop a view owned by another role, you can either have that role drop it, ask a superuser to do it, or use <code>ALTER VIEW ... OWNER TO</code> to change ownership first (if you have permission to do that).</p>



<h2 class="wp-block-heading">Common Use Cases for DROP VIEW</h2>



<ol class="wp-block-list">
<li><strong>Cleaning up during development</strong>: removing throwaway views you created for testing or exploration.</li>



<li><strong>Schema refactoring</strong>: replacing an old view structure with a redesigned one, especially when column changes make <code>CREATE OR REPLACE VIEW</code> insufficient.</li>



<li><strong>Removing deprecated reporting views</strong>: as business requirements change, old reporting views become obsolete and should be cleaned up to avoid confusion.</li>



<li><strong>Migration scripts</strong>: dropping views as part of a versioned migration process before recreating them with updated logic.</li>



<li><strong>Reducing schema clutter</strong>: in large databases, unused views accumulate over time and make it harder to understand what&#8217;s actually in use.</li>
</ol>



<h2 class="wp-block-heading">Troubleshooting Common Issues</h2>



<h3 class="wp-block-heading">&#8220;View Does Not Exist&#8221; Error</h3>



<p class="wp-block-paragraph">Double check the spelling and the schema. If the view lives in a non-default schema, you need to qualify it:</p>



<pre class="wp-block-code"><code>DROP VIEW IF EXISTS reporting.monthly_sales_summary;
</code></pre>



<h3 class="wp-block-heading">&#8220;Cannot Drop View Because Other Objects Depend On It&#8221;</h3>



<p class="wp-block-paragraph">This means you need to either use CASCADE (after checking what will be affected) or manually drop the dependent objects first in the correct order.</p>



<h3 class="wp-block-heading">Accidentally Dropped an Important View</h3>



<p class="wp-block-paragraph">Since DROP VIEW doesn&#8217;t have a built-in undo, your best recovery option is to have the view&#8217;s CREATE statement saved somewhere, like in version control, a migration file, or a schema backup. This is a strong argument for always keeping your view definitions in source control rather than only in the live database.</p>



<h3 class="wp-block-heading">Permission Denied</h3>



<p class="wp-block-paragraph">Make sure you&#8217;re connected as the view owner or a role with sufficient privileges, or ask someone with the right access to run the drop for you.</p>



<h2 class="wp-block-heading">Best Practices for Using DROP VIEW</h2>



<ul class="wp-block-list">
<li><strong>Always check dependencies first</strong>: before using CASCADE, run a dependency check so you know exactly what else will be removed.</li>



<li><strong>Use IF EXISTS in scripts</strong>: this makes your migration and deployment scripts idempotent, meaning they can be run multiple times safely.</li>



<li><strong>Keep view definitions in version control</strong>: store your <code>CREATE VIEW</code> statements in your codebase so you can always recreate a view if it&#8217;s dropped by mistake.</li>



<li><strong>Prefer CREATE OR REPLACE VIEW when possible</strong>: it&#8217;s generally safer and preserves grants, so reserve DROP VIEW for cases where you truly need to remove a view or make structural changes that replace can&#8217;t handle.</li>



<li><strong>Be cautious with CASCADE in production</strong>: consider testing the cascade behavior in a staging environment first, or manually reviewing and dropping dependent views one at a time for more control.</li>



<li><strong>Document why a view was dropped</strong>: especially in team environments, a quick note in your migration history about why a view was removed can save someone confusion months down the line.</li>
</ul>



<h2 class="wp-block-heading">DROP VIEW in Migration Frameworks</h2>



<p class="wp-block-paragraph">If you&#8217;re using a migration framework like Flyway, Liquibase, Sqitch, or a custom migration runner built into your application framework, DROP VIEW statements typically live inside &#8220;down&#8221; or &#8220;rollback&#8221; migration files, paired with the corresponding CREATE VIEW in the &#8220;up&#8221; migration. This lets you version your schema changes and roll them back cleanly if something goes wrong:</p>



<pre class="wp-block-code"><code>-- up_003_create_sales_views.sql
CREATE VIEW monthly_sales_summary AS
SELECT date_trunc('month', order_date) AS month, SUM(total_amount) AS total_sales
FROM orders
GROUP BY 1;

-- down_003_create_sales_views.sql
DROP VIEW IF EXISTS monthly_sales_summary;
</code></pre>



<p class="wp-block-paragraph">Keeping this discipline, where every CREATE has a matching DROP in a rollback script, makes your schema changes much safer to test, deploy, and revert if needed.</p>



<h2 class="wp-block-heading">Comparing DROP VIEW to TRUNCATE and DELETE</h2>



<p class="wp-block-paragraph">New PostgreSQL users sometimes get confused about the difference between removing a view and removing data. It&#8217;s worth being explicit about this distinction:</p>



<ul class="wp-block-list">
<li><code>DROP VIEW</code> removes the saved query definition. No underlying data is touched at all, since views don&#8217;t store data themselves.</li>



<li><code>DELETE FROM table</code> removes rows from an actual table, but the table structure remains.</li>



<li><code>TRUNCATE table</code> quickly removes all rows from a table, again leaving the structure intact.</li>



<li><code>DROP TABLE</code> removes both the structure and the data of an actual table.</li>
</ul>



<p class="wp-block-paragraph">If your goal is just to get rid of a saved query shortcut and you&#8217;re worried about losing data, you can rest easy: dropping a view is a purely cosmetic operation from the perspective of your actual stored data.</p>



<h2 class="wp-block-heading">Scripting Safe View Cleanup</h2>



<p class="wp-block-paragraph">When cleaning up a batch of unused views across a large schema, it helps to first generate a list of all views and their dependencies before touching anything:</p>



<pre class="wp-block-code"><code>SELECT schemaname, viewname 
FROM pg_views 
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY schemaname, viewname;
</code></pre>



<p class="wp-block-paragraph">From there, you can cross-reference against your application&#8217;s query logs or codebase to identify which views are actually still being used, and build a safe drop script for the rest. This kind of audit is worth doing periodically, since views tend to accumulate over the lifetime of a project, and it&#8217;s easy to lose track of which ones matter.</p>



<h2 class="wp-block-heading">Handling Ownership Changes Before Dropping</h2>



<p class="wp-block-paragraph">In team environments, it&#8217;s common for a view to be owned by whoever happened to create it, which might be an individual developer&#8217;s personal account rather than a shared application role. Before you can drop a view owned by someone else, you either need that person to run the drop, need superuser access, or need ownership transferred to you or your role first:</p>



<pre class="wp-block-code"><code>ALTER VIEW monthly_sales_summary OWNER TO db_admin;
DROP VIEW monthly_sales_summary;
</code></pre>



<p class="wp-block-paragraph">This is a common friction point in growing teams, so it&#8217;s worth establishing early that important, shared views should be owned by a dedicated application or admin role rather than an individual&#8217;s personal login, precisely to avoid this kind of blocker later on.</p>



<h2 class="wp-block-heading">Cleaning Up Views Tied to Deprecated Reporting Tools</h2>



<p class="wp-block-paragraph">A very practical scenario worth mentioning: many organizations accumulate views created specifically to feed a particular BI tool or reporting dashboard. When that tool gets replaced, the views built for it often get forgotten and left behind. A good habit is tagging or naming such views clearly, like <code>bi_tableau_monthly_summary</code>, so that when the tool is eventually retired, it&#8217;s easy to search for and clean up every view associated with it:</p>



<pre class="wp-block-code"><code>SELECT viewname FROM pg_views WHERE viewname LIKE 'bi_tableau_%';
</code></pre>



<p class="wp-block-paragraph">From there, a batch DROP VIEW statement, ideally wrapped in a transaction so you can roll back if something looks wrong, makes the cleanup straightforward:</p>



<pre class="wp-block-code"><code>BEGIN;
DROP VIEW IF EXISTS bi_tableau_monthly_summary, bi_tableau_quarterly_summary CASCADE;
-- review results, then COMMIT or ROLLBACK
COMMIT;
</code></pre>



<p class="wp-block-paragraph">Wrapping destructive DDL operations in an explicit transaction like this gives you a chance to inspect the outcome (via subsequent SELECT queries against related tables) before finalizing the change, and to back out cleanly with ROLLBACK if something unexpected happened.</p>



<h2 class="wp-block-heading">Frequently Asked Questions</h2>



<p class="wp-block-paragraph"><strong>Can I drop a view that&#8217;s referenced inside a function?</strong></p>



<p class="wp-block-paragraph">If a function references a view in its body, PostgreSQL generally does not track that as a hard dependency the way it does for other views, since function bodies aren&#8217;t parsed for dependencies in the same way. This means DROP VIEW might succeed even though a function still references it, and that function will simply fail at runtime the next time it&#8217;s called. Always check your codebase for references, not just database-level dependencies.</p>



<p class="wp-block-paragraph"><strong>Will dropping a view free up disk space?</strong></p>



<p class="wp-block-paragraph">Not meaningfully, since views don&#8217;t store data. There might be a tiny amount of catalog space freed, but don&#8217;t expect any measurable difference in your database&#8217;s disk usage.</p>



<p class="wp-block-paragraph"><strong>Is there a way to temporarily disable a view without dropping it?</strong></p>



<p class="wp-block-paragraph">Not directly, no. Views don&#8217;t have an enable/disable mechanism like triggers do. If you need to temporarily &#8220;turn off&#8221; a view, your options are to drop and later recreate it, or use REVOKE to remove access to it temporarily without actually deleting the definition.</p>



<p class="wp-block-paragraph"><strong>What happens to permissions if I drop and recreate a view?</strong></p>



<p class="wp-block-paragraph">Permissions granted directly on the view are lost when you drop it and need to be re-granted after recreating it. This is one of the reasons <code>CREATE OR REPLACE VIEW</code> is often preferred over drop-and-recreate, since it preserves existing grants.</p>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p class="wp-block-paragraph">DROP VIEW is a straightforward command on the surface, but the real complexity lives in managing dependencies safely, especially once your database has layers of views built on top of other views. Get comfortable with checking dependencies before you drop anything, use IF EXISTS for safer scripting, and lean on CREATE OR REPLACE VIEW when you&#8217;re just updating logic rather than truly needing to remove an object. With those habits in place, you&#8217;ll rarely be caught off guard by a DROP VIEW statement.</p>
<p>The post <a href="https://awjunaid.com/postgresql/how-to-use-the-drop-view-command-in-postgresql/">How to Use the DROP VIEW Command in PostgreSQL</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/postgresql/how-to-use-the-drop-view-command-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7436</post-id>	</item>
	</channel>
</rss>
