<?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>DEV Community: obed marabu</title>
    <description>The latest articles on DEV Community by obed marabu (@obed_marabu_1228278258151).</description>
    <link>https://dev.to/obed_marabu_1228278258151</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3952234%2Fd3eeea7e-5b26-4117-998b-e8f47e693168.png</url>
      <title>DEV Community: obed marabu</title>
      <link>https://dev.to/obed_marabu_1228278258151</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/obed_marabu_1228278258151"/>
    <language>en</language>
    <item>
      <title>Mastering SQL: Window Functions, Joins, CTEs and Subqueries</title>
      <dc:creator>obed marabu</dc:creator>
      <pubDate>Mon, 19 Oct 2026 18:11:59 +0000</pubDate>
      <link>https://dev.to/obed_marabu_1228278258151/mastering-sql-window-functions-joins-ctes-and-subqueries-5f3f</link>
      <guid>https://dev.to/obed_marabu_1228278258151/mastering-sql-window-functions-joins-ctes-and-subqueries-5f3f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Basic SELECT, WHERE and GROUP BY statements answer simple questions: how many visits happened, or how much money a department earned. Real hospital analytics asks harder ones. Which doctor’s most expensive visit was it? How long was it between a patient’s first and second visit? Which diagnoses appeared one week and vanished the next? Which doctors earn more per visit than the hospital average?&lt;br&gt;
Four families of SQL tools answer these questions: window functions, joins, Common Table Expressions (CTEs) and subqueries. This article explains each one, shows why it exists, and demonstrates it on our hospital database with a query, an explanation of what the query does, and the expected result.&lt;br&gt;
All results below were worked out by hand from the data in the creation script, so you can run each query and check it against the output shown.&lt;/p&gt;
&lt;h3&gt;
  
  
  SQL Window Functions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;What is a window function?&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
A window function performs a calculation across a set of rows that are related to the current row, without collapsing those rows into one.&lt;br&gt;
This lets you compare a row with its group, rank rows, look at the previous or next row, and compute running totals, all in a single query. Window functions are used for ranking, top-N per group, period-over-period comparison, running totals, moving averages and percentile bucketing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;The anatomy of a window: OVER, PARTITION BY, ORDER BY, and the frame&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;                     
           &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;              
           &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
                    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&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;running_revenue_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;&lt;strong&gt;OVER()&lt;/strong&gt;&lt;/em&gt; is what turns an ordinary function into a window function. It defines the window, the set of rows the function can see. An empty &lt;em&gt;&lt;strong&gt;OVER ()&lt;/strong&gt;&lt;/em&gt; means the window is the whole result set.&lt;br&gt;
PARTITION BY splits the rows into independent groups, like GROUP BY does, but without merging the rows. The function restarts for every partition. For example, &lt;code&gt;PARTITION BY doctor_id&lt;/code&gt; means each doctor is calculated separately.&lt;br&gt;
&lt;code&gt;ORDER BY&lt;/code&gt; inside &lt;code&gt;OVER&lt;/code&gt; sorts the rows within each partition. It is essential for ranking functions and for &lt;em&gt;&lt;strong&gt;LAG/LEAD&lt;/strong&gt;&lt;/em&gt;, and for aggregates it turns the calculation into a running one. It is separate from the &lt;em&gt;&lt;strong&gt;ORDER BY&lt;/strong&gt;&lt;/em&gt; at the end of the query, which only sorts the final output.&lt;br&gt;
The window frame narrows the window further, relative to the current row. It is written as:&lt;br&gt;
&lt;code&gt;ROWS BETWEEN &amp;lt;start&amp;gt; AND &amp;lt;end&amp;gt;&lt;/code&gt;&lt;br&gt;
where the boundaries can be &lt;em&gt;&lt;strong&gt;UNBOUNDED PRECEDING, n PRECEDING, CURRENT ROW, n FOLLOWING or UNBOUNDED FOLLOWING.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Ranking functions&lt;/strong&gt;&lt;/em&gt;
&lt;strong&gt;ROW_NUMBER()&lt;/strong&gt;:assigns a unique sequential number (1, 2, 3 …) to each row in a partition, following the &lt;em&gt;&lt;strong&gt;ORDER BY&lt;/strong&gt;&lt;/em&gt;. Ties still get different numbers.
Why it is used: top-N per group, removing duplicates, and pagination.
Scenario: finance wants each doctor’s single most expensive visit.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
                              &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&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;rn&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The inner query numbers each doctor’s visits from the highest bill down. Because window functions cannot be used in &lt;strong&gt;&lt;em&gt;WHERE&lt;/em&gt;&lt;/strong&gt;, the outer query keeps only &lt;em&gt;&lt;strong&gt;rn = 1&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;RANK() and DENSE_RANK()&lt;/em&gt;&lt;/strong&gt;
What they do: both give tied rows the same rank. They differ in what happens next:
• &lt;strong&gt;RANK()&lt;/strong&gt; skips numbers after a tie (1, 2, 2, 4).
• &lt;strong&gt;DENSE_RANK()&lt;/strong&gt; does not skip (1, 2, 2, 3).
Why they are used: leaderboards where ties are real, for example ranking admissions by length of stay.
Scenario: rank admitted patients by length of stay and compare all three numbering functions side by side.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;length_of_stay_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;length_of_stay_days&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&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;row_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;length_of_stay_days&lt;/span&gt; &lt;span class="k"&gt;DESC&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;rnk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;length_of_stay_days&lt;/span&gt; &lt;span class="k"&gt;DESC&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;dense_rnk&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;length_of_stay_days&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Only the ten visits with a hospital stay are kept. Several have the same number of days, so the three functions behave differently.&lt;/p&gt;

