{"version":"https://jsonfeed.org/version/1.1","title":"Vic Demuzere - SQLite","home_page_url":"https://vic.demuzere.be/articles/tags/sqlite/","feed_url":"https://vic.demuzere.be/articles/tags/sqlite/feed.json","description":"Blog posts tagged SQLite by Vic Demuzere","language":"en","items":[{"id":"https://vic.demuzere.be/articles/generating-uuids-in-sqlite/","content_html":"<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>\n<p>Before we continue, it's important to note that you should verify whether SQLite's random functions are adequate for your usecase.\nThey may not return truly random data.\nHowever, 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;.\nAnd since we all follow that to the letter, it doesn't really matter.. right?</p>\n<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>\n<blockquote>\n<p>Hint: applications can generate globally unique identifiers using this function together with hex() and/or lower().</p>\n</blockquote>\n<p>Combining the <code>randomblob(N)</code> function with <code>hex(N)</code> does indeed create something that vaguely resembles a UUID.\nFor 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>:\nvalid 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>\n<p>We can do better!</p>\n<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>\n<p>The binary representation of a version 4 UUID consists of:</p>\n<ul>\n<li>48 bits (6 bytes) random data</li>\n<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>\n<li>6 bits (1.5 bytes) random data</li>\n<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>\n<li>60 bits (7.5 bytes) random data</li>\n</ul>\n<p>As far as I know, we can't modify individual bytes in a blob using the available functions in SQLite.\nHowever, we do have access to a lot of string functions.\nTaking a detour using HEX encoding provides a solution.\nWe'll first concatenate a HEX string containing the data we want, then convert it back to a binary blob.\nNote that each byte is represented by two hexadecimal characters.</p>\n<p>The first 48 random bits are easy:</p>\n<pre><code class=\"language-sqlite\">sqlite&gt; select HEX(RANDOMBLOB(6));\r\n4004714237AF\r\n</code></pre>\n<p>The first 4 bits of the next byte need to be set to <code>0100</code> (or <code>4</code> in hex).\nAs every byte is represented by two characters in hexadecimal, we can just use a literal <code>4</code> for the next character.</p>\n<pre><code class=\"language-sqlite\">sqlite&gt; select '4';\r\n4\r\n</code></pre>\n<p>Next, we need 6 bits of random data.\nWe can only generate random bytes using <code>RANDOMBLOB(N)</code>, but we need 1.5 bytes.\nLuckily, because we are working with a hexadecimal string, we can generate 2 bytes (4 hex characters) and take the first three characters.</p>\n<pre><code>sqlite&gt; select HEX(RANDOMBLOB(2));\r\n7B4A\r\n\r\nsqlite&gt; select SUBSTR(HEX(RANDOMBLOB(2)),0,4);\r\n5BD\r\n</code></pre>\n<p>The next one is a little more complex.\nThe <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.\nFor our version 4 UUID, we need to set the first two bits to <code>10</code>, the others can be random.\nThat 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>\n<pre><code>select FORMAT('%X', 8 + ABS(RANDOM() % 4));\r\nA\r\n</code></pre>\n<p>After this, we need 60 more random bits.\nSince this again includes a half byte again (7.5 bytes), we're doing the same <code>SUBSTR</code> trick as before:</p>\n<pre><code>sqlite&gt; select SUBSTR(HEX(RANDOMBLOB(8)),0,16);\r\nE83EF2136842130\r\n</code></pre>\n<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>\n<p>Using <code>CONCAT(X, ...)</code> we create a single string:</p>\n<pre><code>sqlite&gt; select CONCAT(\r\n(x1...&gt;   HEX(RANDOMBLOB(6)),\r\n(x1...&gt;   '4',\r\n(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n(x1...&gt;   FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(8)),0,16)\r\n(x1...&gt; );\r\n\r\n64DF281823BE47249BB2CA8D6450066B\r\n</code></pre>\n<p>Then use <code>UNHEX(X)</code> to convert it back into a binary blob:</p>\n<pre><code>select UNHEX(CONCAT(\r\n  HEX(RANDOMBLOB(6)),\r\n  '4',\r\n  SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n  FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n  SUBSTR(HEX(RANDOMBLOB(8)),0,16)\r\n));\r\n</code></pre>\n<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>\n<p>The SQL above can be used as the default value for a blob column:</p>\n<pre><code>CREATE TABLE &quot;animals&quot; (\r\n  &quot;id&quot; INTEGER PRIMARY KEY NOT NULL,\r\n  &quot;uuid&quot; BLOB UNIQUE NOT NULL\r\n    DEFAULT (UNHEX(CONCAT(\r\n      HEX(RANDOMBLOB(6)), '4',\r\n      SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n      FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n      SUBSTR(HEX(RANDOMBLOB(8)),0,16))))\r\n    UNIQUE CHECK(LENGTH(&quot;uuid&quot;) = 16),\r\n   &quot;name&quot; TEXT NOT NULL\r\n) STRICT;\r\n</code></pre>\n<p>This will generate a UUID if we don't specify one on insert:</p>\n<pre><code>sqlite&gt; INSERT INTO &quot;animals&quot; (&quot;name&quot;) VALUES ('Fluffy');\r\n\r\nsqlite&gt; SELECT * FROM &quot;animals&quot;;\r\n1|[blob]|Fluffy\r\n\r\nsqlite&gt; SELECT &quot;id&quot;, HEX(&quot;uuid&quot;), &quot;name&quot; FROM &quot;animals&quot;;\r\n1|85671F7120F14AA6802B8B4E9213B8BE|Fluffy\r\n</code></pre>\n<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>\n<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>\n<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.\n<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>\n<pre><code>sqlite&gt; select CONCAT(\r\n  HEX(RANDOMBLOB(4)),\r\n  '-',\r\n  HEX(RANDOMBLOB(2)),\r\n  '-',\r\n  '4',\r\n  SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n  '-',\r\n  FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n  SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n  '-',\r\n  SUBSTR(HEX(RANDOMBLOB(8)),0,13)\r\n);\r\n\r\n199ED82A-3102-42DB-B59B-8C356B6F3FF7\r\n</code></pre>\n<p>Voila!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<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>\n</li>\n</ol>\n</section>\n<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>","url":"https://vic.demuzere.be/articles/generating-uuids-in-sqlite/","date_published":"2025-12-24T15:28:41Z","date_modified":"2026-01-04T17:26:44Z","language":"en"}]}