{"id":297,"date":"2026-05-24T08:12:46","date_gmt":"2026-05-24T06:12:46","guid":{"rendered":"https:\/\/sandrock.co.za\/carl\/?p=297"},"modified":"2026-05-24T08:12:46","modified_gmt":"2026-05-24T06:12:46","slug":"my-postgresql-cli-setup","status":"publish","type":"post","link":"https:\/\/sandrock.co.za\/carl\/2026\/05\/my-postgresql-cli-setup\/","title":{"rendered":"My PostgreSQL CLI setup"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Motivatation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">My database of choice is PostgreSQL. I often find myself wanting to do quick ad-hoc queries against a set of well-defined databases. Maybe I just want to know the internal id of a customer from their name, or check on the status of a job.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If I&#8217;m doing real reporting and producing outputs I will usually spin up PyCharm. It&#8217;s got amazing db support, which is the same as you would get from JetBrain&#8217;s dedicated database product DataGrip. Great ergonomics, but Pycharm takes a while to get started and frankly the setup of all our dbs is onerous enough that I haven&#8217;t registered all of them. I also use <a href=\"https:\/\/www.pgadmin.org\">pgAdmin<\/a>, but that&#8217;s also tedious to set up &#8220;just right&#8221;. And sometimes when you&#8217;re already in a terminal mindset, you don&#8217;t want to jump out to a GUI.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So I used to ssh into our host and fire up psql. I got laggy input and a bit of a startup-delay due to jumping through our bastion, but it worked well enough that I found myself staring at that no-frills psql prompt a lot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But I kept thinking it could be better. I was right. After a few hours of ChatGPT and self-directed browsing and experimentation, I ended up assembling a surprisingly nice terminal-based workflow using:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PostgreSQL service configs<\/li>\n\n\n\n<li><code>pgcli<\/code><\/li>\n\n\n\n<li><code>pspg<\/code><\/li>\n\n\n\n<li><code>duckdb<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The result feels very \u201cclose-to-hand\u201d: fast startup, pleasant interaction, integrated with the terminal environment, and has opened up a powerful cross-database workflow.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">PostgreSQL Service Definitions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL already has a built-in mechanism for naming database connections: <code>~\/.pg_service.conf<\/code><\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>#~\/.pg_service.conf\n&#91;prod-db1&#93;\nhost=localhost\nport=port1\ndbname=prod_db1\nuser=readonly_prod_db1\nsslmode=prefer\n\n&#91;dev-db1&#93;\nhost=localhost\nport=port2\ndbname=dev_db1\nuser=readonly_dev_db1\nsslmode=prefer\n\n&#91;prod-db2&#93;\nhost=localhost\nport=port3\ndbname=prod_db2\nuser=readonly_prod_db2\nsslmode=prefer<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #586E75; font-style: italic\">#~\/.pg_service.conf<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">&#91;prod-db1&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">host<\/span><span style=\"color: #839496\">=localhost<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">port<\/span><span style=\"color: #839496\">=port1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">dbname<\/span><span style=\"color: #839496\">=prod_db1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">user<\/span><span style=\"color: #839496\">=readonly_prod_db1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">sslmode<\/span><span style=\"color: #839496\">=prefer<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #839496\">&#91;dev-db1&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">host<\/span><span style=\"color: #839496\">=localhost<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">port<\/span><span style=\"color: #839496\">=port2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">dbname<\/span><span style=\"color: #839496\">=dev_db1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">user<\/span><span style=\"color: #839496\">=readonly_dev_db1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">sslmode<\/span><span style=\"color: #839496\">=prefer<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #839496\">&#91;prod-db2&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">host<\/span><span style=\"color: #839496\">=localhost<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">port<\/span><span style=\"color: #839496\">=port3<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">dbname<\/span><span style=\"color: #839496\">=prod_db2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">user<\/span><span style=\"color: #839496\">=readonly_prod_db2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">sslmode<\/span><span style=\"color: #839496\">=prefer<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This lets you connect using <\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>psql service=prod-db1<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #268BD2\">psql<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">service=prod-db1<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve set up port forwarding for our dbs so that the different servers are reachable through different ports. The nice thing about a text-based system like this is you can create the entries programmatically, like if you have dev, qa and prod versions of all these dbs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Password Management<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL uses <code>~\/.pgpass<\/code> for password lookup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>#~\/.pgpass\n#host:port:dbname:username:password\nlocalhost:port1:prod_db1:readonly_prod_db1:password1\nlocalhost:port2:dev_db1:readonly_dev_db1:password2\nlocalhost:port3:prod_db2:readonly_prod_db2:password3<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #586E75; font-style: italic\">#~\/.pgpass<\/span><\/span>\n<span class=\"line\"><span style=\"color: #586E75; font-style: italic\">#host:port:dbname:username:password<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">localhost:port1:prod_db1:readonly_prod_db1:password1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">localhost:port2:dev_db1:readonly_dev_db1:password2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">localhost:port3:prod_db2:readonly_prod_db2:password3<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Postgres won&#8217;t read from this if you don&#8217;t set these permissions:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>chmod 600 ~\/.pgpass<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #268BD2\">chmod<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #D33682\">600<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">~\/.pgpass<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Once configured, authentication becomes automatic. If it freaks you out to have passwords stored at rest like this, you can generate&nbsp;<code>.pgpass<\/code>&nbsp;on the fly and pass it through as an environment variable to a local terminal, too.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A Better Interactive Client: pgcli<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>psql<\/code>\u00a0is powerful and reliable, but\u00a0<code><a href=\"https:\/\/www.pgcli.com\/\">pgcli<\/a><\/code>\u00a0makes interactive querying much more pleasant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install with Homebrew:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>brew install pgcli<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #268BD2\">brew<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">install<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">pgcli<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>pgcli service=prod-db1<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #268BD2\">pgcli<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">service=prod-db1<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This gives you quite a nice interactive experience. As-you-type syntax highlighting, a visible autocomplete, even for those hard-to-remember backslash psql commands, great history searching:<\/p>\n\n\n\n<figure class=\"wp-block-image alignwide size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"185\" src=\"https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1024x185.png\" alt=\"\" class=\"wp-image-298\" srcset=\"https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1024x185.png 1024w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-300x54.png 300w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-768x139.png 768w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1536x278.png 1536w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image.png 1912w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"429\" src=\"https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1-1024x429.png\" alt=\"\" class=\"wp-image-299\" srcset=\"https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1-1024x429.png 1024w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1-300x126.png 300w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1-768x322.png 768w, https:\/\/sandrock.co.za\/carl\/wp-content\/uploads\/sites\/2\/2026\/05\/image-1.png 1294w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I can&#8217;t say enough good things about pgcli (bonus: check out litecli which is the same thing for sqlite).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I also edit <code>~\/.config\/pgcli\/config<\/code>, which in the default homebrew install gets set to a nicely commented default config, to set <code>table_format = fancygrid<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A Better Pager: pspg<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Another huge win is having a better pager. Normally, large query results in&nbsp;<code>psql<\/code>&nbsp;are paged through&nbsp;<code>less<\/code>, which is not very pleasant for tabular data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code><a href=\"https:\/\/github.com\/okbob\/pspg\">pspg<\/a><\/code>\u00a0is a terminal pager specifically designed for SQL tables.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install using Homebrew:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>brew install pspg<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #268BD2\">brew<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">install<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">pspg<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Configure in&nbsp;<code>~\/.psqlrc<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># ~\/.psqlrc\n# Some other config I like\n\\pset linestyle unicode\n\\pset border 2\n\\pset null \u2205\n\n# The actual pager\n\\setenv PSQL_PAGER 'pspg'<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #586E75; font-style: italic\"># ~\/.psqlrc<\/span><\/span>\n<span class=\"line\"><span style=\"color: #586E75; font-style: italic\"># Some other config I like<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">\\pset<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">linestyle<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">unicode<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">\\pset<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">border<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #D33682\">2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">\\pset<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">null<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">\u2205<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #586E75; font-style: italic\"># The actual pager<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">\\setenv<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">PSQL_PAGER<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">&#39;pspg&#39;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This gives you a table-aware browser instead of a line-based pager. It freezes headers, allows scrolling and searching in straightforward ways and has a built-in save-to-csv feature (I can never remember the syntax for outputing to csv in postgres). <code>pgcli<\/code> also supports this by setting <code>pager = pspg<\/code> in the config<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Themes and Colors<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The default coloring in\u00a0<code>pspg<\/code>\u00a0doesn&#8217;t quite match my preferences. Fortunately it has configuration in <code>~\/.pspgconf<\/code><\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># ~\/.pspgconf\ntheme = 17 # Solarized Dark\nborder_type = 2\nforce_uniborder = true\nignore_case = true\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #586E75; font-style: italic\"># ~\/.pspgconf<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">theme<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">=<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #D33682\">17<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #586E75; font-style: italic\"># Solarized Dark<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">border_type<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">=<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #D33682\">2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">force_uniborder<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">=<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #B58900\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #268BD2\">ignore_case<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">=<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #B58900\">true<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">More theme information is available from their <a href=\"https:\/\/github.com\/okbob\/pspg\">github page<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Maybe an even better pager: visidata<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As I was writing this, I came across an intriguing idea, using <a href=\"https:\/\/www.visidata.org\">visidata<\/a> as a pager. This gives you even more interactivity, but has a steeper learning curve. I&#8217;ve been using visidata for a while for quick CSV viewing, and this intrigued me, I might do a different post on those results. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">DuckDB Changes Everything<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The biggest surprise for me was how nicely <a href=\"https:\/\/duckdb.org\/\">DuckDB<\/a> integrates into this workflow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DuckDB can attach PostgreSQL databases directly:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>LOAD postgres;\n\nATTACH 'service=prod-db1'\nAS prod_db1\n(TYPE postgres, READ_ONLY);\n\nATTACH 'service=prod-db2'\nAS prod_db2\n(TYPE postgres, READ_ONLY);<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #859900\">LOAD<\/span><span style=\"color: #839496\"> postgres;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #859900\">ATTACH<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">&#39;service=prod-db1&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">AS<\/span><span style=\"color: #839496\"> prod_db1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">(<\/span><span style=\"color: #859900\">TYPE<\/span><span style=\"color: #839496\"> postgres, <\/span><span style=\"color: #859900\">READ_ONLY<\/span><span style=\"color: #839496\">);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #859900\">ATTACH<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">&#39;service=prod-db2&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">AS<\/span><span style=\"color: #839496\"> prod_db2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">(<\/span><span style=\"color: #859900\">TYPE<\/span><span style=\"color: #839496\"> postgres, <\/span><span style=\"color: #859900\">READ_ONLY<\/span><span style=\"color: #839496\">);<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">At that point you can query across completely separate PostgreSQL databases:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>SELECT\n    a.field1,\n    b.field2\nFROM prod_db1.public.table1 a\nJOIN prod_db2.public.table2 b\n    ON a.id = b.foreign_id;<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #859900\">SELECT<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">    <\/span><span style=\"color: #CB4B16\">a<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">field1<\/span><span style=\"color: #839496\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">    <\/span><span style=\"color: #CB4B16\">b<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">field2<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">FROM<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #CB4B16\">prod_db1<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">public<\/span><span style=\"color: #839496\">.table1 a<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">JOIN<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #CB4B16\">prod_db2<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">public<\/span><span style=\"color: #839496\">.table2 b<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">    <\/span><span style=\"color: #859900\">ON<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #CB4B16\">a<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">id<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #859900\">=<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #CB4B16\">b<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">foreign_id<\/span><span style=\"color: #839496\">;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s an astonishingly powerful capability for ad-hoc investigation work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DuckDB&#8217;s default output and paging is already pretty nice, but they <a href=\"https:\/\/github.com\/duckdb\/duckdb\/pull\/19676\">support custom pagers<\/a>, too.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lazy Database Attachment<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">One downside of having all the attach statements in one file is that DuckDB will connect to all of them on startup. So I ended up using small helper files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>-- ~\/.duckdb\/prod-db1.sql\n\nLOAD postgres;\n\nATTACH IF NOT EXISTS 'service=prod-db1'\nAS prod_db1\n(TYPE postgres, READ_ONLY);\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #586E75; font-style: italic\">-- ~\/.duckdb\/prod-db1.sql<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #859900\">LOAD<\/span><span style=\"color: #839496\"> postgres;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #859900\">ATTACH<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #859900\">IF<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #859900\">NOT<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #859900\">EXISTS<\/span><span style=\"color: #839496\"> <\/span><span style=\"color: #2AA198\">&#39;service=prod-db1&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #859900\">AS<\/span><span style=\"color: #839496\"> prod_db1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #839496\">(<\/span><span style=\"color: #859900\">TYPE<\/span><span style=\"color: #839496\"> postgres, <\/span><span style=\"color: #859900\">READ_ONLY<\/span><span style=\"color: #839496\">);<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Then inside DuckDB:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-Fira-Code\" style=\"font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#839496;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>.read ~\/.duckdb\/prod-db1.sql<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki solarized-dark\" style=\"background-color: #002B36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #839496\">.<\/span><span style=\"color: #859900\">read<\/span><span style=\"color: #839496\"> ~<\/span><span style=\"color: #859900\">\/<\/span><span style=\"color: #839496\">.duckdb<\/span><span style=\"color: #859900\">\/<\/span><span style=\"color: #839496\">prod<\/span><span style=\"color: #859900\">-<\/span><span style=\"color: #CB4B16\">db1<\/span><span style=\"color: #839496\">.<\/span><span style=\"color: #CB4B16\">sql<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This gives explicit, lazy attachment only when needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final word<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This all took a bit of time to reserach and implement, even with ChatGPT giving me a lot of help. I&#8217;m reminded of the first time I spent some time customising my zsh.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For quick investigative work, it has really made some of the older parts of my workflow feel new again.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Motivatation My database of choice is PostgreSQL. I often find myself wanting to do quick ad-hoc queries against a set of well-defined databases. Maybe I just want to know the internal id of a customer from their name, or check on the status of a job. If I&#8217;m doing real reporting and producing outputs I [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-297","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/posts\/297","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/comments?post=297"}],"version-history":[{"count":1,"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/posts\/297\/revisions"}],"predecessor-version":[{"id":300,"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/posts\/297\/revisions\/300"}],"wp:attachment":[{"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/media?parent=297"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/categories?post=297"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sandrock.co.za\/carl\/wp-json\/wp\/v2\/tags?post=297"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}