&lt;p&gt;5.&lt;strong&gt;_ NTILE(n)_&lt;/strong&gt;&lt;br&gt;
What it does: splits the ordered rows into n roughly equal buckets and returns the bucket number. When rows do not divide evenly, the earlier buckets get one extra row.&lt;br&gt;
Why it is used: quartiles, deciles and tiering customers or patients by spend.&lt;br&gt;
Scenario: find the top quartile of bills, the highest-spending 25% of visits.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;NTILE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&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;quartile&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;quartile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30 visits divided by 4 is 7 remainder 2, so buckets 1 and 2 hold 8 rows each and buckets 3 and 4 hold 7 each. Bucket 1 therefore contains the 8 largest bills.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Value (navigation) functions&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;LAG() and LEAD()
What they do: &lt;strong&gt;&lt;em&gt;LAG&lt;/em&gt;&lt;/strong&gt;(col, n, default) reads the value from n rows before the current row within the partition; LEAD(col, n, default) reads n rows after. With no offset, n is 1. If no such row exists, the result is &lt;strong&gt;NULL&lt;/strong&gt; (or the default you supply).
Why they are used: comparing a row with its neighbour without a self join: change since the last period, time between events, readmission checks.
Scenario: for patients who came back to the hospital, show the previous bill, the next visit date, and the number of days since the last visit.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&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;previous_bill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&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;next_visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&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;days_since_last&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="k"&gt;IN&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each patient is its own partition, ordered by date. A patient’s first visit has no previous row, so previous_bill and days_since_last are &lt;strong&gt;NULL&lt;/strong&gt;; their last visit has no next row, so next_visit_date is &lt;strong&gt;NULL&lt;/strong&gt;. In PostgreSQL, subtracting two dates returns the number of days.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;FIRST_VALUE() and LAST_VALUE()&lt;/em&gt;&lt;/strong&gt;
What they do: return the value of an expression from the first or last row of the window frame.
Why they are used: compare every row to the beginning or end of a sequence, for example to a doctor’s first bill of the period.
The classic trap: because the default frame stops at the current row, &lt;strong&gt;LAST_VALUE&lt;/strong&gt; with only an &lt;strong&gt;ORDER BY&lt;/strong&gt; simply returns the current row’s own value. You must widen the frame to &lt;strong&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.&lt;/strong&gt;
Scenario: for Dr. Peter Otieno (103), show each visit’s bill alongside his first and last bill of the period.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;FIRST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_bill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;LAST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_bill_wrong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;LAST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;
                                         &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;
                                         &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
                                                  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&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;last_bill_correct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;103&lt;/span&gt;
&lt;span class="k"&gt;WINDOW&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The named window &lt;em&gt;w&lt;/em&gt; (defined with the WINDOW clause) is reused to avoid repetition. &lt;em&gt;last_bill_wrong&lt;/em&gt; uses the default frame; &lt;em&gt;last_bill_correct&lt;/em&gt; uses the full partition.&lt;br&gt;
A related function, &lt;strong&gt;&lt;em&gt;NTH_VALUE(col, n)&lt;/em&gt;&lt;/strong&gt;, returns the n-th row of the frame and has the same frame caveat.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Aggregate window functions: SUM, AVG, COUNT, MIN, MAX&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The familiar aggregates become window functions when followed by &lt;strong&gt;&lt;em&gt;OVER.&lt;/em&gt;&lt;/strong&gt; They calculate across the window but keep every row. Without ORDER BY they see the whole partition; with &lt;strong&gt;&lt;em&gt;ORDER BY&lt;/em&gt;&lt;/strong&gt; they become running calculations.&lt;br&gt;
Per-partition aggregates (no ORDER BY)&lt;br&gt;
Scenario: for Dr. Faith Chebet’s visits, show each bill next to her visit count, total, average, smallest and largest bill, plus each visit’s share of her total.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&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;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&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;doctor_visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&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;doctor_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&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;doctor_avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&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;doctor_min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&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;doctor_max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
             &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&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;pct_of_doctor_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The five aggregates all see the same six rows (doctor 104’s visits), so every row repeats the same count, total, average, minimum and maximum. The percentage column shows why this is powerful: a row-level value divided by a group-level value, with no join or subquery.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;SQL Joins&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why joins exist&lt;/strong&gt;
Our database is normalised: a visit stores only doctor_id, not the doctor’s name. To produce a readable report you must combine tables, and a join does that by matching rows on a related column, normally a foreign key to a primary key (visits.doctor_id = doctors.doctor_id).
Join
Returns
Typical question&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;INNER JOIN&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Only rows that match in both tables&lt;br&gt;
Visits with their doctor and department&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;LEFT JOIN&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All rows from the left table, plus matches from the right (NULL if none)&lt;br&gt;
Every doctor, even those with no emergency visits&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;RIGHT JOIN&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
All rows from the right table, plus matches from the left&lt;br&gt;
The mirror image of LEFT JOIN&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;FULL OUTER JOIN&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All rows from both tables, matched where possible&lt;br&gt;
What is in one data set but not the other&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;CROSS JOIN&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Every combination of rows (Cartesian product)&lt;br&gt;
Build a complete grid of doctors × admission types&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;SELF JOIN&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A table joined to itself (any join type)&lt;br&gt;
Compare rows of the same table, such as repeat visits&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;INNER JOIN&lt;/em&gt;&lt;/strong&gt;
What it does: keeps only rows that have a match on both sides. Rows without a match are dropped silently.
Why it is used: it is the default way to attach descriptive information to a fact table when every row is expected to have a match.
Scenario: hospital management wants visits and revenue by department and doctor.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;revenue_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;   &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&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;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;revenue_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each of the 30 visits is matched to exactly one doctor and one department (both are NOT NULL foreign keys), then grouped. INNER is optional; plain JOIN means the same thing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;LEFT JOIN (LEFT OUTER JOIN)&lt;/em&gt;&lt;/strong&gt;
What it does: returns every row from the left table. Where the right table has no match, its columns are NULL.
Why it is used: reports that must list all members of a group, including those with zero activity, and anti-joins that find rows with no match.
Scenario 1: list every doctor with the number of emergency visits, including doctors with none.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&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;emergency_visits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
       &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Emergency'&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_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 emergency filter sits in the ON clause, not in WHERE. This matters: if you put WHERE v.admission_type = 'Emergency', doctors without emergencies would be removed and the query would behave like an inner join. We count v.visit_id rather than * because &lt;strong&gt;COUNT(*)&lt;/strong&gt; would count the &lt;strong&gt;NULL&lt;/strong&gt; placeholder row as 1.&lt;/p&gt;

