<?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[Omnigres]]></title><description><![CDATA[Omnigres]]></description><link>https://blog.omnigres.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1722614064807/67886681-b8b5-4fa6-b32a-f7cda5cf256a.png</url><title>Omnigres</title><link>https://blog.omnigres.com</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 21 May 2026 07:16:55 GMT</lastBuildDate><atom:link href="https://blog.omnigres.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Troubleshooting SQL queries with omni_id]]></title><description><![CDATA[Once you have a complex SQL query, sometimes the query doesn't fail, but it returns unexpected results.
Something is going on with it, but it's hard to tell what exactly, and SQL and databases don't always help you figure that out. Let's look at this...]]></description><link>https://blog.omnigres.com/troubleshooting-sql-queries-with-omni-id</link><guid isPermaLink="true">https://blog.omnigres.com/troubleshooting-sql-queries-with-omni-id</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[SQL]]></category><category><![CDATA[TypeSafety]]></category><dc:creator><![CDATA[Yurii Rashkovskii]]></dc:creator><pubDate>Fri, 02 Aug 2024 19:10:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722625938347/f9cc5999-5f55-4a67-964e-821e9589a9c1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Once you have a complex SQL query, sometimes the query doesn't fail, but it returns unexpected results.</p>
<p>Something is going on with it, but it's hard to tell what exactly, and SQL and databases don't always help you figure that out. Let's look at this fictitious scenario: we have a users table, a products table, and an orders table. Every order contains items, and we have reviews for the products.</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> users
(
    id         <span class="hljs-type">serial</span> <span class="hljs-keyword">primary key</span>,
    username   <span class="hljs-type">varchar</span>(<span class="hljs-number">50</span>)  <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>,
    email      <span class="hljs-type">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>,
    created_at <span class="hljs-type">timestamp</span> <span class="hljs-keyword">default</span> <span class="hljs-built_in">current_timestamp</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> products
(
    id           <span class="hljs-type">serial</span> <span class="hljs-keyword">primary key</span>,
    product_name <span class="hljs-type">varchar</span>(<span class="hljs-number">100</span>)   <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>,
    price        <span class="hljs-type">numeric</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>,
    stock        <span class="hljs-type">int</span>            <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>,
    created_at   <span class="hljs-type">timestamp</span> <span class="hljs-keyword">default</span> <span class="hljs-built_in">current_timestamp</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> orders
(
    id           <span class="hljs-type">serial</span> <span class="hljs-keyword">primary key</span>,
    user_id      <span class="hljs-type">int</span> <span class="hljs-keyword">references</span> users (id),
    order_date   <span class="hljs-type">timestamp</span> <span class="hljs-keyword">default</span> <span class="hljs-built_in">current_timestamp</span>,
    total_amount <span class="hljs-type">numeric</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> order_items
(
    id         <span class="hljs-type">serial</span> <span class="hljs-keyword">primary key</span>,
    order_id   <span class="hljs-type">int</span> <span class="hljs-keyword">references</span> orders (id),
    product_id <span class="hljs-type">int</span> <span class="hljs-keyword">references</span> products (id),
    quantity   <span class="hljs-type">int</span>            <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>,
    price      <span class="hljs-type">numeric</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> reviews
(
    id          <span class="hljs-type">serial</span> <span class="hljs-keyword">primary key</span>,
    user_id     <span class="hljs-type">int</span> <span class="hljs-keyword">references</span> users (id),
    product_id  <span class="hljs-type">int</span> <span class="hljs-keyword">references</span> products (id),
    rating      <span class="hljs-type">int</span> <span class="hljs-keyword">check</span> (rating <span class="hljs-keyword">between</span> <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> <span class="hljs-number">5</span>),
    review_text <span class="hljs-type">text</span>,
    created_at  <span class="hljs-type">timestamp</span> <span class="hljs-keyword">default</span> <span class="hljs-built_in">current_timestamp</span>
);
</code></pre>
<p>Let's add some data!</p>
<pre><code class="lang-pgsql">
<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> users (username, email)
<span class="hljs-keyword">values</span> (<span class="hljs-string">'john_doe'</span>, <span class="hljs-string">'john@example.com'</span>),
       (<span class="hljs-string">'jane_smith'</span>, <span class="hljs-string">'jane@example.com'</span>),
       (<span class="hljs-string">'alice_jones'</span>, <span class="hljs-string">'alice@example.com'</span>);

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> products (product_name, price, stock)
<span class="hljs-keyword">values</span> (<span class="hljs-string">'Laptop'</span>, <span class="hljs-number">1200.00</span>, <span class="hljs-number">10</span>),
       (<span class="hljs-string">'Smartphone'</span>, <span class="hljs-number">800.00</span>, <span class="hljs-number">20</span>),
       (<span class="hljs-string">'Tablet'</span>, <span class="hljs-number">500.00</span>, <span class="hljs-number">15</span>),
       (<span class="hljs-string">'Headphones'</span>, <span class="hljs-number">150.00</span>, <span class="hljs-number">30</span>);

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> orders (user_id, total_amount)
<span class="hljs-keyword">values</span> (<span class="hljs-number">1</span>, <span class="hljs-number">1950.00</span>),
       (<span class="hljs-number">2</span>, <span class="hljs-number">800.00</span>),
       (<span class="hljs-number">3</span>, <span class="hljs-number">650.00</span>);

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> order_items (order_id, product_id, quantity, price)
<span class="hljs-keyword">values</span> (<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1200.00</span>), <span class="hljs-comment">-- Laptop</span>
       (<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">150.00</span>),  <span class="hljs-comment">-- Headphones</span>
       (<span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>, <span class="hljs-number">800.00</span>),  <span class="hljs-comment">-- Smartphone</span>
       (<span class="hljs-number">3</span>, <span class="hljs-number">3</span>, <span class="hljs-number">1</span>, <span class="hljs-number">500.00</span>),  <span class="hljs-comment">-- Tablet</span>
       (<span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">1</span>, <span class="hljs-number">150.00</span>);  <span class="hljs-comment">-- Headphones</span>

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> reviews (user_id, product_id, rating, review_text)
<span class="hljs-keyword">values</span> (<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">5</span>, <span class="hljs-string">'Excellent laptop!'</span>),
       (<span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-string">'Great smartphone, but a bit pricey.'</span>),
       (<span class="hljs-number">3</span>, <span class="hljs-number">3</span>, <span class="hljs-number">3</span>, <span class="hljs-string">'Tablet is okay, but could be better.'</span>),
       (<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-string">'Fantastic headphones for the price.'</span>),
       (<span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">4</span>, <span class="hljs-string">'Good quality sound.'</span>);
</code></pre>
<p>Finally, let's look at the query we might be having trouble with.</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">select</span> <span class="hljs-keyword">distinct</span> o.id                                   <span class="hljs-keyword">as</span> order_id,
                o.order_date,
                u.username,
                u.email,
                p.product_name,
                oi.quantity,
                oi.price,
                (oi.quantity * oi.price)               <span class="hljs-keyword">as</span> total_product_cost,
                count(oi.id) <span class="hljs-keyword">over</span> (<span class="hljs-keyword">partition</span> <span class="hljs-keyword">by</span> o.id)  <span class="hljs-keyword">as</span> total_items,
                avg(r.rating) <span class="hljs-keyword">over</span> (<span class="hljs-keyword">partition</span> <span class="hljs-keyword">by</span> p.id) <span class="hljs-keyword">as</span> average_rating
<span class="hljs-keyword">from</span> orders o
         <span class="hljs-keyword">join</span>
     users u <span class="hljs-keyword">on</span> o.user_id = u.id
         <span class="hljs-keyword">join</span>
     order_items oi <span class="hljs-keyword">on</span> o.id = oi.order_id
         <span class="hljs-keyword">join</span>
     products p <span class="hljs-keyword">on</span> oi.order_id = p.id
         <span class="hljs-keyword">left join</span>
     reviews r <span class="hljs-keyword">on</span> p.id = r.product_id
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> o.id, p.product_name
</code></pre>
<p>This query retrieves information about orders, including what people bought, how much they spent, the number of items per order, and the average rating for each product. Let's run it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722624561898/98c46b4f-eed4-49fc-98b8-c7916683af8f.png" alt class="image--center mx-auto" /></p>
<p>It looks fine at first, but if you examine it closely, you'll notice something is off. For example, there is a laptop with a quantity of 1 and another entry for the same laptop with a quantity of 5. What does that mean? The data we populated does not match. So, something is wrong with the query.</p>
<p>This is where the recently released omni_id extension can help! Let's explore it.</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">select</span> identity_type(<span class="hljs-type">name</span>)
<span class="hljs-keyword">from</span> unnest(<span class="hljs-string">'{user_id, product_id, order_id, order_item_id, review_id}'</span>::<span class="hljs-type">text</span>[]) t(<span class="hljs-type">name</span>);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> users
(
    id         user_id <span class="hljs-keyword">primary key</span> <span class="hljs-keyword">default</span> user_id_nextval(),
    <span class="hljs-comment">-- rest is the same</span>
);


<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> orders
(
    id           order_id <span class="hljs-keyword">primary key</span> <span class="hljs-keyword">default</span> order_id_nextval(),
    user_id      user_id <span class="hljs-keyword">references</span> users (id),
    <span class="hljs-comment">-- rest is the same</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> products
(
    id           product_id <span class="hljs-keyword">primary key</span> <span class="hljs-keyword">default</span> product_id_nextval(),
    <span class="hljs-comment">-- rest is the same</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> order_items
(
    id         order_item_id <span class="hljs-keyword">primary key</span> <span class="hljs-keyword">default</span> order_item_id_nextval(),
    order_id   order_id <span class="hljs-keyword">references</span> orders (id),
    product_id product_id <span class="hljs-keyword">references</span> products (id),
    <span class="hljs-comment">-- rest is the same</span>
);

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> reviews
(
    id          review_id <span class="hljs-keyword">primary key</span> <span class="hljs-keyword">default</span> review_id_nextval(),
    user_id     user_id <span class="hljs-keyword">references</span> users (id),
    product_id  product_id <span class="hljs-keyword">references</span> products (id),
    <span class="hljs-comment">-- rest is the same</span>
);
</code></pre>
<p>So, we define the <code>user_id</code>, <code>product_id</code>, <code>order_id</code>, <code>order_item_id</code>, and <code>review_id</code> types. Then, we use these types in the respective table definitions. We also refer to them using these same types for foreign keys.<br />So, if we rebuild the schema with these tables, we can go back to the query and try to figure out what happened.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">☹</div>
<div data-node-type="callout-text"><strong>ERROR: operator does not exist: order_id = product_id</strong></div>
</div>

<p>This is what happened! We are comparing the order ID with the product ID, and they don't compare as they are independent types. Okay, let's try to fix this:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">select</span> <span class="hljs-keyword">distinct</span> <span class="hljs-comment">/* ... */</span>
<span class="hljs-keyword">from</span> orders o
         <span class="hljs-keyword">join</span>
     users u <span class="hljs-keyword">on</span> o.user_id = u.id
         <span class="hljs-keyword">join</span>
     order_items oi <span class="hljs-keyword">on</span> o.id = oi.order_id
         <span class="hljs-keyword">join</span>
     <span class="hljs-comment">--- BELOW: changed oi.order_id to oi.product_id</span>
     products p <span class="hljs-keyword">on</span> oi.product_id = p.id 
         <span class="hljs-keyword">left join</span>
     reviews r <span class="hljs-keyword">on</span> p.id = r.product_id
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> o.id, p.product_name;
</code></pre>
<p>And now the data looks correct—we have five headphones as in the original data and just one laptop:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722625139385/d38a1338-6ebc-4ef5-9e1e-e520c7e639d5.png" alt class="image--center mx-auto" /></p>
<p>So, we just saved ourselves a lot of time trying to figure out what went wrong.</p>
<p>So, what does omni_id do? It creates a new integer type for each relation identity and prevents different types from being compared to each other. This concept is well-known in programming languages, called "<a target="_blank" href="https://doc.rust-lang.org/book/ch19-04-advanced-types.html">newtype</a>." That's exactly what we did here for Postgres—it performs just as well as normal integer keys and only requires PL/pgSQL to create the type.</p>
<p>This little trick shows how important (and useful) it is to enforce the correctness of data and queries at the model level.</p>
<hr />
<p>If you prefer a video format, check out the Loom below!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/5aef4295495b4577b436b359246febe4?sid=e126f061-049c-4fb3-a3d4-9777cf29693c">https://www.loom.com/share/5aef4295495b4577b436b359246febe4?sid=e126f061-049c-4fb3-a3d4-9777cf29693c</a></div>
]]></content:encoded></item></channel></rss>