<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: spkibe</title>
    <description>The latest articles on Forem by spkibe (@spkibe).</description>
    <link>https://forem.com/spkibe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F879788%2F039fe684-80c7-46d3-9eff-23c1e2152906.png</url>
      <title>Forem: spkibe</title>
      <link>https://forem.com/spkibe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/spkibe"/>
    <language>en</language>
    <item>
      <title>The Silent Bug That Exposed All Tenant Data in Databricks Unity Catalog</title>
      <dc:creator>spkibe</dc:creator>
      <pubDate>Mon, 11 May 2026 07:39:39 +0000</pubDate>
      <link>https://forem.com/spkibe/the-silent-bug-that-exposed-all-tenant-data-in-databricks-unity-catalog-4egj</link>
      <guid>https://forem.com/spkibe/the-silent-bug-that-exposed-all-tenant-data-in-databricks-unity-catalog-4egj</guid>
      <description>&lt;p&gt;We were building a multi-tenant data platform on Databricks. Multiple organisations sharing the same physical tables — each one should see only their own rows. Standard stuff.&lt;br&gt;
We implemented it using Unity Catalog's row-level security and column masking. The functions compiled. The filter showed as applied in &lt;em&gt;DESCRIBE EXTENDED&lt;/em&gt;. Every test from the admin account looked perfect.&lt;br&gt;
Then we logged in as a real tenant user.&lt;br&gt;
They could see every tenant's data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Row-Level Security and Column Masking Actually Do&lt;/strong&gt;&lt;br&gt;
Before getting to the bug, a quick primer on how Unity Catalog security works — because understanding the mechanism is what makes the bug obvious in hindsight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row-Level Security — Row Filters&lt;/strong&gt;&lt;br&gt;
A row filter is a SQL function you attach to a table. Unity Catalog calls it automatically on every query, passing the value of a specified column from each row. If the function returns TRUE, the row is shown. If it returns FALSE, the row is completely hidden — not counted, not visible, not even hinted at.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Attach a row filter to a table&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TENANT_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user never writes a WHERE clause for this. They cannot remove it. It fires invisibly on every query from every tool — SQL editor, notebook, BI connection, API call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Column-Level Masking — Column Masks&lt;/strong&gt;&lt;br&gt;
A column mask is a SQL function attached to a specific column. Instead of hiding rows, it transforms values at query time. The row is visible but sensitive fields are replaced, generalized, or redacted based on who is asking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Attach a column mask&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;FIRST_NAME&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;MASK&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same SELECT returns different values depending on the user's group membership:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzb8qp7f6hh80ns693av.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzb8qp7f6hh80ns693av.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One table. One query. Different results per role. Platform-enforced.&lt;/p&gt;

&lt;p&gt;Why This Matters&lt;br&gt;
The old approach — dynamic views, one per tenant per role — requires you to trust that every developer always queries the right view, that views stay in sync with schema changes, and that no one ever accidentally gets direct table access. Unity Catalog removes all of that trust dependency. Security lives at the storage engine layer, not the SQL layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;&lt;br&gt;
Here is the row filter function we wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt;
&lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_key&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt;
  &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;OR&lt;/span&gt;
  &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_group_mapping&lt;/span&gt; &lt;span class="n"&gt;tgm&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_key&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenant_key&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it carefully.&lt;br&gt;
The function parameter is named tenant_key.&lt;br&gt;
The mapping table column is also named tenant_key.&lt;br&gt;
In the WHERE clause:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AND CAST(tgm.tenant_key AS BIGINT) = tenant_key&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
SQL sees two references to tenant_key. It resolves both as the table column tgm.tenant_key. The function parameter is completely ignored.&lt;/p&gt;

