<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><id>https://vic.demuzere.be/articles/tags/sqlite/atom.xml</id><title>Vic Demuzere - SQLite</title><link href="https://vic.demuzere.be/articles/tags/sqlite/atom.xml" rel="self" type="application/atom+xml" /><link href="https://vic.demuzere.be/articles/tags/sqlite/" rel="alternate" type="text/html" /><updated>2025-12-24T15:28:41+00:00</updated><author><name>Vic Demuzere</name></author><entry><id>https://vic.demuzere.be/articles/generating-uuids-in-sqlite/</id><title>Generating UUIDs in SQLite</title><link href="https://vic.demuzere.be/articles/generating-uuids-in-sqlite/" rel="alternate" /><updated>2026-01-04T17:26:44+00:00</updated><published>2025-12-24T15:28:41+00:00</published><content type="html"><![CDATA[<p>SQLite does not have built-in UUID functions, but by getting creative with the <code>randomblob(N)</code> function we can generate version 4 (random) UUIDs.</p>
<p>Before we continue, it's important to note that you should verify whether SQLite's random functions are adequate for your usecase.
They may not return truly random data.
However, as <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" title="RFC 9562 section 4">the RFC</a> notes, &quot;they (UUIDs) MUST NOT be used as security capabilities&quot;.
And since we all follow that to the letter, it doesn't really matter.. right?</p>
<p>The <a href="https://sqlite.org/lang_corefunc.html#randomblob" title="randomblob(N) function at SQLite manual">excellent SQLite manual</a> gives us a starting point:</p>
<blockquote>
<p>Hint: applications can generate globally unique identifiers using this function together with hex() and/or lower().</p>
</blockquote>
<p>Combining the <code>randomblob(N)</code> function with <code>hex(N)</code> does indeed create something that vaguely resembles a UUID.
For some purposes this may suffice, but the result isn't actually a valid UUID<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>:
valid UUIDs must have <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" title="RFC 9562 section 4">specific bits set</a> to indicate their version and variant.</p>
<p>We can do better!</p>
<h2 id="as-binary-blob">As binary blob<a href="#as-binary-blob" aria-label="Link to heading 'As binary blob'" data-heading-content="As binary blob" class="anchor"></a></h2>
<p>The binary representation of a version 4 UUID consists of:</p>
<ul>
<li>48 bits (6 bytes) random data</li>
<li>4 bits (0.5 bytes) <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-version-field" title="RFC 9562 section 4.2: Version Field">version field</a></li>
<li>6 bits (1.5 bytes) random data</li>
<li>4 bits (0.5 bytes) <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-variant-field" title="RFC 9562 section 4.1: Variant Field">variant field</a></li>
<li>60 bits (7.5 bytes) random data</li>
</ul>
<p>As far as I know, we can't modify individual bytes in a blob using the available functions in SQLite.
However, we do have access to a lot of string functions.
Taking a detour using HEX encoding provides a solution.
We'll first concatenate a HEX string containing the data we want, then convert it back to a binary blob.
Note that each byte is represented by two hexadecimal characters.</p>
<p>The first 48 random bits are easy:</p>
<pre><code class="language-sqlite">sqlite&gt; select HEX(RANDOMBLOB(6));
4004714237AF
</code></pre>
<p>The first 4 bits of the next byte need to be set to <code>0100</code> (or <code>4</code> in hex).
As every byte is represented by two characters in hexadecimal, we can just use a literal <code>4</code> for the next character.</p>
<pre><code class="language-sqlite">sqlite&gt; select '4';
4
</code></pre>
<p>Next, we need 6 bits of random data.
We can only generate random bytes using <code>RANDOMBLOB(N)</code>, but we need 1.5 bytes.
Luckily, because we are working with a hexadecimal string, we can generate 2 bytes (4 hex characters) and take the first three characters.</p>
<pre><code>sqlite&gt; select HEX(RANDOMBLOB(2));
7B4A

