<?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>Duckdb on Jamal Hansen</title><link>https://jamalhansen.com/tags/duckdb/</link><description>Recent content in Duckdb on Jamal Hansen</description><image><title>Jamal Hansen</title><url>https://jamalhansen.com/social-card.png</url><link>https://jamalhansen.com/social-card.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Wed, 30 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jamalhansen.com/tags/duckdb/index.xml" rel="self" type="application/rss+xml"/><item><title>Go Hybrid: Find the Results LIKE Can't</title><link>https://jamalhansen.com/blog/go-hybrid-pyohio-2026/</link><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><guid>https://jamalhansen.com/blog/go-hybrid-pyohio-2026/</guid><description>Companion page for my PyOhio 2026 talk. Slides, code, and the dog-spa dataset for searching free text beyond LIKE with BM25, vectors, and hybrid.</description><content:encoded><![CDATA[<p>This is the companion page for my <a href="https://www.pyohio.org/">PyOhio 2026</a> talk. Everything I demoed is here to take home: the slides, the notebooks, and the dataset.</p>
<p><code>LIKE</code> is the tool most of us reach for first when we search free text. You end up chaining <code>OR</code> and <code>NOT LIKE</code> clauses to patch each miss, and you are still reading every record by hand.</p>
<p>The talk works through what to reach for instead, using 448 free-text notes from a fictional dog spa.</p>
<h2 id="take-it-home">Take it home</h2>
<ul>
<li><strong><a href="https://github.com/jamalhansen/go-hybrid">Code and notebooks on GitHub</a></strong> &ndash; start with <code>search_toolkit.ipynb</code>. Each method is a few lines you can lift straight into your own DuckDB project. <code>go_hybrid.ipynb</code> is the full walkthrough that follows the talk.</li>
<li><strong><a href="go-hybrid-slides.pdf">Download the slides (PDF)</a></strong></li>
</ul>
<h2 id="run-the-code">Run the code</h2>
<!-- test:skip -->
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/jamalhansen/go-hybrid
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> go-hybrid
</span></span><span class="line"><span class="cl">uv sync
</span></span><span class="line"><span class="cl">uv run jupyter lab search_toolkit.ipynb
</span></span></code></pre></div><p>The first run downloads the embedding model (<code>all-MiniLM-L6-v2</code>, about 90 MB). After that it is fully offline.</p>
<p>The dataset is synthetic and curated so each method fails visibly. Bring a note of your own, drop it in, and see which tools find it.</p>
<p>If you saw the talk, thank you for coming. If you have questions, <a href="/about/">reach out</a>.</p>
]]></content:encoded></item><item><title>(All) Databases Are Just Files. Postgres Too.</title><link>https://jamalhansen.com/finds/all-databases-are-just-files-postgres-too/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><guid>https://jamalhansen.com/finds/all-databases-are-just-files-postgres-too/</guid><description>DuckDB&amp;#39;s file-native design is a first-class feature, not a limitation. File-native is good design. This article makes that clearer by pulling back the curtain...</description><content:encoded><![CDATA[<h2 id="why-this-caught-my-eye">Why This Caught My Eye</h2>
<p>DuckDB&rsquo;s file-native design is a first-class feature, not a limitation. File-native is good design. This article makes that clearer by pulling back the curtain on Postgres &ndash; a great database, but ultimately a complex program that stores data in a filesystem.</p>
<p>Note: the article contains a long quote used here for context. Trim to under 15 words before publishing.</p>
<blockquote>
<p>At its core, <code>postgres</code> is simply a program that turns SQL queries into filesystem operations. A <code>CREATE TABLE</code> becomes a mkdir. An <code>UPDATE</code> eventually becomes: open a file, write to it, close it. It’s a complex and powerful system—but fundamentally, it’s just an executable that manipulates files.</p></blockquote>
]]></content:encoded></item><item><title>Don't forget to save! Persisting your DuckDB database</title><link>https://jamalhansen.com/blog/dont-forget-to-save-persisting-your-duckdb-database/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><guid>https://jamalhansen.com/blog/dont-forget-to-save-persisting-your-duckdb-database/</guid><description>Your in-memory database disappears when Python exits. Let&amp;#39;s fix that with a one-line change that saves everything to disk.</description><content:encoded><![CDATA[<p>I still remember losing schoolwork and video game progress because I forgot to save. That sinking feeling when hours of work vanish because you were too caught up in the flow to pause and save.</p>
<p><a href="/blog/generate-practice-data-with-faker/">In our last post</a>, we created a customer database and generated 500 rows of fake data. Our in-memory database has the same problem: when Python exits, all that data vanishes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;:memory:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;CREATE TABLE customers (id INT, name VARCHAR)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;INSERT INTO customers VALUES (1, &#39;Alice&#39;)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT * FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchdf</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Script ends... and the data is gone forever</span>
</span></span></code></pre></div><p>A database is supposed to provide persistent storage, isn&rsquo;t it? Let&rsquo;s fix that with one small change.</p>
<h2 id="the-one-line-fix">The One-Line Fix</h2>
<p>So far, we&rsquo;ve been creating our connection like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;:memory:&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>If we connect using a file path rather than the <code>:memory:</code> keyword, DuckDB stores everything to that file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;practice.duckdb&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>That&rsquo;s it. One small change.</p>
<h2 id="persistent-data-storage">Persistent Data Storage</h2>
<p>Now, let&rsquo;s upgrade our customer generation script to use persistent storage. This version uses a handy <code>CREATE TABLE IF NOT EXISTS</code>, which will only create the table when it isn&rsquo;t there. This avoids errors that happen when you run the script multiple times.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">date</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faker</span> <span class="kn">import</span> <span class="n">Faker</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Connect to a FILE instead of memory</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;practice.duckdb&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># CREATE TABLE IF NOT EXISTS lets us run this script repeatedly</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    CREATE TABLE IF NOT EXISTS customers (
</span></span></span><span class="line"><span class="cl"><span class="s2">        id INTEGER,
</span></span></span><span class="line"><span class="cl"><span class="s2">        name VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        email VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        city VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        signup_date DATE,
</span></span></span><span class="line"><span class="cl"><span class="s2">        is_premium BOOLEAN
</span></span></span><span class="line"><span class="cl"><span class="s2">    )
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Only generate data if the table is empty</span>
</span></span><span class="line"><span class="cl"><span class="n">row_count</span> <span class="o">=</span> <span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT COUNT(*) FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">row_count</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">fake</span> <span class="o">=</span> <span class="n">Faker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">Faker</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">customers</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">500</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">customers</span><span class="o">.</span><span class="n">append</span><span class="p">((</span>
</span></span><span class="line"><span class="cl">            <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">fake</span><span class="o">.</span><span class="n">name</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">fake</span><span class="o">.</span><span class="n">email</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">fake</span><span class="o">.</span><span class="n">city</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">fake</span><span class="o">.</span><span class="n">date_between</span><span class="p">(</span><span class="n">start_date</span><span class="o">=</span><span class="n">date</span><span class="p">(</span><span class="mi">2024</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">end_date</span><span class="o">=</span><span class="n">date</span><span class="p">(</span><span class="mi">2025</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">([</span><span class="kc">True</span><span class="p">,</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">con</span><span class="o">.</span><span class="n">executemany</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;INSERT INTO customers VALUES (?, ?, ?, ?, ?, ?)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">customers</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Created 500 customers!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Data already exists! (</span><span class="si">{</span><span class="n">row_count</span><span class="si">}</span><span class="s2"> customers)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Either way, we can query it</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT * FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchdf</span><span class="p">()</span><span class="o">.</span><span class="n">head</span><span class="p">())</span>
</span></span></code></pre></div><h2 id="proving-it-persists">Proving It Persists</h2>
<p>In a fresh Python session:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The file persists between Python sessions</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;practice.duckdb&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Data is still there!</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT * FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchdf</span><span class="p">()</span><span class="o">.</span><span class="n">head</span><span class="p">())</span>
</span></span></code></pre></div><p>The data survived. Now you have a persistent database with 500 customers ready for querying.</p>
<h2 id="whats-next">What&rsquo;s Next</h2>
<p>So far, we&rsquo;ve only written SQL that brings back all the data in a table. To move forward quickly, we need to make a mental model for how SQL works. SQL is more like a list comprehension than a <code>for</code> loop, and next week we&rsquo;ll explore this mental shift.</p>
]]></content:encoded></item><item><title>Generate Practice Data with faker</title><link>https://jamalhansen.com/blog/generate-practice-data-with-faker/</link><pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate><guid>https://jamalhansen.com/blog/generate-practice-data-with-faker/</guid><description>Real SQL practice needs real-looking data. Generate hundreds of customers with Python&amp;#39;s Faker library without downloading a single CSV</description><content:encoded><![CDATA[<p>Last week, we got <a href="/blog/run-your-first-sql-query-in-under-5-minutes/">DuckDB running</a> with three hardcoded rows. That got us started, but three rows? You can eyeball that. Let&rsquo;s generate hundreds of realistic customers and build a dataset worth exploring.</p>
<p>Python has the perfect tool: <code>faker</code>. It&rsquo;s a library that generates realistic fake data: names, emails, addresses, dates, and anything else you&rsquo;d find in a real database. Let&rsquo;s use it to build a dataset we can explore for the rest of this series.</p>
<h2 id="the-dataset-customers">The Dataset: Customers</h2>
<p>We&rsquo;ll create a customer table, the kind you&rsquo;ll find in almost any database:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">customers</span>
</span></span><span class="line"><span class="cl"><span class="err">├──</span> <span class="n">id</span> <span class="p">(</span><span class="n">integer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="err">├──</span> <span class="n">name</span> <span class="p">(</span><span class="n">varchar</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="err">├──</span> <span class="n">email</span> <span class="p">(</span><span class="n">varchar</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="err">├──</span> <span class="n">city</span> <span class="p">(</span><span class="n">varchar</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="err">├──</span> <span class="n">signup_date</span> <span class="p">(</span><span class="n">date</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="err">└──</span> <span class="n">is_premium</span> <span class="p">(</span><span class="n">boolean</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="building-our-dataset">Building Our Dataset</h2>
<p>Now let&rsquo;s write the Python code that will:</p>
<ul>
<li>Generate 500 rows of data with <code>faker</code></li>
<li>Create the customer table</li>
<li>Insert the generated data into the table</li>
<li>Query that data to ensure it&rsquo;s there</li>
</ul>
<p>To get started, we will install the <code>faker</code> module:</p>
<!-- test:skip -->
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install faker
</span></span></code></pre></div><p>Now let&rsquo;s generate 500 customers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">date</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faker</span> <span class="kn">import</span> <span class="n">Faker</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fake</span> <span class="o">=</span> <span class="n">Faker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">Faker</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>  <span class="c1"># Same data every time</span>
</span></span><span class="line"><span class="cl"><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">customers</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">500</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">customers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">name</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">email</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">city</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;signup_date&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">date_between</span><span class="p">(</span><span class="n">start_date</span><span class="o">=</span><span class="n">date</span><span class="p">(</span><span class="mi">2024</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">end_date</span><span class="o">=</span><span class="n">date</span><span class="p">(</span><span class="mi">2025</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;is_premium&#34;</span><span class="p">:</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">([</span><span class="kc">True</span><span class="p">,</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Peek at what we generated</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">customers</span><span class="p">[:</span><span class="mi">3</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">{&#39;id&#39;: 1, &#39;name&#39;: &#39;Allison Hill&#39;, &#39;email&#39;: &#39;donaldgarcia@example.net&#39;, &#39;city&#39;: &#39;New Roberttown&#39;, &#39;signup_date&#39;: datetime.date(2024, 3, 9), &#39;is_premium&#39;: True}
</span></span><span class="line"><span class="cl">{&#39;id&#39;: 2, &#39;name&#39;: &#39;Lance Hoffman&#39;, &#39;email&#39;: &#39;lrobinson@example.com&#39;, &#39;city&#39;: &#39;Port Lindachester&#39;, &#39;signup_date&#39;: datetime.date(2024, 9, 5), &#39;is_premium&#39;: True}
</span></span><span class="line"><span class="cl">{&#39;id&#39;: 3, &#39;name&#39;: &#39;Tyler Rogers&#39;, &#39;email&#39;: &#39;jamesmichael@example.com&#39;, &#39;city&#39;: &#39;Lindsaymouth&#39;, &#39;signup_date&#39;: datetime.date(2025, 1, 26), &#39;is_premium&#39;: False}
</span></span></code></pre></div><p>A few things to notice. First, <code>faker</code> can generate data for a variety of purposes and data types. We tell <code>faker</code> that we want a name or an email address, and it will generate not only a string, but an actual name or email address.</p>
<p>It will also create randomized values such as dates within a range or a choice of values from a list.</p>
<p>The <code>is_premium</code> line is a small trick: by putting <code>False</code> in the list three times, roughly 25% of customers end up as premium members. More realistic than a coin flip.</p>
<p>We are setting the random number seed at the beginning of the script. This makes the &ldquo;random&rdquo; data reproducible, so you&rsquo;ll get the same values every time you run it. This is useful in a number of situations, including testing.</p>
<p>Now we have our generated data in the <code>customers</code> variable. Let&rsquo;s create a table for it to live in and then insert the data into the table. You&rsquo;ll notice <code>VARCHAR</code> in the CREATE TABLE statement. That&rsquo;s SQL&rsquo;s name for text data (like Python&rsquo;s <code>str</code>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;:memory:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    CREATE TABLE customers (
</span></span></span><span class="line"><span class="cl"><span class="s2">        id INTEGER,
</span></span></span><span class="line"><span class="cl"><span class="s2">        name VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        email VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        city VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        signup_date DATE,
</span></span></span><span class="line"><span class="cl"><span class="s2">        is_premium BOOLEAN
</span></span></span><span class="line"><span class="cl"><span class="s2">    )
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">executemany</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;INSERT INTO customers VALUES (?, ?, ?, ?, ?, ?)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">c</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">],</span> 
</span></span><span class="line"><span class="cl">            <span class="n">c</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">],</span> 
</span></span><span class="line"><span class="cl">            <span class="n">c</span><span class="p">[</span><span class="s1">&#39;email&#39;</span><span class="p">],</span> 
</span></span><span class="line"><span class="cl">            <span class="n">c</span><span class="p">[</span><span class="s1">&#39;city&#39;</span><span class="p">],</span> 
</span></span><span class="line"><span class="cl">            <span class="n">c</span><span class="p">[</span><span class="s1">&#39;signup_date&#39;</span><span class="p">],</span> 
</span></span><span class="line"><span class="cl">            <span class="n">c</span><span class="p">[</span><span class="s1">&#39;is_premium&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span> 
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">customers</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Once the data has been inserted into the table, we can query it. Like last time, we just want to bring back all the records to see that they are there.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># See the first few rows</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT * FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchdf</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">head</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Or just specific columns</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT name, city FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchdf</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">head</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
</span></span></code></pre></div><p>The <code>*</code> in <code>SELECT *</code> is shorthand for &ldquo;all columns.&rdquo; Or you can list the columns that you want.</p>
<p>You should see ten unique customers: real-looking names, varied cities, a mix of premium and regular accounts. That&rsquo;s 500 rows of realistic data we can explore for the rest of this series.</p>
<h2 id="the-full-script">The Full Script</h2>
<p>Here&rsquo;s everything in one runnable file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">date</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faker</span> <span class="kn">import</span> <span class="n">Faker</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate fake data</span>
</span></span><span class="line"><span class="cl"><span class="n">fake</span> <span class="o">=</span> <span class="n">Faker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">Faker</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">customers</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">500</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">customers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">name</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">email</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">city</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;signup_date&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">date_between</span><span class="p">(</span><span class="n">start_date</span><span class="o">=</span><span class="n">date</span><span class="p">(</span><span class="mi">2024</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">end_date</span><span class="o">=</span><span class="n">date</span><span class="p">(</span><span class="mi">2025</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;is_premium&#34;</span><span class="p">:</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">([</span><span class="kc">True</span><span class="p">,</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Load into DuckDB</span>
</span></span><span class="line"><span class="cl"><span class="n">con</span> <span class="o">=</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;:memory:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    CREATE TABLE customers (
</span></span></span><span class="line"><span class="cl"><span class="s2">        id INTEGER,
</span></span></span><span class="line"><span class="cl"><span class="s2">        name VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        email VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        city VARCHAR,
</span></span></span><span class="line"><span class="cl"><span class="s2">        signup_date DATE,
</span></span></span><span class="line"><span class="cl"><span class="s2">        is_premium BOOLEAN
</span></span></span><span class="line"><span class="cl"><span class="s2">    )
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">con</span><span class="o">.</span><span class="n">executemany</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;INSERT INTO customers VALUES (?, ?, ?, ?, ?, ?)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">c</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">],</span> <span class="n">c</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">],</span> <span class="n">c</span><span class="p">[</span><span class="s1">&#39;email&#39;</span><span class="p">],</span> <span class="n">c</span><span class="p">[</span><span class="s1">&#39;city&#39;</span><span class="p">],</span> <span class="n">c</span><span class="p">[</span><span class="s1">&#39;signup_date&#39;</span><span class="p">],</span> <span class="n">c</span><span class="p">[</span><span class="s1">&#39;is_premium&#39;</span><span class="p">])</span> 
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">customers</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Query the data</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT * FROM customers&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchdf</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">head</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
</span></span></code></pre></div><h2 id="try-it-yourself">Try It Yourself</h2>
<ul>
<li><strong>Scale it up</strong>: Change <code>range(500)</code> to <code>range(10000)</code>. See how fast it handles larger data.</li>
<li><strong>Change the seed</strong>: Try <code>Faker.seed(123)</code>. Completely different people.</li>
<li><strong>Bonus challenge</strong>: Faker has <code>fake.user_name()</code>. Can you add a username column? You&rsquo;ll need to update the customer dict, the CREATE TABLE statement, and the INSERT query.</li>
</ul>
<h2 id="coming-up">Coming Up</h2>
<p>We now have 500 customers to work with. In future posts, we&rsquo;ll filter them, sort them, and answer real questions about this data.</p>
<p>But there&rsquo;s one problem: every time you restart Python, this data disappears. Next week, we&rsquo;ll save the database to a file so you can keep building on it.</p>
]]></content:encoded></item><item><title>Zero-Setup SQL: Run your first SQL query in under 5 minutes with DuckDB</title><link>https://jamalhansen.com/blog/run-your-first-sql-query-in-under-5-minutes/</link><pubDate>Sat, 10 Jan 2026 00:00:00 +0000</pubDate><guid>https://jamalhansen.com/blog/run-your-first-sql-query-in-under-5-minutes/</guid><description>Install DuckDB with pip, create your first table, insert data, and run a SQL query - all in Python, all in under 5 minutes, zero configuration required</description><content:encoded><![CDATA[<p>Have you ever tried setting up a database server just to learn SQL? Docker containers, admin credentials&hellip; Forget all that. Let me show you how to go from zero to running SQL in under 5 minutes.</p>
<h2 id="why-are-we-using-duckdb-to-learn-sql">Why are we using DuckDB to learn SQL?</h2>
<ul>
<li>No setup - Just import and start using it</li>
<li>Real SQL - PostgreSQL compatible syntax so what you learn transfers</li>
<li>Fast enough - Handles millions of rows on your laptop</li>
<li>Python-native - Works well with lists, DataFrames, CSV files</li>
</ul>
<p>It is perfect for learning without infrastructure headaches.</p>
<h2 id="installation">Installation</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install duckdb
</span></span></code></pre></div><p>That&rsquo;s it. You&rsquo;re done. No, really.</p>
<h2 id="your-first-query">Your First Query</h2>
<p>Now that we are ready to query the database, let&rsquo;s write our first query and run it.</p>
<!-- test:skip -->
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">duckdb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Connect to DuckDB</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">duckdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;:memory:&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">con</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">	<span class="c1"># Create table</span>
</span></span><span class="line"><span class="cl">	<span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">	    CREATE TABLE data(name VARCHAR, score INTEGER)
</span></span></span><span class="line"><span class="cl"><span class="s2">	&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1"># Insert data</span>
</span></span><span class="line"><span class="cl">	<span class="n">con</span><span class="o">.</span><span class="n">executemany</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">	    <span class="s2">&#34;INSERT INTO data VALUES (?, ?)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	    <span class="p">[(</span><span class="s1">&#39;Alice&#39;</span><span class="p">,</span> <span class="mi">95</span><span class="p">),</span> <span class="p">(</span><span class="s1">&#39;Bob&#39;</span><span class="p">,</span> <span class="mi">87</span><span class="p">),</span> <span class="p">(</span><span class="s1">&#39;Carol&#39;</span><span class="p">,</span> <span class="mi">92</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">	<span class="p">)</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1"># Query the data</span>
</span></span><span class="line"><span class="cl">	<span class="n">result</span> <span class="o">=</span> <span class="n">con</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">	    SELECT name, score 
</span></span></span><span class="line"><span class="cl"><span class="s2">	    FROM data 
</span></span></span><span class="line"><span class="cl"><span class="s2">	&#34;&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span></code></pre></div><p>When you run this, you should see the result:</p>
<!-- test:skip -->
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[(</span><span class="s1">&#39;Alice&#39;</span>, 95<span class="o">)</span>, <span class="o">(</span><span class="s1">&#39;Bob&#39;</span>, 87<span class="o">)</span>, <span class="o">(</span><span class="s1">&#39;Carol&#39;</span>, 92<span class="o">)]</span>
</span></span></code></pre></div><h2 id="what-happened">What happened?</h2>
<p>So what exactly is <a href="https://duckdb.org" title="The homepage for DuckDB">DuckDB</a>, and what did we do in these 22 lines of code?</p>
<p><strong>DuckDB</strong> is an embedded SQL database optimized for analytics. Think &ldquo;SQLite for data analysis&rdquo;. It runs inside your Python process with zero setup.</p>
<p>Our 22-line script did four things:</p>
<ol>
<li>Connect - Created an in-memory database (<code>:memory:</code>)</li>
<li>Create - Defined a table structure (name + score)</li>
<li>Insert - Added three rows of data</li>
<li>Query - Retrieved scores from the database</li>
</ol>
<p>This is the basic SQL workflow you&rsquo;ll use everywhere. Often, steps 2 and 3 aren&rsquo;t necessary because someone else already did them for you.</p>
<h2 id="try-it-yourself">Try It Yourself</h2>
<p>Want to experiment? Modify the query to:</p>
<ul>
<li>Get only scores above 90: Add <code>WHERE score &gt; 90</code></li>
<li>See the highest score first: Add <code>ORDER BY score DESC</code></li>
<li>Get just the top scorer: Add <code>LIMIT 1</code></li>
</ul>
<p>The best way to learn SQL is by experimenting. Try these modifications and see what happens!</p>
<h2 id="what-you-just-learned">What You Just Learned</h2>
<p>In 5 minutes, you:</p>
<ul>
<li>Installed a production-grade SQL database</li>
<li>Created your first table</li>
<li>Inserted data</li>
<li>Wrote your first SELECT query</li>
</ul>
<p>No servers. No complexity. Just SQL.</p>
<p>Next week, we will use Python&rsquo;s <code>faker</code> library to generate realistic datasets and practice SQL on hundreds of rows. You&rsquo;ll see why data folks love SQL for exploration.</p>
]]></content:encoded></item><item><title>I know Python; Why learn SQL</title><link>https://jamalhansen.com/blog/i-know-python-why-learn-sql/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><guid>https://jamalhansen.com/blog/i-know-python-why-learn-sql/</guid><description>Learning SQL makes you a better Python developer, even if you use pandas and ORMs. The first post in a series that teaches it with DuckDB and Python.</description><content:encoded><![CDATA[<p>SQL and Python solve different problems with data. Python tells the computer what to do, step by step. SQL describes the data you want and lets the database figure out how to get it. As a Python developer, learning SQL means you&rsquo;ll write better pandas queries, know when to push work to the database instead of pulling everything into memory, and understand what your ORM is actually doing under the hood.</p>
<p>I learned SQL very early in my career. At the time, I didn&rsquo;t understand why, and for the first month or so, it didn&rsquo;t make sense to me. The syntax didn&rsquo;t resemble any language I had seen before, and it employed concepts with which I was unfamiliar. This all made SQL seem scary, and oddly enough, SQL hasn&rsquo;t changed much in the past 40 years, which makes it even more of an oddity.</p>
<p>The problem wasn&rsquo;t the SQL; it was how I was thinking. I had some experience with coding, but only in a procedural style, telling the computer to perform a series of steps in order. My aha moment was when I realized that <a href="/blog/sql-thinks-in-sets-not-loops/">SQL doesn&rsquo;t do this</a>. Instead, a SQL query describes the data that you would like, and it returns that dataset. Making this mind shift turned SQL into a beautiful and functional language that is relatively simple to learn.</p>
<p>SQL is the language of data, and over the past 40 years, data has become more prevalent and more important. If you work with data, learning SQL will help you. This is true even if you already know and use another programming language like Python with good data management libraries. Python has great data support with SQL Alchemy, Pandas, and other great libraries. Learning SQL won&rsquo;t replace them, but it will help you use them more effectively. When you understand how SQL thinks, you&rsquo;ll write better pandas queries, ask smarter questions of your ORM, and know when to push work to the database instead of pulling everything into memory.</p>
<p>In this series, I&rsquo;ll walk you through the steps to level up your SQL skills. We will use Python along the way to keep things practical. Our first step will be to <a href="/blog/run-your-first-sql-query-in-under-5-minutes/">set up a database</a>. This is the biggest hurdle for beginners: you need a database to practice, but setting one up feels intimidating before you know what you&rsquo;re doing. For this series, we will use a modern, local, and user-friendly database called <a href="/blog/run-your-first-sql-query-in-under-5-minutes/">DuckDB</a>, which is simple to get started with.</p>
<p>Next, we will dig into querying data. We will learn about keywords such as SELECT, FROM, WHERE, and GROUP BY, and how they can be used to shape your results.</p>
<p>Finally, we will tackle some more complex topics, like when it&rsquo;s useful to use SQL over an ORM or Pandas, and why you might consider it.</p>
<p>If you are a Python developer, you work with data. Follow along, and I&rsquo;ll see you next week to set up DuckDB.</p>
]]></content:encoded></item></channel></rss>