&lt;p&gt;The comparison becomes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tgm.tenant_key = tgm.tenant_key&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It Was So Hard to Spot&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No error was thrown.
The function compiled without warnings. Unity Catalog reported it as valid SQL.&lt;/li&gt;
&lt;li&gt;DESCRIBE EXTENDED showed the filter was applied.
&lt;code&gt;Row Filter: my_catalog.governance.filter_by_tenant(TENANT_KEY)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything looked correct at the metadata level. The filter was attached. The problem was invisible in the schema description.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Admin tests passed.
Our initial testing was done from an admin account. The admin bypass (IS_ACCOUNT_GROUP_MEMBER('admin_group')) fires before the EXISTS check, so it returned TRUE for the correct reason. We never noticed the EXISTS was broken.&lt;/li&gt;
&lt;li&gt;The function fails open, not closed.
When Unity Catalog cannot properly evaluate a row filter, it fails open — showing rows rather than blocking them. This is the safer choice for uptime but the dangerous choice for security. A broken filter that silently shows everything is much harder to detect than a broken filter that throws an error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Diagnosis&lt;/strong&gt;&lt;br&gt;
The key test was running the filter function directly as the tenant user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Run as the tenant user, not the admin&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;can_see_tenant_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;can_see_tenant_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;can_see_tenant_3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_see_tenant_1 = true
can_see_tenant_2 = true
can_see_tenant_3 = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A user who should only see tenant 3 could see all three. The function was returning true everywhere regardless of tenant key. That confirmed the EXISTS logic was broken — and pointed directly to the parameter name collision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix — Rename the Parameter&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt;
&lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_tenant_key&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="c1"&gt;-- Null tenant keys are always hidden&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;p_tenant_key&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;

    &lt;span class="c1"&gt;-- Admin bypass&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;

    &lt;span class="c1"&gt;-- Tenant check — p_tenant_key is the parameter&lt;/span&gt;
    &lt;span class="c1"&gt;-- tgm.tenant_key is the table column&lt;/span&gt;
    &lt;span class="c1"&gt;-- SQL can now distinguish between them&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_group_mapping&lt;/span&gt; &lt;span class="n"&gt;tgm&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_key&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p_tenant_key&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;

    &lt;span class="c1"&gt;-- Explicit deny — everything else sees zero rows&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two changes:&lt;/p&gt;

&lt;p&gt;Parameter renamed from tenant_key to p_tenant_key — eliminates the name collision&lt;br&gt;
CASE structure with explicit ELSE FALSE — makes the deny-by-default behaviour visible and intentional&lt;/p&gt;

&lt;p&gt;After recreating the function and reapplying the row filter, the same test returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_see_tenant_1 = false
can_see_tenant_2 = false
can_see_tenant_3 = true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Drop and Reapply After Fixing&lt;/strong&gt;&lt;br&gt;
Updating the function is not enough on its own. You also need to drop and reapply the row filter so the table picks up the new function definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;
  &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TENANT_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Column Masking Side&lt;/strong&gt;&lt;br&gt;
For completeness — column masking uses the same pattern and has the same naming risk. Here is what a safe masking function looks like with the p_ prefix convention applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt;
&lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'full_access_group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;p_name&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;p_name&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'partial_access_group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'***'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'#### MASKED ####'&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply it inline at table creation to avoid broken dependencies later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;MEMBER_KEY&lt;/span&gt;   &lt;span class="nb"&gt;BIGINT&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TENANT_KEY&lt;/span&gt;   &lt;span class="nb"&gt;BIGINT&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FIRST_NAME&lt;/span&gt;   &lt;span class="n"&gt;STRING&lt;/span&gt;  &lt;span class="n"&gt;MASK&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LAST_NAME&lt;/span&gt;    &lt;span class="n"&gt;STRING&lt;/span&gt;  &lt;span class="n"&gt;MASK&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DATE_OF_BIRTH&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;   &lt;span class="n"&gt;MASK&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask_dob&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;DELTA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Row filter applied separately&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TENANT_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declaring masks inline means they survive DROP TABLE / CREATE TABLE cycles. The row filter does not — always reapply it after recreating a table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Rule&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Never name a row filter function parameter the same as a column in any table the function queries.&lt;/p&gt;

&lt;p&gt;Prefix all function parameters with p_. It is one character. It prevents this entire class of silent security failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filter_by_tenant(tenant_key BIGINT)   ← dangerous
filter_by_tenant(p_tenant_key BIGINT) ← safe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full Verification Checklist&lt;/strong&gt;&lt;br&gt;
Run these in order before trusting any row filter in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. Confirm groups are account-level (not workspace-level)&lt;/span&gt;
&lt;span class="c1"&gt;--    Run as the target user:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your_tenant_group'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Expected: true&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. Confirm filter function returns correct values per tenant&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Expected: false, false, true (for a tenant 3 user)&lt;/span&gt;