sqlite&gt; select SUBSTR(HEX(RANDOMBLOB(2)),0,4);
5BD
</code></pre>
<p>The next one is a little more complex.
The <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-variant-field" title="RFC 9562 section 4.1: Variant Field">variant field</a> consists of a variable number of bits.
For our version 4 UUID, we need to set the first two bits to <code>10</code>, the others can be random.
That gives us a possible range between <code>1000</code> and <code>1011</code>, or a hexadecimal character between <code>8</code> and <code>B</code> inclusive.</p>
<pre><code>select FORMAT('%X', 8 + ABS(RANDOM() % 4));
A
</code></pre>
<p>After this, we need 60 more random bits.
Since this again includes a half byte again (7.5 bytes), we're doing the same <code>SUBSTR</code> trick as before:</p>
<pre><code>sqlite&gt; select SUBSTR(HEX(RANDOMBLOB(8)),0,16);
E83EF2136842130
</code></pre>
<h3 id="stitching-it-together">Stitching it together<a href="#stitching-it-together" aria-label="Link to heading 'Stitching it together'" data-heading-content="Stitching it together" class="anchor"></a></h3>
<p>Using <code>CONCAT(X, ...)</code> we create a single string:</p>
<pre><code>sqlite&gt; select CONCAT(
(x1...&gt;   HEX(RANDOMBLOB(6)),
(x1...&gt;   '4',
(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(2)),0,4),
(x1...&gt;   FORMAT('%X', 8 + ABS(RANDOM() % 4)),
(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(8)),0,16)
(x1...&gt; );

64DF281823BE47249BB2CA8D6450066B
</code></pre>
<p>Then use <code>UNHEX(X)</code> to convert it back into a binary blob:</p>
<pre><code>select UNHEX(CONCAT(
  HEX(RANDOMBLOB(6)),
  '4',
  SUBSTR(HEX(RANDOMBLOB(2)),0,4),
  FORMAT('%X', 8 + ABS(RANDOM() % 4)),
  SUBSTR(HEX(RANDOMBLOB(8)),0,16)
));
</code></pre>
<h3 id="generating-uuids-automatically">Generating UUIDs automatically<a href="#generating-uuids-automatically" aria-label="Link to heading 'Generating UUIDs automatically'" data-heading-content="Generating UUIDs automatically" class="anchor"></a></h3>
<p>The SQL above can be used as the default value for a blob column:</p>
<pre><code>CREATE TABLE &quot;animals&quot; (
  &quot;id&quot; INTEGER PRIMARY KEY NOT NULL,
  &quot;uuid&quot; BLOB UNIQUE NOT NULL
    DEFAULT (UNHEX(CONCAT(
      HEX(RANDOMBLOB(6)), '4',
      SUBSTR(HEX(RANDOMBLOB(2)),0,4),
      FORMAT('%X', 8 + ABS(RANDOM() % 4)),
      SUBSTR(HEX(RANDOMBLOB(8)),0,16))))
    UNIQUE CHECK(LENGTH(&quot;uuid&quot;) = 16),
   &quot;name&quot; TEXT NOT NULL
) STRICT;
</code></pre>
<p>This will generate a UUID if we don't specify one on insert:</p>
<pre><code>sqlite&gt; INSERT INTO &quot;animals&quot; (&quot;name&quot;) VALUES ('Fluffy');

sqlite&gt; SELECT * FROM &quot;animals&quot;;
1|[blob]|Fluffy

sqlite&gt; SELECT &quot;id&quot;, HEX(&quot;uuid&quot;), &quot;name&quot; FROM &quot;animals&quot;;
1|85671F7120F14AA6802B8B4E9213B8BE|Fluffy
</code></pre>
<h2 id="in-human-readable-hexadecimal-form">In human readable (hexadecimal) form<a href="#in-human-readable-hexadecimal-form" aria-label="Link to heading 'In human readable (hexadecimal) form'" data-heading-content="In human readable (hexadecimal) form" class="anchor"></a></h2>
<p>Depending on your use case, you <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-dbms-and-database-considera" title="RFC 9562 section 6.13: Database Considerations">may want to store UUIDs in a <code>TEXT</code> column</a>.</p>
<p>We were close to a &quot;hex-and-dash&quot; string format with the SQL above, until we call the <code>UNHEX(X)</code> function.
<a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" title="RFC 9562 section 4">RFC 9562</a> specifies that dashes are required, so we'll add them in the right places.</p>
<pre><code>sqlite&gt; select CONCAT(
  HEX(RANDOMBLOB(4)),
  '-',
  HEX(RANDOMBLOB(2)),
  '-',
  '4',
  SUBSTR(HEX(RANDOMBLOB(2)),0,4),
  '-',
  FORMAT('%X', 8 + ABS(RANDOM() % 4)),
  SUBSTR(HEX(RANDOMBLOB(2)),0,4),
  '-',
  SUBSTR(HEX(RANDOMBLOB(8)),0,13)
);

199ED82A-3102-42DB-B59B-8C356B6F3FF7
</code></pre>
<p>Voila!</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>As technically correct is the best kind of correct: if you get <em>really</em> lucky with the generated random data, they might actually be valid. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
</ol>
</section>
<div class="footer"><hr><p>Thanks for subscribing to my feed!</p><p>Feel free to e-mail comments or corrections to <a href="mailto:vic@demuzere.be?subject=RE%3A%20Generating%20UUIDs%20in%20SQLite">vic@demuzere.be</a>.</p></div>]]></content></entry></feed>