<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sql on Max Halford</title><link>https://maxhalford.github.io/tags/sql/</link><description>Recent content in Sql on Max Halford</description><generator>Hugo</generator><language>en-US</language><managingEditor>maxhalford25@gmail.com (Max Halford)</managingEditor><webMaster>maxhalford25@gmail.com (Max Halford)</webMaster><lastBuildDate>Tue, 21 Jul 2026 21:18:41 +0200</lastBuildDate><atom:link href="https://maxhalford.github.io/tags/sql/index.xml" rel="self" type="application/rss+xml"/><item><title>Lower your warehouse costs via DuckDB transpilation</title><link>https://maxhalford.github.io/blog/warehouse-cost-reduction-quack-mode/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/warehouse-cost-reduction-quack-mode/</guid><description>&lt;p&gt;Many people &lt;a href="https://survey.stackoverflow.co/2025/technology#2-databases"&gt;seem to admire&lt;/a&gt; DuckDB. But most of us are stuck with our traditional warehouses, because they&amp;rsquo;re entrenched in our data stacks and IT landscape. This is with good reason: BigQuery, Snowflake, ClickHouse and co. are great software. But they&amp;rsquo;re not cheap, and keeping a warehouse&amp;rsquo;s monthly bill under control is non-trivial.&lt;/p&gt;
&lt;p&gt;What if you could get the best of both worlds? Tables could be kept in your warehouse of choice, but computed with DuckDB. There&amp;rsquo;s been some discussion on multi-engine warehouses &amp;ndash; see &lt;a href="https://juhache.substack.com/p/multi-engine-stacks-deserve-to-be"&gt;this article&lt;/a&gt; by Julien Hurault and Sung Won Chung. But their proposition is to assign each table to one engine. Multi-engine stacks can be useful, and SQLMesh provides &lt;a href="https://sqlmesh.readthedocs.io/en/latest/guides/multi_engine"&gt;support&lt;/a&gt; for it. But to me it sounds too sophisticated, and I&amp;rsquo;m not convinced it&amp;rsquo;s what most practitioners want/need.&lt;/p&gt;</description></item><item><title>Graph components with DuckDB</title><link>https://maxhalford.github.io/blog/graph-components-duckdb/</link><pubDate>Sat, 03 Jun 2023 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/graph-components-duckdb/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Graph problems are quite common. However, it&amp;rsquo;s rare to have access to a database offering graph semantics. There are graph databases, such as &lt;a href="https://neo4j.com/"&gt;Neo4j&lt;/a&gt; and &lt;a href="https://spark.apache.org/docs/latest/graphx-programming-guide.html"&gt;GraphX&lt;/a&gt;, but it&amp;rsquo;s difficult to justify setting one of those up. One could simply use &lt;a href="https://networkx.org/"&gt;networkx&lt;/a&gt; in Python. But that only works if the graph fits in memory.&lt;/p&gt;
&lt;p&gt;From a practical angle, the fact is that people are querying data warehouses in SQL. There are many good reasons to write graph algorithms in SQL. And anyway, one may argue that graphs are a special case of the &lt;a href="https://en.wikipedia.org/wiki/Relational_model"&gt;relational model&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>For analytics, don't use dynamic JSON keys</title><link>https://maxhalford.github.io/blog/no-dynamic-keys-in-json/</link><pubDate>Thu, 11 May 2023 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/no-dynamic-keys-in-json/</guid><description>&lt;p&gt;I love the JSON format. It&amp;rsquo;s the kind of love that grows on you with time. Like others, I&amp;rsquo;ve been using JSON everywhere for so many years, to the point where I just take it for granted.&lt;/p&gt;
&lt;p&gt;I suppose the main thing I like about JSON is its flexibility. You can structure your JSONs without too much care. There will always be a way to consume and manipulate it. But I have discovered a bit of anti-pattern, which I believe is worth raising awareness about. Especially when you&amp;rsquo;re doing analytics.&lt;/p&gt;</description></item><item><title>Online gradient descent written in SQL</title><link>https://maxhalford.github.io/blog/ogd-in-sql/</link><pubDate>Tue, 07 Mar 2023 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/ogd-in-sql/</guid><description>&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt; &amp;ndash; &lt;em&gt;this post &lt;a href="https://news.ycombinator.com/item?id=35054786"&gt;generated&lt;/a&gt; a few insightful comments on Hacker News. I&amp;rsquo;ve also put the code in a &lt;a href="https://gist.github.com/MaxHalford/823c4e7f9216607dc853724ec74ec692"&gt;notebook&lt;/a&gt; for ease of use.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Modern MLOps is complex because it involves too many components. You need a message bus, a stream processing engine, an API, a model store, a feature store, a monitoring service, etc. Sadly, containerisation software and the unbundling trend have encouraged an appetite for complexity. I believe MLOps shouldn&amp;rsquo;t be this complex. For instance, MLOps can be made simpler by &lt;a href="https://www.ethanrosenthal.com/2022/05/10/database-bundling/"&gt;bundling the logic into your database&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>A rant against dbt ref</title><link>https://maxhalford.github.io/blog/dbt-ref-rant/</link><pubDate>Tue, 28 Jun 2022 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/dbt-ref-rant/</guid><description>&lt;h2 id="disclaimer"&gt;Disclaimer&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Let me be absolutely clear: I think dbt is a great tool. Although this post is a rant, the goal is to be constructive and suggest an improvement.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="dbt-in-a-nutshell"&gt;dbt in a nutshell&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.getdbt.com/"&gt;dbt&lt;/a&gt; is a workflow orchestrator for SQL. In other words, it&amp;rsquo;s a fancy &lt;a href="https://en.wikipedia.org/wiki/Make_(software)"&gt;Make&lt;/a&gt; for data analytics. What makes dbt special is that it is the first workflow orchestrator that is dedicated to the SQL language. It said out loud what many data teams were thinking: you can get a lot done with SQL.&lt;/p&gt;</description></item><item><title>Dashboards and GROUPING SETS</title><link>https://maxhalford.github.io/blog/grouping-sets/</link><pubDate>Fri, 10 Sep 2021 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/grouping-sets/</guid><description>&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;At &lt;a href="https://alan.com/"&gt;Alan&lt;/a&gt;, we do almost all our data analysis in SQL. Our data warehouse used to be &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt;, and have since switched to &lt;a href="https://www.snowflake.com/"&gt;Snowflake&lt;/a&gt; for performance reasons. We load data into our warehouse with &lt;a href="https://airflow.apache.org/"&gt;Airflow&lt;/a&gt;. This includes dumps of our production database, third-party data, and health data from other actors in the health ecosystem. This is raw data. We transform this into prepared data via an in-house tool that resembles &lt;a href="https://www.getdbt.com/"&gt;dbt&lt;/a&gt;. You can read more about it &lt;a href="https://medium.com/alan/how-we-solve-the-problem-of-sharing-actionable-data-with-the-team-7e4afeff3cac"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Computing cross-correlations in SQL</title><link>https://maxhalford.github.io/blog/sql-cross-correlations/</link><pubDate>Tue, 17 Nov 2020 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/sql-cross-correlations/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m currently working on a problem at work where I have to measure the impact of a &lt;span style="color: SlateBlue;"&gt;growth initiative&lt;/span&gt; on a &lt;span style="color: MediumSeaGreen;"&gt;performance metric&lt;/span&gt;. Hypothetically, this might answer the following kind of question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve spent &lt;span style="color: SlateBlue;"&gt;X amount of money&lt;/span&gt;, what is the impact on the &lt;span style="color: MediumSeaGreen;"&gt;number of visitors on my website&lt;/span&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Of course, there are many measures that can be taken to answer such a question. I decided to measure the correlation between the &lt;span style="color: SlateBlue;"&gt;initiative&lt;/span&gt; and the &lt;span style="color: MediumSeaGreen;"&gt;metric&lt;/span&gt;, with the latter being shifted forward in time. This measure is called the &lt;a href="https://en.wikipedia.org/wiki/Cross-correlation"&gt;cross-correlation&lt;/a&gt;. It&amp;rsquo;s different from &lt;a href="https://en.wikipedia.org/wiki/Autocorrelation"&gt;serial correlation&lt;/a&gt;, which is the correlation of a series with a shifted version of itself.&lt;/p&gt;</description></item><item><title>SQL subquery enumeration</title><link>https://maxhalford.github.io/blog/sql-subquery-enumeration/</link><pubDate>Mon, 06 May 2019 00:00:00 +0000</pubDate><author>maxhalford25@gmail.com (Max Halford)</author><guid>https://maxhalford.github.io/blog/sql-subquery-enumeration/</guid><description>&lt;p&gt;I recently stumbled on a rather fun problem during my PhD. I wanted to generate all possible subqueries from a given SQL query. In this case an example is easily worth a 1000 thousand words. Take the following SQL query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;purchases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shop_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Swedish&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hair&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Blond&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Stockholm&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here all the possible subqueries that can be generated from the above query.&lt;/p&gt;</description></item></channel></rss>