&lt;span class="c1"&gt;-- 3. Confirm filter is attached to the table&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;EXTENDED&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Look for: Row Filter: my_catalog.governance.filter_by_tenant(TENANT_KEY)&lt;/span&gt;

&lt;span class="c1"&gt;-- 4. Confirm mapping table has correct data&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_group_mapping&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 5. Confirm the EXISTS subquery works correctly&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;governance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_group_mapping&lt;/span&gt; &lt;span class="n"&gt;tgm&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IS_ACCOUNT_GROUP_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;tgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;exists_result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Expected: true (for tenant 3 user)&lt;/span&gt;

&lt;span class="c1"&gt;-- 6. Run query as target user and confirm only their rows appear&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;TENANT_KEY&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;TENANT_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Expected: only their tenant_key in results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Other Gotchas We Hit Along the Way&lt;/strong&gt;&lt;br&gt;
While we are here — these are the other issues that burned us during the same implementation:&lt;br&gt;
Workspace groups vs account groups. &lt;em&gt;IS_ACCOUNT_GROUP_MEMBER()&lt;/em&gt; only recognises account-level groups created in the Databricks Account Console, not workspace-level groups. A workspace group always returns false. This one caused hours of confusion.&lt;br&gt;
Cluster identity. Notebooks attached to a cluster run queries as the cluster owner's identity, not the logged-in user. &lt;em&gt;IS_ACCOUNT_GROUP_MEMBER() _checks the cluster owner's groups. Switch to a SQL Warehouse — it always evaluates per the logged-in user.&lt;br&gt;
Broken dependencies after catalog deletion. Column masks hold references to functions by their fully-qualified path. Delete the catalog containing a masking function without first dropping the masks, and every table with that mask becomes unqueryable with _UC_DEPENDENCY_DOES_NOT_EXIST&lt;/em&gt;. Always drop masks before dropping catalogs.&lt;br&gt;
Row filter lost after DROP TABLE. When you drop and recreate a table, inline column masks are preserved in the CREATE TABLE statement. Row filters are not. Always reapply &lt;em&gt;ALTER TABLE SET ROW FILTER&lt;/em&gt; after recreating any filtered table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;
Unity Catalog row-level security and column masking are genuinely powerful. One filter function and one masking function replace hundreds of views, a duplicate encrypted schema, and developer-discipline-as-security-policy.&lt;br&gt;
But the parameter name collision bug is subtle enough that it will catch you if you are not looking for it. The function looks right. It compiles cleanly. It attaches without errors. And it silently hands every user a complete view of every tenant's data.&lt;br&gt;
Prefix your parameters. Always.&lt;/p&gt;

</description>
      <category>databricks</category>
      <category>datamasking</category>
      <category>dataengineering</category>
      <category>unitycatalog</category>
    </item>
    <item>
      <title>Star Schema: The Data Warehouse Hack So Simple, Experts Hate It</title>
      <dc:creator>spkibe</dc:creator>
      <pubDate>Sun, 16 Mar 2025 20:45:58 +0000</pubDate>
      <link>https://forem.com/spkibe/star-schema-the-data-warehouse-hack-so-simple-experts-hate-it-4eef</link>
      <guid>https://forem.com/spkibe/star-schema-the-data-warehouse-hack-so-simple-experts-hate-it-4eef</guid>
      <description>&lt;p&gt;Let’s cut through the jargon: a star schema is the easiest, most badass way to build a data warehouse. Picture a fact table—say, sales—sitting in the center like a king, surrounded by dimension tables—products, time, customers—like loyal knights. That’s it. No convoluted hierarchies, no endless joins, just a radial, denormalized beauty built for speed and simplicity. Your notes call it out: “A dimensional model in a star configuration… de-normalized for better performance and easier understanding.” It’s the data equivalent of a cheat code.&lt;/p&gt;

&lt;p&gt;Why’s it genius? Because it’s optimized for OLAP queries—those big, juicy SELECT statements that slice and dice your data into insights. Fewer joins mean faster results. Hierarchies like “year → quarter → month” live right in the dimension table, so you’re not chasing parent tables across a database swamp. I’ve seen star schemas turn a 10-minute report into a 10-second one—try that with a normalized mess. Plus, it’s extensible: slap on a new dimension or tweak a hierarchy, and your old queries still work. It’s like Lego for data nerds.&lt;/p&gt;

