<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Frag's blog]]></title><description><![CDATA[Frag's blog]]></description><link>https://fragland.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 14 Sep 2026 06:06:36 GMT</lastBuildDate><atom:link href="https://fragland.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[All You Need to Know About UUIDs]]></title><description><![CDATA[Have you ever been curious about what UUIDs can really do? 😯 Maybe you've seen them around before, like when you're trying to keep track of information in a database, but have you ever stopped to think about their full potential? 
This article is he...]]></description><link>https://fragland.dev/all-you-need-to-know-about-uuids</link><guid isPermaLink="true">https://fragland.dev/all-you-need-to-know-about-uuids</guid><category><![CDATA[development]]></category><category><![CDATA[fundamentals]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Matteo Crosta]]></dc:creator><pubDate>Thu, 27 Jul 2023 22:59:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/KmKZV8pso-s/upload/6f4872e970528e7ee1475ac9b7d044df.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever been curious about what UUIDs can really do? 😯 Maybe you've seen them around before, like when you're trying to keep track of information in a database, but have you ever stopped to think about their full potential? </p>
<p>This article is here to help you understand the nitty-gritty details of UUIDs.</p>
<h3 id="heading-key-aspects">KEY ASPECTS</h3>
<ul>
<li><p>UUID is an acronym for Universally Unique IDentifier. It is also known as GUID.</p>
</li>
<li><p>UUIDs are a fast and secure way to identify any type of information. Essentially, they serve as an ideal primary key for your databases.</p>
</li>
<li><p>They are unique; the risk of generating the same ID is close to zero.</p>
</li>
<li><p>They are always made up of 128 bit</p>
</li>
<li><p>The generation process is very fast and safe</p>
</li>
<li><p>There are five versions of UUIDs defined by RFC4122, each suited for different purposes. However, most likely, you will only need to use version 4.</p>
</li>
</ul>
<h2 id="heading-format">FORMAT</h2>
<p>To make it user-friendly, UUIDs are consistently displayed as a sequence of hex numbers with a fixed length. The numbers are categorized into five groups and are united by a hyphen.</p>
<p><code>970ea114-fa06–472f-acca-df384a39bf7e</code></p>
<p>Going deeper, the parts are named as follows:</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*N7zjL0wS9NIVqxzFbFrdeQ.png" alt /></p>
<p>Did you know that UUID version can be determined by checking the first character of the third group?<br />In the example provided, the UUID version is "4".</p>
<h2 id="heading-version-1-timestamp-and-mac-address"><strong>VERSION 1: TIMESTAMP and MAC ADDRESS</strong></h2>
<p>This version is based on a timestamp and the mac address of the network card, by combining these two parameters we obtain UUIDs that are:</p>
<ul>
<li><p><strong>unique over time</strong>: the same computer cannot regenerate the same UUID twice, nor during the same timestamp nor in the future</p>
</li>
<li><p><strong>unique over space</strong>: UUIDs generated by different computers at the same time will always be different (thanks to their Mac address)</p>
</li>
</ul>
<p>Here’s their composition:</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*0k6Oog3nRQ60rqOmggtErQ.png" alt /></p>
<ul>
<li><p><em>the</em> <code>timestamp</code> is a 60 bit number obtained from the current UTC timestamp, using a resolution of up to 100 microseconds</p>
</li>
<li><p><code>clock sequence</code> is a 14 bit unsigned integer used to disambiguate when many UUIDs are made before the timestamp changes</p>
</li>
<li><p><em>MAC address</em> of the primary network card identifier. This value can be faked if a network card is not available.</p>
</li>
</ul>
<h3 id="heading-version-4-random-madness"><strong>VERSION 4: RANDOM MADNESS</strong></h3>
<p>This is easy: every byte of the UUID is randomic.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*qlZIu4-1UsJzEak6Muu9OA.png" alt /></p>
<p>Use this as a general purpose uuid, it will work in almost every scenario.</p>
<p>By involving random operations, generating lots of uuid v4 can be quite slow, consider other versions if you need to generate lots of ids at the same time.</p>
<h3 id="heading-version-5-name-and-namespace"><strong>VERSION 5: NAME and NAMESPACE</strong></h3>
<p>This version involves two inputs called <em>name</em> and <em>namespace:</em></p>
<ul>
<li><p><code>name</code> is any sequence of bytes, such as a string</p>
</li>
<li><p>the <code>namespace</code> must be a valid UUID</p>
</li>
</ul>
<p>UUID v5 is obtained from the <a target="_blank" href="https://en.wikipedia.org/wiki/SHA-1">sha1 hash</a> of the namespace concatenated with the name, thus leading to a UUID that is predictable:</p>
<ul>
<li><p><strong>the same name and namespace will always produce the same UUID</strong></p>
</li>
<li><p>the same name on different namespaces will produce different UUIDs</p>
</li>
<li><p>different names on the same namespace will produce different UUIDs</p>
</li>
</ul>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*XUqmn5s4Ir6cFboO5nVHSQ.png" alt /></p>
<p>A possible use scenario for this kind of uuid is the storage of encoded passwords in a database or to verify the integrity of a string.</p>
<h2 id="heading-what-about-version-2-and-3">What about version 2 and 3?</h2>
<ul>
<li><p>version 2 also called “DCE security” is a variant of version 1 that introduces a “local domain” value and a shorter timestamp (only 28 bits).<br />  This version is barely documented and most libraries do not implement it at all, just forget about it</p>
</li>
<li><p>version 3 works just like version 5 but uses md5 instead of sha1, for this reason, it is considered less safe and its usage is discouraged.</p>
</li>
</ul>
<p>References:<br /><a target="_blank" href="https://en.wikipedia.org/wiki/Universally_unique_identifier">Wikipedia</a>, <a target="_blank" href="https://www.ietf.org/rfc/rfc4122.txt">RFC4122</a></p>
]]></content:encoded></item><item><title><![CDATA[Mastering PostgreSQL Table Partitioning]]></title><description><![CDATA[Table partitioning is a highly effective technique used to improve the performance of very large database tables. By dividing the table's content into smaller sub-tables, known as partitions, the overall size of the table is reduced, leading to signi...]]></description><link>https://fragland.dev/a-guide-to-table-partitioning-with-postgresql-12</link><guid isPermaLink="true">https://fragland.dev/a-guide-to-table-partitioning-with-postgresql-12</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Performance Optimization]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Matteo Crosta]]></dc:creator><pubDate>Thu, 27 Jul 2023 09:16:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/YXwt-vJ3szA/upload/4c1df44c0fe9efc10fc42bd9f91c4a42.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Table partitioning is a highly effective technique used to improve the performance of very large database tables. By dividing the table's content into smaller sub-tables, known as partitions, the overall size of the table is reduced, leading to significant performance enhancements.</p>
<h2 id="heading-when-should-you-partition-a-table">When should you partition a table?</h2>
<ul>
<li><p>If your table is too big for your server's RAM.<br />  You should consider partitioning it: when a table reaches a few gigabytes in size, it's time to split it up.</p>
</li>
<li><p>If you're working with massive amounts of data<br />  Don't bother partitioning until you've got millions of records. Otherwise, you won't see much of a performance boost.</p>
</li>
<li><p>If your table can be logically broken down into smaller chunks, Example: you have a table full of server logs. You could split them up by date, so all the logs from the same day are in one single partition. This makes it much easier to do tasks like deleting old logs by just dropping the partition.</p>
</li>
</ul>
<h2 id="heading-available-partitioning-types"><strong>Available partitioning types</strong></h2>
<p>Postgres has built-in support for three types of partitioning covering the most common use cases.</p>
<h3 id="heading-partition-by-range"><strong>Partition by Range</strong></h3>
<blockquote>
<p>The table is partitioned into “ranges” defined by a key column or a set of columns, with no overlap between the ranges of values assigned to different partitions</p>
</blockquote>
<p>In the following example, the <em>people</em> table will be partitioned by <em>birth_date</em></p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">int</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    birth_date <span class="hljs-built_in">date</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    country_code <span class="hljs-built_in">character</span>(<span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">text</span>
) <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">RANGE</span> (birth_date);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_y2000 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">FROM</span> (<span class="hljs-string">'2000-01-01'</span>) <span class="hljs-keyword">TO</span> (<span class="hljs-string">'2001-01-01'</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_y2001 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">FROM</span> (<span class="hljs-string">'2001-01-01'</span>) <span class="hljs-keyword">TO</span> (<span class="hljs-string">'2002-01-01'</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_y2002 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">FROM</span> (<span class="hljs-string">'2002-01-01'</span>) <span class="hljs-keyword">TO</span> (<span class="hljs-string">'2003-01-01'</span>);
</code></pre>
<p>Let’s try it:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> people (<span class="hljs-keyword">id</span>, birth_date, country_code, <span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span>
   (<span class="hljs-number">1</span>, <span class="hljs-string">'2000-01-01'</span>, <span class="hljs-string">'US'</span>, <span class="hljs-string">'John'</span>),
   (<span class="hljs-number">2</span>, <span class="hljs-string">'2000-02-02'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'Jane'</span>),
   (<span class="hljs-number">3</span>, <span class="hljs-string">'2001-03-03'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'Bob'</span>);
&gt; <span class="hljs-keyword">INSERT</span> <span class="hljs-number">0</span> <span class="hljs-number">3</span>

<span class="hljs-keyword">SELECT</span> schemaname,relname,n_live_tup 
   <span class="hljs-keyword">FROM</span> pg_stat_user_tables 
   <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> n_live_tup <span class="hljs-keyword">DESC</span>;

schemaname  |   relname    | n_live_tup 
<span class="hljs-comment">------------+--------------+------------</span>
 public     | people_y2000 |          2
 public     | people_y2001 |          1
 public     | people_y2002 |          0
</code></pre>
<p>As you can see we inserted three records into the master table, <em>people.</em> Since the table is partitioned by birth_date, two records have been added to partition <em>people_y2000</em>, one into <em>people_y2001</em> while <em>people_y2002</em> is still empty.</p>
<h3 id="heading-partition-by-list"><strong>Partition by List</strong></h3>
<blockquote>
<p><em>The table is partitioned by explicitly listing which key values appear in each partition.</em></p>
</blockquote>
<p>Taking the same example, let’s add a <em>country_code</em> column and use it as the partitioning key</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">int</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    birth_date <span class="hljs-built_in">date</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    country_code <span class="hljs-built_in">character</span>(<span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">text</span>
) <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">LIST</span> (country_code);

<span class="hljs-comment">-- Partition for people living in Europe</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_EU <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">IN</span> (<span class="hljs-string">'AT'</span>, <span class="hljs-string">'DE'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'ES'</span>, ..... );

<span class="hljs-comment">-- Partition for people living in United States</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_US <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">IN</span> (<span class="hljs-string">'US'</span>);
</code></pre>
<p>Let’s try it:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> people (<span class="hljs-keyword">id</span>, birth_date, country_code, <span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span>
   (<span class="hljs-number">1</span>, <span class="hljs-string">'2000-01-01'</span>, <span class="hljs-string">'US'</span>, <span class="hljs-string">'John'</span>),
   (<span class="hljs-number">2</span>, <span class="hljs-string">'2000-02-02'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'Jane'</span>),
   (<span class="hljs-number">3</span>, <span class="hljs-string">'2001-03-03'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'Bob'</span>);
&gt; <span class="hljs-keyword">INSERT</span> <span class="hljs-number">0</span> <span class="hljs-number">3</span>

<span class="hljs-keyword">SELECT</span> schemaname,relname,n_live_tup 
   <span class="hljs-keyword">FROM</span> pg_stat_user_tables 
   <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> n_live_tup <span class="hljs-keyword">DESC</span>;

 schemaname |  relname  | n_live_tup 
<span class="hljs-comment">------------+-----------+------------</span>
 public     | people_eu |          2
 public     | people_us |          1
</code></pre>
<p>Again, PostgreSQL moved every row to the correct partition.</p>
<h3 id="heading-partition-by-hash"><strong>Partition by Hash</strong></h3>
<blockquote>
<p><em>The table is partitioned by specifying a modulus and a remainder for each partition. Each partition will hold the rows for which the hash value of the partition key divided by the specified modulus will produce the specified remainder.</em></p>
</blockquote>
<p>This type is useful when we can’t logically divide our data, but we can only reduce the table size by spreading rows into many smaller partitions.</p>
<p>The following SQL will divide people into three tables, every table will contain (almost) the same number of rows.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">int</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    birth_date <span class="hljs-built_in">date</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    country_code <span class="hljs-built_in">character</span>(<span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">text</span>
) <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">HASH</span> (<span class="hljs-keyword">id</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_1 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">WITH</span> (MODULUS <span class="hljs-number">3</span>, <span class="hljs-keyword">REMAINDER</span> <span class="hljs-number">0</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_2 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">WITH</span> (MODULUS <span class="hljs-number">3</span>, <span class="hljs-keyword">REMAINDER</span> <span class="hljs-number">1</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_3 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">WITH</span> (MODULUS <span class="hljs-number">3</span>, <span class="hljs-keyword">REMAINDER</span> <span class="hljs-number">2</span>);
</code></pre>
<p>Let’s try it:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> people (<span class="hljs-keyword">id</span>, birth_date, country_code, <span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span>
   (<span class="hljs-number">1</span>, <span class="hljs-string">'2000-01-01'</span>, <span class="hljs-string">'US'</span>, <span class="hljs-string">'John'</span>),
   (<span class="hljs-number">2</span>, <span class="hljs-string">'2000-02-02'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'Jane'</span>),
   (<span class="hljs-number">3</span>, <span class="hljs-string">'2001-03-03'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'Bob'</span>);
&gt; <span class="hljs-keyword">INSERT</span> <span class="hljs-number">0</span> <span class="hljs-number">3</span>

<span class="hljs-keyword">SELECT</span> schemaname,relname,n_live_tup 
   <span class="hljs-keyword">FROM</span> pg_stat_user_tables 
   <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> n_live_tup <span class="hljs-keyword">DESC</span>;

 schemaname | relname  | n_live_tup 
<span class="hljs-comment">------------+----------+------------</span>
 public     | people_1 |          1
 public     | people_2 |          1
 public     | people_3 |          1
</code></pre>
<p>As you can see, the three records have been evenly split across all the partitions available.</p>
<h2 id="heading-default-partition"><strong>Default partition</strong></h2>
<p>What happens when you try to insert a record that can’t fit into any partition?</p>
<p>Let’s go back to the people table defined in the list partitioning chapter and try to add Linda, from Canada:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> people (<span class="hljs-keyword">id</span>, birth_date, country_code, <span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span>
   (<span class="hljs-number">4</span>, <span class="hljs-string">'2002-04-04'</span>, <span class="hljs-string">'CA'</span>, <span class="hljs-string">'Linda'</span>);

ERROR:  no partition of relation "people" found for rowDETAILS: Partition key of the failing row contains (country_code) = (CA).
</code></pre>
<p>The INSERT will fail because PostgreSQL doesn’t know where to add that record.</p>
<p>The most obvious solution would be to add a new partition, but if we have to do it for every country in the world, we would end up with hundreds of tables with a very small number of records. Not really nice.</p>
<p>Luckily, it’s possible to define a <strong>DEFAULT</strong> partition!</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_default <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people <span class="hljs-keyword">DEFAULT</span>;
</code></pre>
<p>Trying the same inserts again, it will result in:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> people (<span class="hljs-keyword">id</span>, birth_date, country_code, <span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span>
   (<span class="hljs-number">1</span>, <span class="hljs-string">'2000-01-01'</span>, <span class="hljs-string">'US'</span>, <span class="hljs-string">'John'</span>),
   (<span class="hljs-number">2</span>, <span class="hljs-string">'2000-02-02'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'Jane'</span>),
   (<span class="hljs-number">3</span>, <span class="hljs-string">'2001-03-03'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'Bob'</span>),
   (<span class="hljs-number">4</span>, <span class="hljs-string">'2002-04-04'</span>, <span class="hljs-string">'CA'</span>, <span class="hljs-string">'Linda'</span>);
&gt; <span class="hljs-keyword">INSERT</span> <span class="hljs-number">0</span> <span class="hljs-number">4</span>

schemaname |    relname     | n_live_tup 
<span class="hljs-comment">------------+----------------+------------</span>
 <span class="hljs-keyword">public</span>     | people_eu      |          <span class="hljs-number">2</span>
 <span class="hljs-keyword">public</span>     | people_us      |          <span class="hljs-number">1</span>
 <span class="hljs-keyword">public</span>     | people_default |          <span class="hljs-number">1</span>
</code></pre>
<p>As you can see, Linda has now been added to <em>people_default</em>.</p>
<h2 id="heading-sub-partitioning"><strong>Sub Partitioning</strong></h2>
<p>A single partition can also be a partitioned table!</p>
<p>Back to the LIST example, we can imagine that people_EU will contain a lot of records, so we may want to subdivide it by hash:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">int</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    birth_date <span class="hljs-built_in">date</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    country_code <span class="hljs-built_in">character</span>(<span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">text</span>
) <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">LIST</span> (country_code);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_US <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">IN</span> (<span class="hljs-string">'US'</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_EU <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">IN</span> (<span class="hljs-string">'AT'</span>, <span class="hljs-string">'DE'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'ES'</span>, ..... )
    <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">HASH</span> (<span class="hljs-keyword">id</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_EU_1 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people_EU
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">WITH</span> (MODULUS <span class="hljs-number">3</span>, <span class="hljs-keyword">REMAINDER</span> <span class="hljs-number">0</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_EU_2 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people_EU
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">WITH</span> (MODULUS <span class="hljs-number">3</span>, <span class="hljs-keyword">REMAINDER</span> <span class="hljs-number">1</span>);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> people_EU_3 <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">OF</span> people_EU
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">WITH</span> (MODULUS <span class="hljs-number">3</span>, <span class="hljs-keyword">REMAINDER</span> <span class="hljs-number">2</span>);
</code></pre>
<p>This will result in:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> people (<span class="hljs-keyword">id</span>, birth_date, country_code, <span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span>
   (<span class="hljs-number">1</span>, <span class="hljs-string">'2000-01-01'</span>, <span class="hljs-string">'US'</span>, <span class="hljs-string">'John'</span>),
   (<span class="hljs-number">2</span>, <span class="hljs-string">'2000-02-02'</span>, <span class="hljs-string">'IT'</span>, <span class="hljs-string">'Jane'</span>),
   (<span class="hljs-number">3</span>, <span class="hljs-string">'2001-03-03'</span>, <span class="hljs-string">'FR'</span>, <span class="hljs-string">'Bob'</span>);
&gt; <span class="hljs-keyword">INSERT</span> <span class="hljs-number">0</span> <span class="hljs-number">3</span>

<span class="hljs-keyword">SELECT</span> schemaname,relname,n_live_tup 
   <span class="hljs-keyword">FROM</span> pg_stat_user_tables 
   <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> n_live_tup <span class="hljs-keyword">DESC</span>;

schemaname |   relname   | n_live_tup 
<span class="hljs-comment">------------+-------------+------------</span>
 public     | people_eu_2 |          1
 public     | people_eu_1 |          1
 public     | people_us   |          1
 public     | people_eu_3 |          0
</code></pre>
<h2 id="heading-partition-operations"><strong>Partition operations</strong></h2>
<h3 id="heading-attaching-and-detaching-partitions"><strong>Attaching and detaching partitions</strong></h3>
<p>As we have seen, single partitions can be created and dropped whenever we want, but what if we want to exclude some records from the master table without deleting them?</p>
<p>The answer is: <strong>DETACH</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> people DETACH <span class="hljs-keyword">PARTITION</span> people_us;
</code></pre>
<p>A detached partition will act as a normal table, so it will be possible to insert records that would violate the partition constraints.</p>
<p>The reverse operation, <strong>ATTACH</strong>, is as easy as:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> people ATTACH <span class="hljs-keyword">PARTITION</span> people_us <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">VALUES</span> <span class="hljs-keyword">IN</span> (<span class="hljs-string">'US'</span>);
</code></pre>
<h3 id="heading-indexing"><strong>Indexing</strong></h3>
<p>Too bad, PostgreSQL doesn’t allow to create a single index covering every partition of the table, but you have to create an index for every partition.</p>
<p>The bad news about this is that the <strong>primary key</strong>, or any other <strong>unique index</strong>, must include the columns used on the <code>partition by</code> statement.</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- THIS WON'T WORK</span>

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">UNIQUE</span> <span class="hljs-keyword">INDEX</span> idx_uniq <span class="hljs-keyword">ON</span> people (<span class="hljs-keyword">id</span>);
&gt; ERROR:  insufficient columns in UNIQUE constraint definition
&gt; DETAILS: UNIQUE constraint on table "people" lacks column "country_code" which is part of the partition key.


<span class="hljs-comment">-- THIS WORKS!</span>

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">UNIQUE</span> <span class="hljs-keyword">INDEX</span> idx_uniq <span class="hljs-keyword">ON</span> people (<span class="hljs-keyword">id</span>, country_code);
&gt; <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span>
</code></pre>
<p>The reason behind this is the fact that every partition is treated as an independent table, so adding the partition key to the index is the only way to grant the uniqueness of a record across the whole table.</p>
<p>Note that creating an index on the master table will automatically replicate it to every attached partition:</p>
<pre><code class="lang-plaintext">CREATE UNIQUE INDEX idx_uniq ON people (id, country_code);


-- Check created indexes

SELECT tablename, indexname FROM pg_indexes
WHERE schemaname = 'public' ORDER BY tablename, indexname;

tablename      | indexname
---------------+-------------------------------------
people         | idx_uniq
people_default | people_default_id_country_code_idx
people_eu      | people_eu_id_country_code_idx
</code></pre>
<p>And that's all you need to know!<br />Follow me if you liked this article or leave a comment.</p>
]]></content:encoded></item><item><title><![CDATA[Howto: drop Postgres tables matching a pattern]]></title><description><![CDATA[Suppose that your database has lots of tables, with similar names (maybe partitions of a bigger table?), here's a quick way to drop them in bulk
SELECT 'drop table '||n.nspname ||'.'|| c.relname||';' as "Name" 
FROM pg_catalog.pg_class c
     LEFT JO...]]></description><link>https://fragland.dev/howto-drop-postgres-tables-matching-a-pattern</link><guid isPermaLink="true">https://fragland.dev/howto-drop-postgres-tables-matching-a-pattern</guid><category><![CDATA[#howtos]]></category><category><![CDATA[SQL]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Matteo Crosta]]></dc:creator><pubDate>Mon, 27 Jul 2020 09:49:16 GMT</pubDate><content:encoded><![CDATA[<p>Suppose that your database has lots of tables, with similar names (<a target="_blank" href="https://fragland.dev/mastering-postgresql-table-partitioning">maybe partitions of a bigger table</a>?), here's a quick way to drop them in bulk</p>
<pre><code class="lang-bash">SELECT <span class="hljs-string">'drop table '</span>||n.nspname ||<span class="hljs-string">'.'</span>|| c.relname||<span class="hljs-string">';'</span> as <span class="hljs-string">"Name"</span> 
FROM pg_catalog.pg_class c
     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN (<span class="hljs-string">'r'</span>,<span class="hljs-string">'v'</span>,<span class="hljs-string">'S'</span>,<span class="hljs-string">''</span>)
     AND n.nspname &lt;&gt; <span class="hljs-string">'pg_catalog'</span>
     AND n.nspname &lt;&gt; <span class="hljs-string">'information_schema'</span>
     AND n.nspname !~ <span class="hljs-string">'^pg_toast'</span>
     AND pg_catalog.pg_table_is_visible(c.oid)
     AND c.relname ilike <span class="hljs-string">'tbl_2015_%'</span>
</code></pre>
<p>This query will search into the <strong>pg_catalog</strong> for all the tables named <em>tbl_2015_something</em> and it will output a <strong>drop</strong> command for each one:</p>
<pre><code class="lang-bash">         Name          
-----------------------
 drop table public.tbl_2015_01;
 drop table public.tbl_2015_02;
 drop table public.tbl_2015_03;
 ......
</code></pre>
<p>Save the output to a .sql file and execute it with <em>psql -f yoursqlfile.sql</em></p>
<p><em>Et voilà</em>!</p>
]]></content:encoded></item></channel></rss>