&lt;p&gt;Scenario 2 (anti-join): which patients have never been admitted as inpatients?&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
       &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Inpatient'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patients with no inpatient visit get a NULL visit_id from the left join, and the WHERE keeps exactly those. Nine patients (3, 4, 10, 14, 15, 16, 22, 23, 24) have inpatient visits, so 16 patients are returned: Amina Otieno, John Kariuki, Esther Achieng, Mohamed Ali, Lucy Wambui, Samuel Kiptoo, Mary Wairumu, Brian Omondi, Catherine Musyoka, Daniel Maina, Isaac Kamau, Jane Muthoni, Kevin Barasa, Lilian Adhiambo, Moses Cheruiyot and Josephine Wekesa.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;RIGHT JOIN (RIGHT OUTER JOIN)&lt;/em&gt;&lt;/strong&gt;
What it does: the mirror of &lt;strong&gt;LEFT JOIN&lt;/strong&gt;. All rows from the right table are kept.
Why it is used: rarely, because any right join can be rewritten as a left join by swapping the table order, and most people find left joins easier to read. It is useful when you are adding a join to an existing query and the table you want to preserve is already on the right.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&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;emergency_visits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;RIGHT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
       &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Emergency'&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here doctors is on the right, so all four doctors are preserved. The result is identical to Scenario 1 above. Compare the two queries to see that &lt;strong&gt;&lt;em&gt;A LEFT JOIN B and B RIGHT JOIN A&lt;/em&gt;&lt;/strong&gt; are the same thing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;FULL OUTER JOIN&lt;/em&gt;&lt;/strong&gt;
What it does: keeps all rows from both tables. Matching rows are combined; unmatched rows from either side appear with &lt;strong&gt;NULLs&lt;/strong&gt; for the other side.
Why it is used: comparing or reconciling two sets, such as this week versus last week, or system A versus system B.
Scenario: which diagnoses were seen in only one of the two weeks? Week 1 is 1–7 June and week 2 is 8–15 June.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;week1_diagnosis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;w2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;week2_diagnosis&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-07'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;w1&lt;/span&gt;
&lt;span class="k"&gt;FULL&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-08'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-15'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;w2&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&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;OR&lt;/span&gt; &lt;span class="n"&gt;w2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each side is a de-duplicated list of diagnosis IDs for one week. The full join lines them up; the WHERE keeps only the rows where one side is missing. Week 1 has 11 diagnoses, week 2 has 15, and 8 appear in both.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;CROSS JOIN&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What it does: pairs every row of the first table with every row of the second. With 4 doctors and 3 admission types you get 12 rows. There is no ON clause.&lt;br&gt;
Why it is used: generating complete grids (all combinations, including those with no data), calendars and scenario tables. It is dangerous on large tables, since 1,000 × 1,000 rows is a million.&lt;br&gt;
Scenario: a complete doctor × admission-type matrix, with zeros where a combination never occurred.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&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;visits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Outpatient'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Inpatient'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Emergency'&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
       &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admission_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cross join builds all 12 combinations, then the left join counts the real visits for each. Without the cross join, combinations with zero visits would simply be missing from the report.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;SELF JOIN&lt;/em&gt;&lt;/strong&gt;