&lt;p&gt;But here’s the controversial twist: some “experts” overcomplicate it because they can’t handle its simplicity. They’ll snowflake it up (more on that tomorrow) or drown it in metadata until it’s unrecognizable. I once worked with a team that spent weeks debating surrogate keys for a star schema that was already humming—meanwhile, the business was begging for actionable numbers. The truth? Star schemas shine when you keep them lean. Over-engineer it, and you’re just flexing for the wrong crowd.&lt;/p&gt;

&lt;p&gt;Sure, it’s not perfect. Denormalization means redundancy—updating a customer’s address might hit multiple rows, and that can sting. But in a data warehouse, where reads outnumber writes 100-to-1, who cares? It’s built for analysts, not accountants. So, next time someone smirks at your star schema’s “simplicity,” tell them: “It’s not basic—it’s brilliant.” Then watch their report lag while yours sings.&lt;/p&gt;

</description>
      <category>datawarehouse</category>
      <category>datamodelling</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>"Data Warehouses: The Silent Powerhouse Your Boss Doesn’t Understand"</title>
      <dc:creator>spkibe</dc:creator>
      <pubDate>Wed, 12 Mar 2025 08:12:10 +0000</pubDate>
      <link>https://forem.com/spkibe/data-warehouses-the-silent-powerhouse-your-boss-doesnt-understand-1b8a</link>
      <guid>https://forem.com/spkibe/data-warehouses-the-silent-powerhouse-your-boss-doesnt-understand-1b8a</guid>
      <description>&lt;p&gt;Picture this: mountains of transactional data piling up in your company’s systems—sales, clicks, shipments, complaints—all screaming for attention. But your fancy OLTP database is choking, optimized for quick inserts, not big-picture insights. Enter the &lt;strong&gt;data warehouse&lt;/strong&gt;: the relational (or multidimensional) beast built for &lt;em&gt;query and analysis&lt;/em&gt;, not petty transaction processing. It’s the unsung hero of business decisions, and most executives don’t even know it exists.&lt;/p&gt;

&lt;p&gt;Then What is a Data WareHouse?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s a subject-oriented, integrated, time-variant, and non-volatile collection of historical data to support management decision-making.” Translation? It’s a time machine for your business, hoarding years of data from every source imaginable—ERP, CRM, that sketchy Excel sheet your intern made—and turning it into something useful. Non-volatile means once it’s in, it stays in. Time-variant means it evolves with your business. Subject-oriented? It’s laser-focused on what matters: sales trends, customer behavior, profit margins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here’s the controversial bit: data warehouses are overkill for 90% of startups and small fries. You don’t need a Ferrari to drive to the corner store. I’ve seen companies sink millions into a warehouse only to query it once a quarter for a PowerPoint slide. Meanwhile, big players—like retail giants or banks—live or die by them, slicing and dicing historical data to predict the next big move.&lt;/p&gt;

&lt;p&gt;The real kicker? It separates analysis from transactions, so your operational systems don’t crash when the CEO demands a 5-year sales report. It’s not sexy, but it’s the backbone of every KPI dashboard you’ve ever bragged about. So next time someone asks, “Why do we even have this?”—tell them it’s the difference between guessing and knowing.&lt;/p&gt;

</description>
      <category>datawarehouse</category>
      <category>datamodelling</category>
      <category>dataengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>Uses of Snowflake Schema</title>
      <dc:creator>spkibe</dc:creator>
      <pubDate>Wed, 15 Jan 2025 09:09:37 +0000</pubDate>
      <link>https://forem.com/spkibe/uses-of-snowflake-schema-1e14</link>
      <guid>https://forem.com/spkibe/uses-of-snowflake-schema-1e14</guid>
      <description>&lt;p&gt;The snowflake schema is a type of database schema that organizes data into a centralized fact table surrounded by normalized dimensions. Unlike a star schema, where dimensions are typically denormalized into flat tables, the snowflake schema splits dimensions into related sub-dimensions, reducing data redundancy and improving storage efficiency.&lt;/p&gt;

&lt;p&gt;Dimensions with hierarchies can be decomposed into a snowflake structure when you want to avoid joins to big dimension tables when you are using an aggregate of the fact table. For example, if you have brand information that you want to separate out from a product dimension table, you can create a brand snowflake that consists of a single row for each brand and that contains significantly fewer rows than the product dimension table. The following figure shows a snowflake structure for the brand and product line elements and the brand_agg aggregate table.&lt;/p&gt;

