<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sql on Robin's blog</title><link>https://kaveland.no/tags/sql/</link><description>Recent content in Sql on Robin's blog</description><generator>Hugo -- 0.145.0</generator><language>en-us</language><lastBuildDate>Wed, 28 May 2025 19:30:00 +0200</lastBuildDate><atom:link href="https://kaveland.no/tags/sql/index.xml" rel="self" type="application/rss+xml"/><item><title>Using SQL to turn all the buses around</title><link>https://kaveland.no/posts/2025-05-28-turning-the-bus-sql/</link><pubDate>Wed, 28 May 2025 19:30:00 +0200</pubDate><guid>https://kaveland.no/posts/2025-05-28-turning-the-bus-sql/</guid><description>&lt;p>I have a small hobby project over at &lt;a href="https://kollektivkart.arktekk.no/">kollektivkart.arktekk.no&lt;/a> that is for visualizing changes in public transit in Norway. For some time I&amp;rsquo;ve been wanting to do some visualizations on public transit lines. For example, plot the mean delay at each stop used by a line over time.&lt;/p>
&lt;p>When trying to do some concept work on this, I discovered a puzzle in the data! Many lines go in two opposite directions. Here in Trondheim, Line 3 goes from Loholt to Hallset, but also from Hallset to Loholt. The way I can tell these apart is to look up the &lt;em>direction&lt;/em> in the data. Within a line, there can be variations in each direction. Some services might skip some stops, or depending on how you look at it, others visit extra stops. But these are variations on a theme, and it probably makes sense to group them together to preserve our sanity and not get 12 different plots for each line—2 should be plenty!&lt;/p></description></item><item><title>That join sure is a natural</title><link>https://kaveland.no/posts/2025-04-30-that-join-is-natural/</link><pubDate>Wed, 30 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-30-that-join-is-natural/</guid><description>&lt;p>Working with SQL can sometimes be painful, &lt;em>especially&lt;/em> when you have composite keys and many tables to join. Today I want to write a helpful tip for designing data models with such keys, to make it less painful to handwrite SQL for them.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>&lt;em>TIP:&lt;/em>&lt;/strong> Introduce a consistent naming standard for all columns that take part in a primary key, so that the column has the same name in all tables it is used, also where it&amp;rsquo;s on the referencing side of a foreign key.&lt;/p></description></item><item><title>Checking SQL migrations with eugene</title><link>https://kaveland.no/posts/2025-04-16-whats-up-eugene/</link><pubDate>Wed, 16 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-16-whats-up-eugene/</guid><description>&lt;p>It’s been almost a year since I last posted an update on &lt;a href="https://github.com/kaaveland/eugene/">eugene&lt;/a>, the CLI tool I’m building to help people write safer SQL migration scripts for postgres. I announced this tool in &lt;a href="https://kaveland.no/posts/2024-05-06-careful-with-that-lock-eugene-pt-2">Careful with That Lock, Eugene: Part 2&lt;/a>. At the time, &lt;code>eugene&lt;/code> would execute a single SQL script, recording all the locks acquired and warn about possible downtime due to migrations.&lt;/p>
&lt;p>It could produce JSON suitable for automated tooling and Markdown suitable for human reading and using in CI comments/checks. That version was already good enough for me to start using in real projects — but it&amp;rsquo;s improved a lot since then, it&amp;rsquo;s now easy to run with almost no setup.&lt;/p></description></item><item><title>Finding foreign keys missing indexes</title><link>https://kaveland.no/posts/2025-04-04-finding-missing-indexes-in-pg-catalog/</link><pubDate>Fri, 04 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-04-finding-missing-indexes-in-pg-catalog/</guid><description>&lt;p>Last week I was made aware that we had some foreign keys not backed by indexes in the system we&amp;rsquo;re developing at work. Foreign keys in postgres must be backed by an index only on the side they refer &lt;em>to&lt;/em>, not necessarily the side they refer &lt;em>from&lt;/em>. Here&amp;rsquo;s an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">generated&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">always&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">identity&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">primary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">generated&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">always&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">identity&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">primary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">references&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this example, there&amp;rsquo;s a foreign key from the &lt;code>book&lt;/code> table to the &lt;code>author&lt;/code> table. Since &lt;code>author&lt;/code> refers to a primary key in the &lt;code>author&lt;/code> table, inserts into &lt;code>book&lt;/code> can validate very quickly. There&amp;rsquo;s no index on the &lt;code>author&lt;/code> column in the &lt;code>book&lt;/code> table though. The consequence of this is that &lt;code>delete&lt;/code> on &lt;code>author&lt;/code> must check every single row in &lt;code>book&lt;/code> to check if it&amp;rsquo;s safe to actually delete. The really annoying part of this is that the scan does not show up in query plans:&lt;/p></description></item><item><title>Why would I use DuckDB for that?</title><link>https://kaveland.no/posts/2025-03-02-can-i-just-use-postgres/</link><pubDate>Sun, 02 Mar 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-03-02-can-i-just-use-postgres/</guid><description>&lt;p>The past few weeks I&amp;rsquo;ve been experimenting with &lt;a href="https://duckdb.org/">DuckDB&lt;/a>, and as a consequence I&amp;rsquo;ve ended up talking about it a lot as well. I&amp;rsquo;m not going to lie, I really like it! However, experienced programmers will rightly be skeptical to add new technology that overlaps with something that already works great. So why not just use postgres?&lt;/p>
&lt;p>Well, I really like postgres too, and I think you should consider just using it! But despite both of these technologies being all about tabular data, they&amp;rsquo;re not really for the same kinds of problems. I think DuckDB is primarily an analysis or ELT tool, and it really excels in this space. postgres &lt;em>can&lt;/em> do a lot of the things that DuckDB can do, but not nearly as fast or easily. I wouldn&amp;rsquo;t want to use DuckDB for a transactional workload, so it&amp;rsquo;s not going to replace postgres for anything that I use it for.&lt;/p></description></item><item><title>Consider using array operators over the SQL in operator</title><link>https://kaveland.no/posts/2024-09-21-equals-any-over-where-in/</link><pubDate>Sat, 21 Sep 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-09-21-equals-any-over-where-in/</guid><description>&lt;p>In my post about &lt;a href="https://kaveland.no/posts/2024-08-30-multi-selecting-by-composite-key/">batch operations&lt;/a>, I used the
&lt;code>where id = any(:ids)&lt;/code> pattern, with &lt;code>ids&lt;/code> bound to a JDBC array. I&amp;rsquo;ve gotten questions about that
afterwards, asking why I do it like that, instead of using &lt;code>in (:id1, :id2, ...)&lt;/code>. Many libraries
can take care of the dynamic SQL generation for you, so often you can just write &lt;code>in (:ids)&lt;/code>, just
like the array example. I would still prefer to use the &lt;code>= any(:ids)&lt;/code> pattern, and I decided to write
down my reasoning here.&lt;/p></description></item><item><title>Batch operations using composite keys in postgres over jdbc</title><link>https://kaveland.no/posts/2024-08-30-multi-selecting-by-composite-key/</link><pubDate>Fri, 30 Aug 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-08-30-multi-selecting-by-composite-key/</guid><description>&lt;p>Throughout a career as a software developer, you encounter many patterns. Some appear just often
enough to remember that they exist, but you still need to look them up every time. I&amp;rsquo;ve discovered
that writing things down helps me remember them more easily. This particular pattern is very useful
for my current project. So, it&amp;rsquo;s time to write it down and hopefully commit it to memory properly
this time. Although this post is specific to PostgreSQL, I&amp;rsquo;m sure other databases have the necessary
features to achieve the same results efficiently.&lt;/p></description></item></channel></rss>