What it does: joins a table to itself, using two aliases so the database treats them as two separate copies. It is not a different keyword, just any join where both sides are the same table.
Why it is used: to compare rows within one table: employees and their managers, pairs of records with the same attribute, or an event and its previous event.
Scenario 1: find repeat visits by the same patient and the number of days between them.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_visit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;return_visit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;days_between&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&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;v1&lt;/strong&gt; is the earlier visit and v2 the later one. The condition &lt;strong&gt;v2&lt;/strong&gt;.&lt;em&gt;&lt;strong&gt;visit_date &amp;gt; v1.visit_date&lt;/strong&gt;&lt;/em&gt; stops a visit being paired with itself and prevents each pair appearing twice.&lt;br&gt;
This gives the same facts as the LAG example in Section 3.4. Both approaches work; the window function is shorter and usually faster, while the self join is more flexible when the matching rule is complicated.&lt;br&gt;
Scenario 2: pairs of patients who come from the same county.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;patient_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;patient_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two pairs: Esther Achieng and Brian Omondi (Kisumu) and Amina Otieno and Isaac Kamau (Nairobi). The condition a.patient_id &amp;lt; b.patient_id removes mirror-image duplicates (A with B and B with A). This is also how you would query a hierarchy such as staff and their managers in a single table, which we revisit with recursion in the CTE section.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;Choosing the right join&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Need only complete matches, and every row should match? INNER JOIN.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Need every row of a main list, with optional details? LEFT JOIN (and WHERE right.key IS NULL to find the missing ones).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Need to compare two lists and see what is unique to each? FULL OUTER JOIN.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Need all combinations, or a skeleton to fill with counts? CROSS JOIN.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Need to relate rows of a table to other rows of the same table? SELF JOIN.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Think RIGHT JOIN? Swap the tables and use LEFT JOIN.&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Common mistakes&lt;/em&gt;&lt;/strong&gt;: filtering the right table in WHERE after a left join (turns it into an inner join), forgetting the join condition (accidental cross join), and joining on a column that is not unique (row multiplication, which inflates sums). Always check that totals still match after adding a join.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Common Table Expressions (CTEs)&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;_ What is a CTE?_&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A Common Table Expression is a named, temporary result set defined with the WITH keyword at the start of a statement. You can then use that name in the main query as if it were a table. It exists only for the duration of that one statement; nothing is stored in the database.&lt;br&gt;
CTEs are used to break a complex query into readable steps, to reuse a result several times in one query, to filter on window-function results, and to walk hierarchies with recursion. In PostgreSQL 12 and later, a non-recursive CTE that is referenced once is normally inlined by the planner (like a subquery), and you can force the behaviour with_ AS MATERIALIZED or AS NOT MATERIALIZED._&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;Basic CTE&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;Scenario: produce a doctor performance summary. First calculate the numbers per doctor, then attach names and specialties.&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;WITH&lt;/span&gt; &lt;span class="n"&gt;doctor_summary&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;revenue_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;avg_bill_kes&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&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;doctor_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_specialty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;revenue_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctor_summary&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;revenue_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;CTE&lt;/strong&gt; doctor_summary reduces 30 visit rows to 4 doctor rows. The main query then joins those 4 rows to doctors. Aggregating first and joining second is cleaner and often faster than joining first.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;Multiple CTEs&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;You can define several &lt;strong&gt;CTEs&lt;/strong&gt; in one &lt;strong&gt;WITH&lt;/strong&gt;, separated by commas. Later CTEs can refer to earlier ones, so a query reads as a pipeline of steps.&lt;br&gt;
Scenario: which doctors charge more per visit than the hospital average, and by how much?&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;WITH&lt;/span&gt; &lt;span class="n"&gt;doctor_stats&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;avg_bill&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&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;doctor_id&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;hospital_avg&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;avg_bill&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill&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;doctor_avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill&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;hospital_avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill&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;above_average_by&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctor_stats&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;hospital_avg&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_bill&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;above_average_by&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step one finds each doctor’s average bill; step two finds the single hospital-wide average (KES 16,266.67). Because hospital_avg has exactly one row, a CROSS JOIN safely attaches it to every doctor. The &lt;strong&gt;WHERE&lt;/strong&gt; then keeps doctors above the average.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;CTEs with joins and aggregations&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;Scenario: the hospital board wants revenue by diagnosis category. The &lt;strong&gt;CTE&lt;/strong&gt; joins the fact table to its dimension first; the final query aggregates.&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;WITH&lt;/span&gt; &lt;span class="n"&gt;visit_detail&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_category&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;diagnosis&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;diagnosis_category&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;revenue_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visit_detail&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;diagnosis_category&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;revenue_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;em&gt;Recursive CTEs&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;A &lt;strong&gt;recursive&lt;/strong&gt; &lt;strong&gt;CTE&lt;/strong&gt; refers to itself. It has two parts joined by &lt;strong&gt;&lt;em&gt;UNION ALL&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The anchor member, which runs once and produces the starting rows.&lt;/li&gt;
&lt;li&gt;The recursive member, which refers to the CTE’s own name and runs repeatedly on the rows produced in the previous round, until it returns no new rows.
You &lt;strong&gt;must&lt;/strong&gt; use the &lt;strong&gt;&lt;em&gt;RECURSIVE&lt;/em&gt;&lt;/strong&gt; keyword in PostgreSQL, and you m_ust make sure the recursion ends, either through a condition in the recursive member or a depth limit_, otherwise the query runs forever.
Example 1: generating a calendar to find days with no visits. Our data stops on 15 June, so we extend the calendar to 17 June to show how gaps appear.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;            
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="o"&gt;+&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;calendar&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-17'&lt;/span&gt;             
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&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;visits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The anchor produces 1 June; each round adds one more day until 17 June is reached, giving 17 rows. A left join then counts visits per day, with 0 for days that have none.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;CTEs versus subqueries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Both let you use the result of one query inside another, and for simple cases they give identical results and plans. They differ in readability, reuse and capability.&lt;br&gt;
&lt;strong&gt;Performance&lt;/strong&gt;&lt;br&gt;
Usually the same; PostgreSQL 12+ may inline it, older versions always materialised it&lt;br&gt;
Optimiser can always inline it&lt;br&gt;
&lt;strong&gt;Rule of thumb:&lt;/strong&gt; use a CTE when the logic has several steps, when you need the same result twice, or when you need recursion. Use a subquery for a short, single-use filter such as WHERE x IN (SELECT ...), or when you need a correlated check against each outer row.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;doctor_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a problem this small the subquery is more compact. The CTE wins as the number of steps grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;SQL Subqueries&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;em&gt;What is a subquery?&lt;/em&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A subquery (inner query) is a &lt;strong&gt;SELECT&lt;/strong&gt; statement nested inside another statement, always written in parentheses. The inner query produces a value, a list or a table, and the outer query uses it. Subqueries let you answer questions in two steps without creating temporary tables, for example: first find the average bill, then find the visits above it.&lt;br&gt;
A subquery can appear in three places:&lt;br&gt;
&lt;strong&gt;Clause&lt;/strong&gt;&lt;br&gt;
What the subquery returns&lt;br&gt;
Example use&lt;br&gt;
&lt;strong&gt;WHERE (or HAVING)&lt;/strong&gt;&lt;br&gt;
A single value, a list, or an existence test&lt;br&gt;
Filter visits above the average bill&lt;br&gt;
&lt;strong&gt;FROM&lt;/strong&gt;&lt;br&gt;
A table (called a derived table; it needs an alias)&lt;br&gt;
Aggregate first, then aggregate the aggregates&lt;br&gt;
&lt;strong&gt;SELECT&lt;/strong&gt;&lt;br&gt;
A single value per outer row (scalar)&lt;br&gt;
Show a count beside each doctor&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;&lt;strong&gt;Scalar subqueries&lt;/strong&gt;&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;What it is: a subquery that returns exactly one row and one column, so it can be used anywhere a single value is allowed. If it returns more than one row, PostgreSQL raises an error.&lt;br&gt;
Scenario: list visits billed above the hospital average.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query returns one number, 16,266.67. The outer query compares each bill with it. This cannot be written as &lt;strong&gt;WHERE total_bill_kes &amp;gt;&lt;/strong&gt; &lt;strong&gt;AVG(total_bill_kes)&lt;/strong&gt;, because aggregates are not allowed in &lt;strong&gt;WHERE&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;&lt;strong&gt;Non-correlated (independent) subqueries&lt;/strong&gt;&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;What it is: a subquery that does not refer to the outer query. It can run completely on its own, and the database evaluates it once. The scalar example above is non-correlated, and so is any &lt;strong&gt;IN (SELECT ...)&lt;/strong&gt;.&lt;br&gt;
Scenario: list the patients who have ever been admitted as inpatients.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patient_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;patients&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
                     &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
                     &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Inpatient'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query returns the list of patient IDs with an inpatient visit; the outer query keeps patients whose ID is in that list. Duplicates in the list do not matter.&lt;br&gt;