&lt;p&gt;Snowflake schemas are especially useful in scenarios where certain attributes apply only to subsets of a dimension, leading to sparse data and inefficiencies in traditional denormalized structures.&lt;/p&gt;

&lt;p&gt;Below are three practical use cases where the snowflake schema is applied, along with clear data models to demonstrate how it works.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Use Case 1: Large Customer Dimension&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In businesses such as online marketing, there are two types of customers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anonymous Visitors:&lt;/strong&gt; Identified only by cookie data, with minimal attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registered Customers:&lt;/strong&gt; Have detailed information, including demographics, address, and payment history.&lt;/p&gt;

&lt;p&gt;Storing these two types of entities in a single table results in &lt;em&gt;inefficiencies&lt;/em&gt; as most attributes remain &lt;em&gt;null&lt;/em&gt; for anonymous visitors.&lt;/p&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;p&gt;Using a snowflake schema:&lt;/p&gt;

&lt;p&gt;The base Customer Dimension holds common attributes for both visitors and registered customers.&lt;/p&gt;

&lt;p&gt;Separate sub-dimensions store specific attributes for Visitors and Registered Customers.&lt;/p&gt;

&lt;p&gt;Data Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------+        +--------------------------+
|  Customer (Base)    |        | Customer Details (Snow)  |
+---------------------+        +--------------------------+
| Customer_ID (PK)    |--------| Customer_ID (FK)         |
| Customer_Type       |        | Demographics             |
| Last_Visit_Date     |        | Address                 |
| Signup_Date         |        | Payment_History         |
+---------------------+        +--------------------------+

       |
       |
       V
+---------------------+
| Visitors (Snow)     |
+---------------------+
| Visitor_ID (PK)     |
| Cookie_ID           |
| Visit_Frequency     |
| Browsing_History    |
+---------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Use Case 2: Financial Product Dimension&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In financial services, different product types (e.g., loans and insurance) have distinct attributes. Attempting to store all attributes in one dimension results in sparse data, as many attributes will not apply to all products.&lt;/p&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;p&gt;Using a snowflake schema:&lt;/p&gt;

&lt;p&gt;The base Product Dimension contains attributes common to all products.&lt;/p&gt;

&lt;p&gt;Separate sub-dimensions store specialized attributes for different product types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+        +------------------------------+
| Product (Base)       |        | Product Details (Snowflake) |
+----------------------+        +------------------------------+
| Product_ID (PK)      |--------| Product_ID (FK)             |
| Product_Type         |        | Specialized_Attribute_1     |
| Core_Attribute       |        | Specialized_Attribute_2     |
+----------------------+        +------------------------------+

       |
       |
       V
+----------------------+
| Loan Products (Snow) |
+----------------------+
| Loan_ID (PK)         |
| Interest_Rate        |
| Loan_Term            |
| Collateral_Type      |
+----------------------+

       |
       |
       V
+-----------------------+
| Insurance Products    |
+-----------------------+
| Insurance_ID (PK)     |
| Coverage_Type         |
| Premium_Amount        |
| Policy_Duration       |
+-----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Use Case 3: Multi-Enterprise Calendar Dimension&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In international businesses, calendars vary by country. For example:&lt;/p&gt;

&lt;p&gt;The US might have specific fiscal quarters and national holidays.&lt;/p&gt;

&lt;p&gt;The UK might have unique bank holidays.&lt;/p&gt;

&lt;p&gt;India might have a calendar with festival-specific dates.&lt;/p&gt;

&lt;p&gt;Storing all attributes in one table leads to complexity and inefficiency.&lt;/p&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;p&gt;Using a snowflake schema:&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;base Calendar Dimension&lt;/strong&gt; contains attributes common to all countries.&lt;/p&gt;

&lt;p&gt;Separate sub-dimensions store country-specific calendar attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+        +----------------------------+
| Calendar (Base)      |        | US Calendar (Snowflake)   |
+----------------------+        +----------------------------+
| Calendar_ID (PK)     |--------| Calendar_ID (FK)          |
| Date                |         | National_Holiday          |
| Week_Number          |        | US_Fiscal_Quarter         |
| Fiscal_Year          |        | US_Specific_Attribute     |
+----------------------+        +----------------------------+

       |
       |
       V
+----------------------------+
| UK Calendar (Snowflake)    |
+----------------------------+
| Calendar_ID (FK)           |
| National_Holiday           |
| Bank_Holiday               |
| UK_Fiscal_Quarter          |
+----------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The snowflake schema is an efficient and organized approach to handling complex dimensions with sparse data. By breaking down dimensions into smaller, logical sub-dimensions, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces storage requirements.&lt;/li&gt;
&lt;li&gt;Improves query performance for specific attribute groups.&lt;/li&gt;
&lt;li&gt;Enhances clarity in schema design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples above highlight how snowflake schemas can be applied to real-world scenarios, such as customer data, financial products, and multi-country calendars, ensuring data is both accessible and efficiently structured.&lt;/p&gt;

</description>
      <category>datamodelling</category>
      <category>datawarehouse</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
    <item>
      <title>Web Scraping - Buy rent kenya website</title>
      <dc:creator>spkibe</dc:creator>
      <pubDate>Mon, 26 Feb 2024 02:35:42 +0000</pubDate>
      <link>https://forem.com/spkibe/web-scraping-buy-rent-kenya-website-42bh</link>
      <guid>https://forem.com/spkibe/web-scraping-buy-rent-kenya-website-42bh</guid>
      <description>&lt;p&gt;Web Scraping is getting data from websites that is contained in it's html tags of the website.&lt;br&gt;
Here is the link to the website we'll be scraping (&lt;a href="https://www.buyrentkenya.com/"&gt;https://www.buyrentkenya.com/&lt;/a&gt;) This websites list the the projects and house available for selling or renting in kenya.&lt;br&gt;
I utilized BeautifulSoup, Python library to scrape this site. The first thing to do is to install the necessary libraries. I used other tools like requests and pandas.&lt;br&gt;
To easily easily install these python libraries it's wise to create a python environment that will contain our required libraries.&lt;br&gt;
utilizing the Virtualenv tool, we set up the environment, firstly install the tool:&lt;br&gt;
&lt;code&gt;pip install virtualenv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To use venv in your project, in your terminal, create a new project folder, cd to the project folder in your terminal, and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir web_scraping #creating a new folder
cd web_scraping 
python -m venv venv # creating and environment named venv 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to activate the environment use&lt;br&gt;
&lt;code&gt;source venv/bin/activate&lt;/code&gt; - for Linux and Mac users&lt;br&gt;
&lt;code&gt;Scripts\activate&lt;/code&gt; - for Windows users.&lt;/p&gt;

&lt;p&gt;After activating the environment you need to install the required libraries:&lt;br&gt;
Below is a snippet of how I did the above processes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwocednik7dl9665pxm2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwocednik7dl9665pxm2m.png" alt="Image description" width="726" height="207"&gt;&lt;/a&gt;&lt;br&gt;
Now the environment is ready, we are set to begin our web Scraping process.&lt;/p&gt;

&lt;p&gt;Getting Started:&lt;br&gt;
Before we dive into the code, let's understand the goal. We want to collect data on houses for rent, including details such as title, location, number of bedrooms and bathrooms, description, and price. We'll be scraping data from multiple pages to create a comprehensive dataset.&lt;/p&gt;