&lt;strong&gt;Caution with &lt;em&gt;NOT IN&lt;/em&gt;&lt;/strong&gt;: if the inner list contains even one &lt;strong&gt;NULL, NOT IN&lt;/strong&gt; returns no rows at all, because the comparison with &lt;strong&gt;NULL&lt;/strong&gt; is unknown. Columns such as ward_id and diagnosis_id in visits are nullable, so prefer &lt;strong&gt;NOT EXISTS&lt;/strong&gt; for &lt;em&gt;“not in”&lt;/em&gt; logic.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Correlated subqueries&lt;/em&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;What it is: a subquery that refers to a column of the outer query, so it is conceptually re-evaluated for every outer row.&lt;br&gt;
Scenario: find visits where the bill is higher than that same doctor’s own average bill.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                          &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
                          &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each outer visit v, the inner query calculates the average for that visit’s doctor &lt;strong&gt;(v2.doctor_id = v.doctor_id)&lt;/strong&gt; and compares. The comparison value changes from doctor to doctor: 23,344.44 for Dr. Mwangi, 10,866.67 for Dr. Wanjiku, 17,533.33 for Dr. Otieno and 12,483.33 for Dr. Chebet.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Nested subqueries&lt;/em&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;What it is: a subquery inside another subquery. Each level feeds the one above it.&lt;br&gt;
Scenario: list the patients treated by the highest-earning doctor.&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;patient_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;patients&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;
                &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&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;doctor_id&lt;/span&gt;
                &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
                &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the inside out: the innermost query ranks doctors by revenue and returns the top one (doctor 101, KES 210,100). The middle query returns the IDs of patients that doctor saw (1, 2, 5, 10, 12 and 16). The outer query turns those IDs into names.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Subqueries with IN, EXISTS, ANY and ALL&lt;/em&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;IN&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