&lt;p&gt;Create a python script, mine I named buy_rent_kenya.py.&lt;br&gt;
The code is well-structured and efficient, following these main steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a GET request to the initial URL.&lt;/li&gt;
&lt;li&gt;Use BeautifulSoup to parse the HTML content.&lt;/li&gt;
&lt;li&gt;Extract information from each listing on the page.&lt;/li&gt;
&lt;li&gt;Iterate through multiple pages, repeating the process.&lt;/li&gt;
&lt;li&gt;Store the collected data in a Pandas DataFrame.&lt;/li&gt;
&lt;li&gt;Save the DataFrame to a CSV file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;import the necessary libraries for our task as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pandas as pd
from bs4 import BeautifulSoup
import requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next thing is to get your browser agent, just search "&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;what is my browser agent&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On your browser and you'll definitely get it.&lt;br&gt;
or get it from this link &lt;a href="https://www.whatismybrowser.com/detect/what-is-my-user-agent/"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;browser agent&lt;/strong&gt; in web scraping is crucial for mimicking different browsers, avoiding detection by websites, and ensuring compatibility. It helps prevent being flagged as a scraper, allows access to content tailored for specific browsers, and enhances overall scraping efficiency.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url = "https://www.buyrentkenya.com/houses-for-rent"
agent = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines set the target URL and the user agent, which simulates a web browser. It helps in avoiding any potential blocking or restrictions imposed by the website.&lt;br&gt;
Note: Replace the agent with yours, obtained from above search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Set headers for the HTTP request
HEADERS = ({'User-Agent':agent,'Accept-Language':'en-US, en;q=0.5'})
# Send a GET request to the URL
response = requests.get(url,headers=HEADERS)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, headers are defined for the HTTP request, and a GET request is made to the specified URL using the requests library&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Parse the HTML content with BeautifulSoup
soup = BeautifulSoup(response.content,'html.parser')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML content of the page is parsed using BeautifulSoup, making it easier to navigate and extract information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize lists to store data
titles = []
locations = []
no_of_bathrooms = []
no_of_bedrooms = []
descriptions = []
prices = []
links= []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty lists are initialized to store the extracted data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Find all listing cards on the page
houses = soup.find_all("div",class_="listing-card")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line locates all HTML elements with the class "listing-card," which corresponds to individual housing listings on the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Extract information from each listing
for house in houses:
    # Extract title
    title = house.find("span",class_="relative top-[2px] hidden md:inline").text.strip()
    # Extract location
    location = house.find("p",class_="ml-1 truncate text-sm font-normal capitalize text-grey-650").text.strip()
    # Extract number of bedrooms and bathrooms
    no_of_bedroom = house.find_all("span",class_="whitespace-nowrap font-normal")[0].text.strip()
    no_of_bathroom = house.find_all("span",class_="whitespace-nowrap font-normal")[1].text.strip()
    # Extract description
    description = house.find("a",class_="block truncate text-grey-500 no-underline").text.strip()
    # Extract price
    price = house.find("p",class_="text-xl font-bold leading-7 text-grey-900").text.strip()
    # Extract link
    link = house.find("a",class_="text-black no-underline").get("href")

    # Append extracted data to respective lists
    titles.append(title)
    locations.append(location)
    no_of_bathrooms.append(no_of_bathroom)
    no_of_bedrooms.append(no_of_bedroom)
    descriptions.append(description)
    prices.append(price)
    links.append(link)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this loop, data such as title, location, number of bedrooms and bathrooms, description, price, and link are extracted from each listing and appended to their respective lists.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The title is found within a span tag with the class "relative top-[2px] hidden md:inline". &lt;/li&gt;
&lt;li&gt;Both the number of bedrooms and bathrooms are within span tags with the same class "whitespace-nowrap font-normal". Thus we need to utilize the BeautifulSoup find_all()which returns them as a list, thus we use indexing to return each differently. The index [0] corresponds to bedrooms, and [1] corresponds to bathrooms.&lt;/li&gt;
&lt;li&gt;The description is found within an a tag with the class "block truncate text-grey-500 no-underline".&lt;/li&gt;
&lt;li&gt;The price is located within a p tag with the class "text-xl font-bold leading-7 text-grey-900".&lt;/li&gt;
&lt;li&gt;The link is obtained from an a tag with the class "text-black no-underline" using the get("href") method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note:  Make sure to inspect the HTML structure of the website you are scraping to adapt these identifiers accordingly. If the website structure changes, you may need to update these selectors accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Display the number houses extracted from the first page about the first page
print(f"The First Page No Of Titles is {len(titles)}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prints out the number of titles on the first page.&lt;/p&gt;

&lt;p&gt;The website has a pagination after the first page, which changes dynamic on the url, incrementing its number in the format below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;url = f"https://www.buyrentkenya.com/houses-for-rent?page={page}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Thus a code to extract more information for the pagenated urls is like as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Iterate through multiple pages
for page in range(2,56):
    url = f"https://www.buyrentkenya.com/houses-for-rent?page={page}"
    # Make a GET request for each page
    response = requests.get(url,headers=HEADERS)
    print(url)
    houses = soup.find_all("div",class_="listing-card")
    for house in houses:
        # Repeat the process of extracting data from each listing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines iterate through multiple pages, updating the URL for each page, making a GET request, and extracting data from each listing on the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Display the total number of titles scraped
print(f"The  Total no of Titles we have scraped is {len(titles)}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prints out the total number of titles scraped from all pages.&lt;/p&gt;

&lt;p&gt;The Last part is to save our extracted data into a csv file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Organize data into a DataFrame
data = {
    "Titles": titles,
    "Locations": locations,
    "No Of Bathrooms": no_of_bathrooms,
    "No Of Bedrooms": no_of_bedrooms,
    "Prices": prices,
    "Description": descriptions
}
df = pd.DataFrame(data)
print(df.shape)
#print(df.head(10))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extracted data is organized into a Pandas DataFrame for better structure and analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Save DataFrame to a CSV file
df.to_csv("buy_rent_kenya.csv",index=False)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the DataFrame is saved as a CSV file named "buy_rent_kenya.csv". The index=False parameter ensures that the DataFrame index is not included in the CSV file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Web scraping is a powerful tool for extracting valuable information from websites. This Python script provides a glimpse into the process of scraping rental property listings from Buy Rent Kenya. Keep in mind that web scraping should be done responsibly and in compliance with the terms of service of the website being scraped.&lt;/p&gt;

&lt;p&gt;Feel free to explore, modify, and adapt the code for your specific needs. Happy coding and may your data exploration endeavors be fruitful.&lt;br&gt;
Here is the link to the full code on github&lt;/p&gt;

&lt;p&gt;Be in the look out for our next article automating the Scraping process above Using Airflow.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introduction to Data Structures and Algorithms.</title>
      <dc:creator>spkibe</dc:creator>
      <pubDate>Mon, 20 Jun 2022 19:05:25 +0000</pubDate>
      <link>https://forem.com/spkibe/introduction-to-data-structures-and-algorithms-4l6h</link>
      <guid>https://forem.com/spkibe/introduction-to-data-structures-and-algorithms-4l6h</guid>
      <description>&lt;p&gt;A carpenter has several tools, yet each one has a specific purpose in completing a task. Similar to a programmer, depending on the task at hand, programmers will require the appropriate tool to handle a certain challenge.&lt;br&gt;
Data structures are programmers' tools, and each one serves a specific purpose. Many companies uses data structures challenges in their interviews to see if a programmer is a strong problem solver.&lt;br&gt;
Data structures are classified into two major sections:&lt;br&gt;
    1. Linear Data structures → stores data in a sequential manner.&lt;br&gt;
    2. Non-linear Data Structure → stores data in a  non- sequential manner.&lt;/p&gt;

&lt;p&gt;Types of Linear data structures:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1) Arrays → stores values in arranged continuous memory. Elements stored in the arrays are determined by the programming language.
2) Stack →  It stores its elements according to the LIFO (last in, first out) principle, which means that the last element added is the first one withdrawn.
3) Queues → It stores its elements using the FIFO (first in, first out) principle, which means that the first element inserted will be the first one withdrawn
4) LinkedList → It organizes its data as a connected network of nodes, with each element containing the address of the next node.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Types of  Non-Linear data structures:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1) Graphs → it’s made up of nodes or vertices and edges. Edges connects two nodes.
2) Trees → stores data in a hierarchical manner which is tree-like structures arranged in multiple levels. It has the root node(top most part) which is the central node. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Algorithms&lt;br&gt;
Is a set of instructions or procedure for solving a specific problem, you can think of it as a recipe to solve a problem or as a blueprint for solving a problem to make it easier to grasp.&lt;br&gt;
Types of Algorithms:&lt;br&gt;
    1) Sort algorithms&lt;br&gt;
    2) Search algorithms&lt;br&gt;
    3) Hashing&lt;/p&gt;

&lt;p&gt;Each written algorithm uses some memory to complete. This is where algorithm complexity comes into play; it calculates the amount of time and space required to execute an algorithm.&lt;br&gt;
Space complexity → The overall amount of space taken up by the algorithm in relation to the input size.&lt;br&gt;
Time complexity → is the amount of time algorithms takes to run. It is mostly expressed using the big O notation(asymptotic notation to represent time complexity). For example,a problem of size n:&lt;br&gt;
    1. O(1) is a constant-time function&lt;br&gt;
    2. O(n) is a linear-time function&lt;br&gt;
    3. O(n^2) is a quadratic-time function&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