expression &lt;em&gt;IN&lt;/em&gt; (subquery) is true if the value equals any value the subquery returns.&lt;em&gt;NOT IN&lt;/em&gt; is its negation, with the &lt;em&gt;NULL&lt;/em&gt; caveat above.&lt;br&gt;
&lt;em&gt;EXISTS&lt;/em&gt; and &lt;em&gt;NOT EXISTS&lt;/em&gt;&lt;br&gt;
What it does: _EXISTS _(subquery) is true if the subquery returns at least one row; the columns selected do not matter (by convention SELECT &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It is almost always correlated, and it stops as soon as one match is found.
Scenario: which doctors have treated at least one Red-triage (critical) patient, and which have not?
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;WHERE&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;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
              &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
                &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triage_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Red'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&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;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
                  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
                    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triage_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Red'&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;&lt;em&gt;ANY and ALL&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
What they do: compare a value with every value returned by a subquery.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;ANY&lt;/strong&gt; (subquery) is true if the value is greater than at least one returned value (equivalent to greater than the minimum).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;ALL&lt;/strong&gt; (subquery) is true if the value is greater than every returned value (equivalent to greater than the maximum).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;= ANY (...)&lt;/code&gt; is the same as &lt;code&gt;IN (...)&lt;/code&gt;, and &lt;code&gt;&amp;lt;&amp;gt; ALL (...)&lt;/code&gt; is the same as &lt;code&gt;NOT IN (...)&lt;/code&gt;.
Scenario: compare General Medicine bills (department 20) with Orthopedics bills (department 30). Orthopedic bills range from KES 6,500 to KES 33,000.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Greater than at least one Orthopedics bill (i.e. above 6,500)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Greater than every Orthopedics bill (i.e. above 33,000)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Subqueries in SELECT, FROM and WHERE&lt;/em&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;In SELECT&lt;/strong&gt; &lt;em&gt;(scalar subquery per row)&lt;/em&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&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;visits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns one row per doctor with 9, 9, 6 and 6 visits (Mwangi, Wanjiku, Otieno, Chebet). It is correlated, since it uses d.doctor_id. A join with GROUP BY is usually more efficient, but this form is convenient for adding one extra column to an existing query without disturbing its grouping.&lt;br&gt;
*&lt;em&gt;In FROM *&lt;/em&gt;&lt;em&gt;(derived table&lt;/em&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&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;avg_revenue_per_doctor&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&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;revenue&lt;/span&gt;
      &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&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;doctor_id&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;doctor_revenue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query produces four revenue totals; the outer query averages them. The result is 122000.00 (488,000 divided by 4 doctors). This “aggregate of an aggregate” cannot be written as AVG(SUM(...)) in a single SELECT, so a derived table (or CTE) is required. The alias doctor_revenue is mandatory.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Learning these four concepts changes what SQL feels like. It stops being a way to retrieve rows and becomes a way to ask questions about data.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>data</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>From Data to Decisions: Mastering PBI Charts and Dashboards</title>
      <dc:creator>obed marabu</dc:creator>
      <pubDate>Mon, 29 Jun 2026 10:36:32 +0000</pubDate>
      <link>https://dev.to/obed_marabu_1228278258151/from-data-to-decisions-mastering-pbi-charts-and-dashboards-1kk6</link>
      <guid>https://dev.to/obed_marabu_1228278258151/from-data-to-decisions-mastering-pbi-charts-and-dashboards-1kk6</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Power BI is one of the leading business intelligence platforms precisely because it simplifies that transformation, turning spreadsheets, databases, and data streams into visual stories that anyone can understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Core building blocks in PBI&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Charts&lt;/em&gt;&lt;/strong&gt;-Individual visual representations of data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Dashboards&lt;/em&gt;&lt;/strong&gt;-Curated collections of charts and visuals, assembled into a unified view that tells a broader story at a glance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Together, Power BI charts and dashboards transform complex, multi-source data into interactive, real-time visuals that give every stakeholder, from analyst to executive, instant clarity and the confidence to act.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Charts in Power BI&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Power BI charts are used to visualize and analyse data from different perspectives. When combined, users can understand trends and relationships in data. This helps to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easily compare different values&lt;/li&gt;
&lt;li&gt;Observe patterns over time&lt;/li&gt;
&lt;li&gt;Analyze connections between data&lt;/li&gt;
&lt;li&gt;Combine charts for hierarchical or multi-dimensional data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Types of Charts&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Stacked Bar Chart : A stacked bar chart in Power BI helps you compare totals while visualizing how individual subcategories contribute to those totals. To create one, you need to drag your categories to the Y-axis, numeric values to the X-axis, and the subcategory into the Legend field.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcus428vtxh8u6nzh4t6h.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcus428vtxh8u6nzh4t6h.png" alt="This is a stacked bar chart that shows the relationship between the crop type and the yield." width="312" height="247"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Column Chart : They display categorical data with &lt;em&gt;&lt;strong&gt;vertical&lt;/strong&gt;&lt;/em&gt; rectangular bars whose lengths are proportional to the values they represent.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyyfl06vmu062hyxx8vyb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyyfl06vmu062hyxx8vyb.png" alt="This is a column chart showing the trend between counties and the sum of profits" width="799" height="395"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;100% Stacked Column Chart : Stretches the stacked bars vertically to fit 100% of the visual window to emphasize the relative percentage allocation&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvztzyk95ehv68o4o1xn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvztzyk95ehv68o4o1xn.png" alt="100% stacked column chart" width="471" height="323"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bar Chart : They display categorical data with &lt;em&gt;&lt;strong&gt;horizontal&lt;/strong&gt;&lt;/em&gt; rectangular bars whose lengths are proportional to the values they represent.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9kyq3o5j53ewqjd0k9d.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9kyq3o5j53ewqjd0k9d.png" alt="This is a bar chart showing the relationship trend between the yield and the crop types " width="299" height="214"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line Chart : Line charts plot data points connected by straight lines, typically used to show how a value changes over time. They are mostly used for visualising trends, seasonal patterns, and performance over a continuous period.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczpcup1ec95w1k8n2lc1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczpcup1ec95w1k8n2lc1.png" alt="This is a sample line chart" width="413" height="289"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Area Chart : They are similar to line charts but with the area beneath the line filled in with colour. They emphasise the magnitude of change over time, making them useful for showing volume energy rather than just the rate of change.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F41kzxqx3gqs219ksm9s0.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F41kzxqx3gqs219ksm9s0.png" alt="This is a sample area chart" width="407" height="353"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pie Chart : Pie charts divide a circle into slices, each representing a category's proportion of the whole. They are best for showing simple part-to-whole relationships when there are a small number of distinct categories.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2dlpw6mylx53qx85vwmj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2dlpw6mylx53qx85vwmj.png" alt="This is a sample pie chart" width="435" height="344"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Donut Chart : Similar to pie charts but with with a hollow centre, often used to display a summary metric in the middle.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxmqanf3izh8d1x43nad.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxmqanf3izh8d1x43nad.png" alt="Sample donut chart" width="570" height="344"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treemap : They display hierarchical data as a set of nested rectangles, where the size of each rectangle is proportional to its value. They are useful when you need to show both the size of categories and their contribution to a hierarchy simultaneously.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F217j4uv9x75be5k2egt3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F217j4uv9x75be5k2egt3.png" alt="This is a sample Treemap" width="510" height="353"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scatter Chart : Scatter charts plot individual data points on a two-axis grid, each point representing the intersection of two numerical values. A bubble chart extends this by adding a third variable represented by the size of each point. They are used to explore correlations and relationships between two variables &lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovv620ze3khmqj7wmky1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovv620ze3khmqj7wmky1.png" alt="This is a sample scatter chart" width="474" height="379"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Card (single metric) : This is used to highlight a single, key metric or KPI that responds dynamically to filters and slicers.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrqbr4tc3t7wkyd6nxap.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrqbr4tc3t7wkyd6nxap.png" alt="This are sample cards" width="357" height="231"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Dashboards in Power BI
&lt;/h2&gt;

&lt;p&gt;A Power BI dashboard is a single-page canvas that brings together multiple visuals into a unified, interactive view of your data. Unlike a report page, a dashboard is designed for monitoring: it is meant to be glanced at, not studied at length, and it surfaces the most important metrics so that decision-makers can quickly assess the state of the business.&lt;br&gt;
Dashboards in Power BI can pull visuals from multiple underlying reports and datasets, making them a powerful aggregation layer. They support real-time data refresh and can be shared across an organisation, making them central to collaborative data culture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of Dashboards
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Strategic Dashboards&lt;/strong&gt;&lt;/em&gt;: Designed for executives and upper management. They provide a high-level overview of long-term business goals, company performance, and key performance indicators (KPIs) to aid in high-level decision making.&lt;br&gt;
&lt;em&gt;Characteristics:&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Minimal and visually clean — low information density by design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus on a small number of carefully selected KPIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use large, bold metrics and simple colour coding (on track / at risk / off track)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Often include period-on-period comparison and target vs. actual indicators&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Operational Dashboards&lt;/em&gt;&lt;/strong&gt;: Focused on day-to-day operations and real-time monitoring. They help middle management and operational staff quickly identify bottlenecks or issues that require immediate action.&lt;br&gt;
&lt;em&gt;Characteristics:&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refreshed frequently — sometimes in real time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focused on current status rather than historical trends &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use traffic-light indicators, gauges, and alert-style KPI cards   &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simple and uncluttered to enable rapid reading&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Analytical Dashboards&lt;/strong&gt;&lt;/em&gt;: Built for data analysts and business intelligence professionals. They are complex, data-rich views designed to process large volumes of data, uncover deep trends, and identify the "why" behind business patterns.&lt;br&gt;
&lt;em&gt;Characteristics:&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rich with interactive filters, slicers, and drill-through capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Include trend lines, comparisons to prior periods, and benchmarks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;May feature more complex chart types (scatter, treemap, decomposition tree)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Often linked to detailed underlying reports for deeper investigation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Tactical Dashboards&lt;/strong&gt;&lt;/em&gt;: Used primarily by department heads and middle managers. They bridge the gap between overarching strategy and day-to-day operations, tracking progress toward short-term goals.&lt;br&gt;
&lt;em&gt;Characteristics:&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Balance summary KPIs with supporting trend data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;May include both current status and recent historical context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interactive enough to allow managers to investigate underperformance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typically refreshed daily or weekly&lt;br&gt;
&lt;em&gt;Below is a visual example of a Power BI Dashbard:&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmako81ewev7wodmovwrn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmako81ewev7wodmovwrn.png" alt="Dashboard showing the relationship between crop performance and profitability" width="774" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Power BI charts and dashboards are not simply visual decoration for data. They are the primary interface through which organisations understand, communicate, and act on information. Choosing the right chart type ensures that your data is presented clearly and honestly. Designing the right kind of dashboard ensures that the right people see the right information at the right time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>Linux Fundamentals for Data Engineering</title>
      <dc:creator>obed marabu</dc:creator>
      <pubDate>Fri, 12 Jun 2026 18:19:43 +0000</pubDate>
      <link>https://dev.to/obed_marabu_1228278258151/linux-fundamentals-for-data-engineering-1ig8</link>
      <guid>https://dev.to/obed_marabu_1228278258151/linux-fundamentals-for-data-engineering-1ig8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;From a hobby to one of the best operating systems, Linux is an important tool in the world of Data Engineering.Linux is a free, open-source operating system kernel first developed by Linus Torvalds in 1991. Built on the principles of Unix, it has since grown into the backbone of modern computing — powering everything from smartphones and personal laptops to enterprise servers, supercomputers, and cloud infrastructure. &lt;/p&gt;

&lt;h3&gt;
  
  
  LinuX Basic Commands
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ls&lt;/code&gt; — The most frequently used command in Linux to list directories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pwd&lt;/code&gt; — Print working directory command in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd&lt;/code&gt; — Linux command to navigate through directories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mkdir&lt;/code&gt; — Command used to create directories in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mv&lt;/code&gt; — Move or rename files in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cp&lt;/code&gt; — Similar usage as mv but for copying files in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rm&lt;/code&gt; — Delete files or directories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;touch&lt;/code&gt; — Create blank/empty files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ln&lt;/code&gt; — Create symbolic links (shortcuts) to other files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cat&lt;/code&gt; — Display file contents on the terminal&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clear&lt;/code&gt; — Clear the terminal display&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;echo&lt;/code&gt; — Print any text that follows the command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;less&lt;/code&gt; — Linux command to display paged outputs in the terminal&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;man&lt;/code&gt; — Access manual pages for all Linux commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uname&lt;/code&gt; — Linux command to get basic information about the OS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;whoami&lt;/code&gt; — Get the active username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tar&lt;/code&gt; — Command to extract and compress files in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grep&lt;/code&gt; — Search for a string within an output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;head&lt;/code&gt;— Return the specified number of lines from the top&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tail&lt;/code&gt; — Return the specified number of lines from the bottom&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;diff&lt;/code&gt; — Find the difference between two files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmp&lt;/code&gt; — Allows you to check if two files are identical&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;comm&lt;/code&gt; — Combines the functionality of diff and cmp&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sort&lt;/code&gt; — Linux command to sort the content of a file while outputting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;export&lt;/code&gt; — Export environment variables in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zip&lt;/code&gt; — Zip files in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unzip&lt;/code&gt; — Unzip files in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ssh&lt;/code&gt; — Secure Shell command in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service&lt;/code&gt; — Linux command to start and stop services&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ps&lt;/code&gt; — Display active processes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kill&lt;/code&gt; and &lt;code&gt;killall&lt;/code&gt; — Kill active processes by process ID or name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;df&lt;/code&gt;— Display disk filesystem information&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mount&lt;/code&gt; — Mount file systems in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chmod&lt;/code&gt; — Command to change file permissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chown&lt;/code&gt; — Command for granting ownership of files or folders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ifconfig&lt;/code&gt; — Display network interfaces and IP addresses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;traceroute&lt;/code&gt; — Trace all the network hops to reach the destination&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wget&lt;/code&gt; — Direct download files from the internet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ufw&lt;/code&gt;— Firewall command&lt;/li&gt;
&lt;li&gt;iptables — Base firewall for all other firewall utilities to interface with&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apt, pacman, yum, rpm&lt;/code&gt; — Package managers depending on the distro&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo&lt;/code&gt; — Command to escalate privileges in Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cal&lt;/code&gt; — View a command-line calendar&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alias&lt;/code&gt; — Create custom shortcuts for your regularly used commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dd&lt;/code&gt; — Majorly used for creating bootable USB sticks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;whereis&lt;/code&gt; — Locate the binary, source, and manual pages for a command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;whatis&lt;/code&gt; — Find what a command is used for&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;top&lt;/code&gt; — View active processes live with their system usage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useradd&lt;/code&gt; and &lt;code&gt;usermod&lt;/code&gt; — Add new user or change existing users data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;passwd&lt;/code&gt; — Create or update passwords for existing users&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Practical Examples of Linux Commands
&lt;/h4&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%2Fe28w77gr82iapchoxbg2.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%2Fe28w77gr82iapchoxbg2.png" alt=" " width="591" height="82"&gt;&lt;/a&gt;&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%2Ftrmjw59wxycxyfsit9rb.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%2Ftrmjw59wxycxyfsit9rb.png" alt=" " width="680" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;The &lt;code&gt;ls&lt;/code&gt; command&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;The most frequently used command in Linux to list directories.&lt;br&gt;
&lt;strong&gt;&lt;code&gt;ls&lt;/code&gt; command options;&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;-l - Long listing format&lt;/li&gt;
&lt;li&gt;-a - Include hidden files&lt;/li&gt;
&lt;li&gt;-h - Human-readable sizes&lt;/li&gt;
&lt;li&gt;-t - Sort by modification time&lt;/li&gt;
&lt;li&gt;-r - Reverse order while sorting&lt;/li&gt;
&lt;li&gt;-R - List subdirectories recursively&lt;/li&gt;
&lt;li&gt;-S - Sort by file size&lt;/li&gt;
&lt;li&gt;-1 - List one file per line&lt;/li&gt;
&lt;li&gt;-d - List directories themselves, not their contents&lt;/li&gt;
&lt;li&gt;-F - Append indicator (one of */=@|) to entries&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Editors in Linux
&lt;/h4&gt;

&lt;p&gt;We use text editors in Linux because "everything is a file" in the Linux philosophy, meaning system settings, hardware configurations, scripts, and logs are all stored as plain text files. Unlike word processors (like Microsoft Word) that embed hidden formatting code, text editors handle pure, raw text, which is required to safely modify system operations without corrupting data.&lt;br&gt;
They include;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vim(&lt;code&gt;vi&lt;/code&gt;): A highly powerful, modal editor available by default on almost every Linux distribution. It utilizes specific modes for typing versus navigating.&lt;/li&gt;
&lt;li&gt;Nano :A simple, beginner-friendly editor that displays helpful keyboard shortcuts at the bottom of the screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  File Permissions in Linux
&lt;/h4&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%2Fhs6po9tntlt709xx042v.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%2Fhs6po9tntlt709xx042v.png" alt=" " width="617" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Reading the Permission String&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
This shows the permissions allowed to each user in a file.&lt;br&gt;
Example: (&lt;code&gt;-rw-r--r--&lt;/code&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First character(-): Shows that this is a file, if first character is (d), then it is a directory.&lt;/li&gt;
&lt;li&gt;Characters 2-4(&lt;code&gt;rw-&lt;/code&gt;): The owner has read and write access, but cannot execute it.&lt;/li&gt;
&lt;li&gt;Characters 5-7(&lt;code&gt;r--&lt;/code&gt;): Members of the group have read-only access  it.&lt;/li&gt;
&lt;li&gt;Characters 8-10(&lt;code&gt;r--&lt;/code&gt;): All other users on the system have read-only access&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Numeric Permission Values
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;0(&lt;code&gt;---&lt;/code&gt;) :No permission&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;1(&lt;code&gt;--x&lt;/code&gt;) :Execute only&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2(&lt;code&gt;-w-&lt;/code&gt;) :Write only&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3(&lt;code&gt;-wx&lt;/code&gt;) :Write + Execute&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;4(&lt;code&gt;r--&lt;/code&gt;) :Read only&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;5(&lt;code&gt;r-x&lt;/code&gt;) :Read + Execute&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;6(&lt;code&gt;rw-&lt;/code&gt;) :Read + Write&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;7(&lt;code&gt;rwx&lt;/code&gt;) :Read + Write + Execute&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Chmod and Chown Commands
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;chmod&lt;/code&gt; command modifies read, write, and execute permissions for files or directories, while the &lt;code&gt;chown&lt;/code&gt; command modifies the user and/or group ownership of those files. Together, they form the core of the Linux file security and access control model.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;Chmod examples&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;chmod 777 file&lt;/code&gt;   :Everyone can read, write, execute&lt;br&gt;
&lt;code&gt;chmod 755 file&lt;/code&gt;   :Owner full permission; others read + execute&lt;br&gt;
&lt;code&gt;chmod 644 file&lt;/code&gt;   :Owner read/write; others read only&lt;br&gt;
&lt;code&gt;chmod 600 file&lt;/code&gt;   :Private file (owner only)&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;Chown examples&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;chmod u+x file&lt;/code&gt;   :Add execute for user&lt;br&gt;
&lt;code&gt;chmod g-w file&lt;/code&gt;   :Remove write from group&lt;br&gt;
&lt;code&gt;chmod o+r file&lt;/code&gt;   :Add read for others&lt;br&gt;
&lt;code&gt;chmod a+x file&lt;/code&gt;   :Allow execute for everyone&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;Ownership commands&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;ls -l&lt;/code&gt;    :View ownership and permissions&lt;br&gt;
&lt;code&gt;sudo chown user file&lt;/code&gt; :Change owner&lt;br&gt;
&lt;code&gt;sudo chown user:group file&lt;/code&gt; :Change owner and group&lt;br&gt;
&lt;code&gt;sudo adduser username&lt;/code&gt; :Create new user&lt;br&gt;
&lt;code&gt;sudo usermod -aG devops username&lt;/code&gt; :Add user to group&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Learning and understanding the Linux fundamentals is necessary for all data engineer. It was the perfect start for my data engineering journey.&lt;/p&gt;

</description>
      <category>data</category>
      <category>linux</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
