<?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: solomon gachure</title>
    <description>The latest articles on DEV Community by solomon gachure (@gachure).</description>
    <link>https://dev.to/gachure</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%2F4193368%2Fef75de88-2ba5-4070-9b7e-b52d58116736.jpg</url>
      <title>DEV Community: solomon gachure</title>
      <link>https://dev.to/gachure</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gachure"/>
    <language>en</language>
    <item>
      <title>SQL Advanced Concepts Explained with Hospital Data</title>
      <dc:creator>solomon gachure</dc:creator>
      <pubDate>Mon, 19 Oct 2026 06:44:06 +0000</pubDate>
      <link>https://dev.to/gachure/sql-advanced-concepts-explained-with-hospital-data-ga0</link>
      <guid>https://dev.to/gachure/sql-advanced-concepts-explained-with-hospital-data-ga0</guid>
      <description>&lt;p&gt;If you're learning SQL, you've probably already used &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, and &lt;code&gt;UPDATE&lt;/code&gt;. These commands are a great starting point, but real-world database work requires more than retrieving individual records.&lt;/p&gt;

&lt;p&gt;Imagine you're working with a hospital database and the management team asks questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which doctors handle the most patient visits?&lt;/li&gt;
&lt;li&gt;Which department has the highest total billing?&lt;/li&gt;
&lt;li&gt;How many times has each patient visited the hospital?&lt;/li&gt;
&lt;li&gt;Which visits have bills above the department average?&lt;/li&gt;
&lt;li&gt;Which patients require follow-up?&lt;/li&gt;
&lt;li&gt;How can we compare a patient's current visit with their previous visit?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can answer these questions using four powerful SQL concepts:&lt;/p&gt;

&lt;p&gt;**1. Window functions&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SQL joins&lt;/li&gt;
&lt;li&gt;Common Table Expressions (CTEs)&lt;/li&gt;
&lt;li&gt;Subqueries**&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, we'll explore each concept using PostgreSQL and a hospital database containing tables such as &lt;code&gt;patients&lt;/code&gt;, &lt;code&gt;doctors&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;diagnosis&lt;/code&gt;, &lt;code&gt;procedures&lt;/code&gt;, &lt;code&gt;wards&lt;/code&gt;, and &lt;code&gt;visits&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;*&lt;em&gt;SQL Window Functions
*&lt;/em&gt;
What are window functions?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Window functions perform calculations across related rows without collapsing those rows into a single result.&lt;/p&gt;

&lt;p&gt;For example, suppose we want to display every patient visit alongside the total number of visits made by that patient.&lt;/p&gt;

&lt;p&gt;We could use &lt;code&gt;GROUP BY&lt;/code&gt;, but that would return a summary for each patient. A window function allows us to keep the individual visit records.&lt;/p&gt;

&lt;p&gt;Understanding &lt;code&gt;OVER()&lt;/code&gt; and &lt;code&gt;PARTITION BY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Before exploring individual functions, let's understand the syntax:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
    visit_id,&lt;br&gt;
    patient_id,&lt;br&gt;
    COUNT(*) OVER (&lt;br&gt;
        PARTITION BY patient_id&lt;br&gt;
    ) AS total_patient_visits&lt;br&gt;
FROM visits;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here's what's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COUNT(*)&lt;/code&gt; counts rows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OVER()&lt;/code&gt; tells PostgreSQL to calculate the function as a window function.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PARTITION BY patient_id&lt;/code&gt; creates a separate window for each patient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hospital can see every visit while also knowing how many visits belong to each patient.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;PARTITION BY&lt;/code&gt; doesn't remove the individual rows.&lt;/p&gt;

&lt;p&gt;1.1 &lt;code&gt;ROW_NUMBER()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt; assigns a unique sequential number to each row within a window.&lt;/p&gt;

&lt;p&gt;Let's number each patient's visits, starting with their most recent visit.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
    patient_id,&lt;br&gt;
    visit_id,&lt;br&gt;
    visit_date,&lt;br&gt;
    ROW_NUMBER() OVER (&lt;br&gt;
        PARTITION BY patient_id&lt;br&gt;
        ORDER BY visit_date DESC, visit_id DESC&lt;br&gt;
    ) AS visit_number&lt;br&gt;
FROM visits&lt;br&gt;
ORDER BY patient_id, visit_number;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The most recent visit for each patient receives row number &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This technique can help identify each patient's latest recorded visit.&lt;/p&gt;

&lt;p&gt;1.2 &lt;code&gt;RANK()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RANK()&lt;/code&gt; assigns ranks based on a value. When two rows have the same value, they receive the same rank, and the next rank is skipped.&lt;/p&gt;

&lt;p&gt;Let's rank doctors by the number of visits they've handled.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
    d.doctor_name,&lt;br&gt;
    COUNT(v.visit_id) AS total_visits,&lt;br&gt;
    RANK() OVER (&lt;br&gt;
        ORDER BY COUNT(v.visit_id) DESC&lt;br&gt;
    ) AS doctor_rank&lt;br&gt;
FROM doctors d&lt;br&gt;
LEFT JOIN visits v&lt;br&gt;
    ON d.doctor_id = v.doctor_id&lt;br&gt;
GROUP BY d.doctor_id, d.doctor_name&lt;br&gt;
ORDER BY doctor_rank;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This query counts visits per doctor and ranks doctors from highest to lowest workload.&lt;/p&gt;

&lt;p&gt;For example, if the visit counts are &lt;code&gt;10, 10, 7&lt;/code&gt;, the ranks will be &lt;code&gt;1, 1, 3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hospital management can use this to understand how recorded visits are distributed among doctors.&lt;/p&gt;

&lt;p&gt;1.3 &lt;code&gt;DENSE_RANK()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DENSE_RANK()&lt;/code&gt; works like &lt;code&gt;RANK()&lt;/code&gt;, except it doesn't skip rank numbers after ties.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
    d.department_name,&lt;br&gt;
    SUM(v.total_bill_kes) AS total_billing_kes,&lt;br&gt;
    DENSE_RANK() OVER (&lt;br&gt;
        ORDER BY SUM(v.total_bill_kes) DESC&lt;br&gt;
    ) AS billing_rank&lt;br&gt;
FROM department d&lt;br&gt;
JOIN visits v&lt;br&gt;
    ON d.department_id = v.department_id&lt;br&gt;
GROUP BY d.department_id, d.department_name&lt;br&gt;
ORDER BY billing_rank;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This query calculates the total recorded billing for each department and ranks the departments.&lt;/p&gt;

&lt;p&gt;If two departments tie for first place, the next department receives rank &lt;code&gt;2&lt;/code&gt;, not &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt; vs. &lt;code&gt;RANK()&lt;/code&gt; vs. &lt;code&gt;DENSE_RANK()&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Values being ranked&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100, 100, 80&lt;/td&gt;
&lt;td&gt;1, 2, 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RANK()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100, 100, 80&lt;/td&gt;
&lt;td&gt;1, 1, 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DENSE_RANK()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100, 100, 80&lt;/td&gt;
&lt;td&gt;1, 1, 2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference matters when multiple records have identical values.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.4 &lt;code&gt;NTILE()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;NTILE()&lt;/code&gt; divides ordered rows into approximately equal groups.&lt;/p&gt;

&lt;p&gt;For example, we can divide hospital visits into four groups based on their bill amounts.&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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    total_bill_kes,
    NTILE(4) OVER (
        ORDER BY total_bill_kes DESC
    ) AS billing_group
FROM visits
ORDER BY billing_group;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function assigns each visit to a group numbered from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Group &lt;code&gt;1&lt;/code&gt; contains the highest bills, while group &lt;code&gt;4&lt;/code&gt; contains the lowest bills.&lt;/p&gt;

&lt;p&gt;This can help the finance department compare visits across billing quartiles. Equal bill amounts may be split across groups because &lt;code&gt;NTILE()&lt;/code&gt; balances row counts rather than keeping tied values together.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.5 &lt;code&gt;LAG()&lt;/code&gt; and &lt;code&gt;LEAD()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Sometimes, we need to compare a row with a previous or subsequent row.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LAG()&lt;/code&gt; looks backward.&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="nv"&gt;`SELECT
    patient_id,
    visit_id,
    visit_date,
    total_bill_kes,
    LAG(total_bill_kes) OVER (
        PARTITION BY patient_id
        ORDER BY visit_date, visit_id
    ) AS previous_bill
FROM visits
ORDER BY patient_id, visit_date, visit_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieves the bill amount from the previous recorded visit by the same patient.&lt;/p&gt;

&lt;p&gt;The first visit for each patient has &lt;code&gt;NULL&lt;/code&gt; for &lt;code&gt;previous_bill&lt;/code&gt; because there is no earlier visit in that patient's window.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LEAD()&lt;/code&gt; looks forward.&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="nv"&gt;`SELECT
    patient_id,
    visit_id,
    visit_date,
    LEAD(visit_date) OVER (
        PARTITION BY patient_id
        ORDER BY visit_date, visit_id
    ) AS next_visit_date
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This displays the next recorded visit date for each patient.&lt;/p&gt;

&lt;p&gt;These functions can help analyze changes in billing and the intervals between recorded visits. They retrieve existing data rather than predicting future appointments.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.6 &lt;code&gt;FIRST_VALUE()&lt;/code&gt; and &lt;code&gt;LAST_VALUE()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These functions retrieve values from specific positions within a window frame.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FIRST_VALUE()&lt;/code&gt; returns the first value.&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="nv"&gt;`SELECT
    patient_id,
    visit_id,
    visit_date,
    FIRST_VALUE(visit_date) OVER (
        PARTITION BY patient_id
        ORDER BY visit_date, visit_id
        ROWS BETWEEN UNBOUNDED PRECEDING
                 AND UNBOUNDED FOLLOWING
    ) AS first_visit_date
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every visit for the same patient displays their earliest recorded visit date.&lt;/p&gt;

&lt;p&gt;Now let's retrieve the latest recorded visit date using &lt;code&gt;LAST_VALUE()&lt;/code&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="nv"&gt;`SELECT
    patient_id,
    visit_id,
    visit_date,
    LAST_VALUE(visit_date) OVER (
        PARTITION BY patient_id
        ORDER BY visit_date, visit_id
        ROWS BETWEEN UNBOUNDED PRECEDING
                 AND UNBOUNDED FOLLOWING
    ) AS last_visit_date
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The window frame tells PostgreSQL to consider the entire patient's partition.&lt;/p&gt;

&lt;p&gt;Without an explicit full-partition frame, &lt;code&gt;LAST_VALUE()&lt;/code&gt; may return the current row's value or the last value among its peers instead of the final value in the partition.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.7 Aggregate window functions
&lt;/h3&gt;

&lt;p&gt;Aggregate functions can also be used as window functions.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;SUM()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;SUM()&lt;/code&gt; calculates a 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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    total_bill_kes,
    SUM(total_bill_kes) OVER (
        PARTITION BY patient_id
    ) AS patient_total_billing
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every visit displays the total recorded billing across that patient's visits.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;AVG()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;AVG()&lt;/code&gt; calculates an 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="nv"&gt;`SELECT
    visit_id,
    department_id,
    patient_satisfaction_score,
    AVG(patient_satisfaction_score) OVER (
        PARTITION BY department_id
    ) AS department_average_satisfaction
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to compare each visit's satisfaction score with its department's average. &lt;code&gt;AVG()&lt;/code&gt; ignores &lt;code&gt;NULL&lt;/code&gt; satisfaction scores.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;COUNT()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;COUNT()&lt;/code&gt; counts records.&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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    COUNT(*) OVER (
        PARTITION BY patient_id
    ) AS patient_visit_count
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every visit displays the total number of recorded visits for that patient.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;MIN()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;MIN()&lt;/code&gt; finds the minimum value.&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="nv"&gt;`SELECT
    visit_id,
    department_id,
    total_bill_kes,
    MIN(total_bill_kes) OVER (
        PARTITION BY department_id
    ) AS lowest_department_bill
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This displays the lowest recorded bill in each department alongside every visit.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;MAX()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;MAX()&lt;/code&gt; finds the maximum value.&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="nv"&gt;`SELECT
    visit_id,
    department_id,
    total_bill_kes,
    MAX(total_bill_kes) OVER (
        PARTITION BY department_id
    ) AS highest_department_bill
FROM visits;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This displays the highest recorded bill in each department.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.8 Understanding &lt;code&gt;ORDER BY&lt;/code&gt; and window frames
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ORDER BY&lt;/code&gt; inside &lt;code&gt;OVER()&lt;/code&gt; determines the order used by the window function.&lt;/p&gt;

&lt;p&gt;For example, we can calculate a running total of hospital billing:&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="nv"&gt;`SELECT
    visit_id,
    visit_date,
    total_bill_kes,
    SUM(total_bill_kes) OVER (
        ORDER BY visit_date, visit_id
        ROWS BETWEEN UNBOUNDED PRECEDING
                 AND CURRENT ROW
    ) AS running_total_billing
FROM visits
ORDER BY visit_date, visit_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first row contains its own bill. Each subsequent row adds its bill to the previous running total.&lt;/p&gt;

&lt;p&gt;Common window-frame boundaries include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt;: the beginning of the window.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CURRENT ROW&lt;/code&gt;: the current row, with behavior depending on the frame type.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED FOLLOWING&lt;/code&gt;: the end of the window.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n PRECEDING&lt;/code&gt; and &lt;code&gt;n FOLLOWING&lt;/code&gt;: positions before or after the current row, depending on the frame unit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostgreSQL supports &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt; frame units.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;ORDER BY&lt;/code&gt; inside &lt;code&gt;OVER()&lt;/code&gt; does not necessarily sort the final output. Use the outer &lt;code&gt;ORDER BY&lt;/code&gt; when you want the displayed records sorted.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SQL Joins&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What are joins?&lt;/p&gt;

&lt;p&gt;Joins allow us to combine data from multiple tables using a relationship between them.&lt;/p&gt;

&lt;p&gt;In our hospital database, &lt;code&gt;patients&lt;/code&gt; contains patient information while &lt;code&gt;visits&lt;/code&gt; contains visit records. We can join the tables using &lt;code&gt;patient_id&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 &lt;code&gt;INNER JOIN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;An &lt;code&gt;INNER JOIN&lt;/code&gt; returns only rows with matching values in both tables.&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="nv"&gt;`SELECT
    p.patient_id,
    p.patient_name,
    v.visit_id,
    v.visit_date
FROM patients p
INNER JOIN visits v
    ON p.patient_id = v.patient_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns patients who have matching visit records. Patients without recorded visits are excluded.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 &lt;code&gt;LEFT JOIN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;LEFT JOIN&lt;/code&gt; returns every row from the left table, together with matching rows from the right table.&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="nv"&gt;`SELECT
    p.patient_id,
    p.patient_name,
    v.visit_id,
    v.visit_date
FROM patients p
LEFT JOIN visits v
    ON p.patient_id = v.patient_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All registered patients appear, even if they have never visited the hospital. For patients without matching visits, the visit columns contain &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To find patients without recorded 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="nv"&gt;`SELECT
    p.patient_id,
    p.patient_name
FROM patients p
LEFT JOIN visits v
    ON p.patient_id = v.patient_id
WHERE v.visit_id IS NULL;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an anti-join: it finds records in one table that have no match in another.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 &lt;code&gt;RIGHT JOIN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;RIGHT JOIN&lt;/code&gt; retains every row from the right table.&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="nv"&gt;`SELECT
    p.patient_name,
    v.visit_id,
    v.visit_date
FROM patients p
RIGHT JOIN visits v
    ON p.patient_id = v.patient_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every visit is retained, along with matching patient information.&lt;/p&gt;

&lt;p&gt;Because the supplied schema has a foreign key from &lt;code&gt;visits.patient_id&lt;/code&gt; to &lt;code&gt;patients.patient_id&lt;/code&gt;, unmatched visit records should not normally exist.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;RIGHT JOIN&lt;/code&gt; can often be rewritten as a &lt;code&gt;LEFT JOIN&lt;/code&gt; by reversing the order of the tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 &lt;code&gt;FULL OUTER JOIN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;FULL OUTER JOIN&lt;/code&gt; retains all rows from both tables.&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="nv"&gt;`SELECT
    p.patient_id,
    p.patient_name,
    v.visit_id,
    v.visit_date
FROM patients p
FULL OUTER JOIN visits v
    ON p.patient_id = v.patient_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Matching records appear together. Patients without visits also appear, and unmatched visits would appear with missing patient details.&lt;/p&gt;

&lt;p&gt;This can help reconcile records from different systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.5 &lt;code&gt;CROSS JOIN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;CROSS JOIN&lt;/code&gt; creates every possible combination of rows from two inputs.&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="nv"&gt;`SELECT
    d.department_name,
    shifts.shift_name
FROM department d
CROSS JOIN (
    VALUES
        ('Morning'),
        ('Evening'),
        ('Night')
) AS shifts(shift_name);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are four departments, this query produces 12 department-and-shift combinations.&lt;/p&gt;

&lt;p&gt;This can help create a template for planning staffing across departments and shifts. Be careful, because cross joins can produce very large result sets.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6 &lt;code&gt;SELF JOIN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A self join joins a table to itself.&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="nv"&gt;`SELECT
    v1.patient_id,
    v1.visit_id AS earlier_visit_id,
    v1.visit_date AS earlier_visit_date,
    v2.visit_id AS later_visit_id,
    v2.visit_date AS later_visit_date
FROM visits v1
JOIN visits v2
    ON v1.patient_id = v2.patient_id
   AND (
        v1.visit_date &amp;lt; v2.visit_date
        OR (
            v1.visit_date = v2.visit_date
            AND v1.visit_id &amp;lt; v2.visit_id
        )
   )
ORDER BY v1.patient_id, v1.visit_date, v2.visit_date;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The aliases &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt; represent two different uses of the same table.&lt;/p&gt;

&lt;p&gt;The query pairs distinct visits belonging to the same patient, with the earlier visit appearing first.&lt;/p&gt;

&lt;p&gt;This can help analyze repeat visits and patterns of hospital attendance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick comparison of joins
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Join&lt;/th&gt;
&lt;th&gt;What it returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INNER JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matching rows only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All left-side rows and matching right-side rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RIGHT JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All right-side rows and matching left-side rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FULL OUTER JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All rows from both sides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CROSS JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every possible combination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SELF JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rows compared with other rows in the same table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key question when choosing a join is: Which records must remain in my result, even if a match is missing?&lt;/p&gt;

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

&lt;p&gt;What is a CTE?&lt;/p&gt;

&lt;p&gt;A Common Table Expression is a named result defined using &lt;code&gt;WITH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;CTEs make complex SQL queries easier to read by dividing them into smaller steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Basic CTE
&lt;/h3&gt;

&lt;p&gt;Let's identify patients who have visited the hospital more than once.&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="nv"&gt;`WITH patient_visit_counts AS (
    SELECT
        patient_id,
        COUNT(*) AS total_visits
    FROM visits
    GROUP BY patient_id
)
SELECT
    patient_id,
    total_visits
FROM patient_visit_counts
WHERE total_visits &amp;gt; 1
ORDER BY total_visits DESC;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CTE calculates each patient's visit count. The outer query filters the result to patients with more than one visit.&lt;/p&gt;

&lt;p&gt;3.2 Multiple CTEs&lt;/p&gt;

&lt;p&gt;You can define multiple CTEs in the same query.&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="nv"&gt;`WITH doctor_visit_counts AS (
    SELECT
        doctor_id,
        COUNT(*) AS total_visits
    FROM visits
    GROUP BY doctor_id
),
doctor_details AS (
    SELECT
        doctor_id,
        doctor_name,
        doctor_specialty
    FROM doctors
)
SELECT
    dd.doctor_name,
    dd.doctor_specialty,
    COALESCE(dvc.total_visits, 0) AS total_visits
FROM doctor_details dd
LEFT JOIN doctor_visit_counts dvc
    ON dd.doctor_id = dvc.doctor_id
ORDER BY total_visits DESC, dd.doctor_name;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first CTE calculates each doctor's visit count. The second selects the doctor information we need. The final query joins the results and displays zero for doctors without visits.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COALESCE()&lt;/code&gt; returns the first non-NULL value from its arguments.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Recursive CTEs
&lt;/h3&gt;

&lt;p&gt;A recursive CTE references itself to build a result iteratively.&lt;/p&gt;

&lt;p&gt;The hospital schema doesn't contain a hierarchical department relationship, but we can demonstrate recursion by generating a calendar.&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="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;visit_calendar&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;`SELECT DATE '2026-06-01' AS calendar_date

    UNION ALL

    SELECT calendar_date + 1
    FROM visit_calendar
    WHERE calendar_date &amp;lt; DATE '2026-06-15'
)
SELECT calendar_date
FROM visit_calendar
ORDER BY calendar_date;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The anchor query starts on June 1. The recursive query adds one day at a time until June 15.&lt;/p&gt;

&lt;p&gt;The result contains 15 consecutive dates.&lt;/p&gt;

&lt;p&gt;We can then combine the calendar with hospital 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;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;visit_calendar&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;`SELECT DATE '2026-06-01' AS calendar_date

    UNION ALL

    SELECT calendar_date + 1
    FROM visit_calendar
    WHERE calendar_date &amp;lt; DATE '2026-06-15'
)
SELECT
    c.calendar_date,
    COUNT(v.visit_id) AS total_visits
FROM visit_calendar c
LEFT JOIN visits v
    ON v.visit_date = c.calendar_date
GROUP BY c.calendar_date
ORDER BY c.calendar_date;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query returns every date in the period, including dates with zero visits.&lt;/p&gt;

&lt;p&gt;The dates are illustrative. Change them to match the period you need to analyze.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 CTEs with joins and aggregations
&lt;/h3&gt;

&lt;p&gt;CTEs are useful when we need to calculate totals before joining them to descriptive tables.&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;patient_billing&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nv"&gt;` SELECT
        patient_id,
        SUM(total_bill_kes) AS total_billed_kes
    FROM visits
    GROUP BY patient_id
)
SELECT
    p.patient_id,
    p.patient_name,
    COALESCE(pb.total_billed_kes, 0) AS total_billed_kes
FROM patients p
LEFT JOIN patient_billing pb
    ON p.patient_id = pb.patient_id
ORDER BY total_billed_kes DESC;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CTE calculates each patient's total recorded billing. The final query joins those totals to the patient records.&lt;/p&gt;

&lt;p&gt;Patients without visits receive a total of zero. These amounts represent recorded bills, not necessarily payments collected.&lt;/p&gt;

&lt;p&gt;3.5 CTEs vs. subqueries&lt;/p&gt;

&lt;p&gt;A subquery is a query nested inside another query. A CTE is a named result defined with &lt;code&gt;WITH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's a subquery:&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="nv"&gt;`SELECT *
FROM (
    SELECT
        patient_id,
        COUNT(*) AS total_visits
    FROM visits
    GROUP BY patient_id
) AS patient_counts
WHERE total_visits &amp;gt; 1;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the equivalent CTE:&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;patient_counts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nv"&gt;` SELECT
        patient_id,
        COUNT(*) AS total_visits
    FROM visits
    GROUP BY patient_id
)
SELECT *
FROM patient_counts
WHERE total_visits &amp;gt; 1;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both return patients with more than one visit.&lt;/p&gt;

&lt;p&gt;CTEs often make multi-step queries easier to understand, while subqueries can be convenient for short, localized calculations.&lt;/p&gt;

&lt;p&gt;A CTE isn't automatically faster. PostgreSQL's query planner determines how eligible CTEs and subqueries are executed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SQL Subqueries&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What is a subquery?&lt;/p&gt;

&lt;p&gt;A subquery is a query inside another SQL query.&lt;/p&gt;

&lt;p&gt;It can return a single value, a set of values, or a table-like result, depending on how it's written.&lt;/p&gt;

&lt;p&gt;4.1 Scalar subqueries&lt;/p&gt;

&lt;p&gt;A scalar subquery returns one row and one column.&lt;/p&gt;

&lt;p&gt;Let's find visits with bills above the overall 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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    total_bill_kes
FROM visits
WHERE total_bill_kes &amp;gt; (
    SELECT AVG(total_bill_kes)
    FROM visits
)
ORDER BY total_bill_kes DESC;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query calculates the average bill. The outer query returns visits whose bills exceed that average.&lt;/p&gt;

&lt;p&gt;If a scalar subquery returns more than one row, PostgreSQL raises an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Non-correlated subqueries
&lt;/h3&gt;

&lt;p&gt;A non-correlated subquery doesn't reference columns from the outer query.&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="nv"&gt;`SELECT
    patient_id,
    patient_name
FROM patients
WHERE patient_id IN (
    SELECT patient_id
    FROM visits
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query returns patient IDs from &lt;code&gt;visits&lt;/code&gt;. The outer query retrieves the matching patient records.&lt;/p&gt;

&lt;p&gt;This lists patients who have visited the hospital.&lt;/p&gt;

&lt;p&gt;4.3 Correlated subqueries&lt;/p&gt;

&lt;p&gt;A correlated subquery refers to a column from the outer query.&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="nv"&gt;`SELECT
    p.patient_id,
    p.patient_name
FROM patients p
WHERE EXISTS (
    SELECT 1
    FROM visits v
    WHERE v.patient_id = p.patient_id
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subquery checks whether a matching visit exists for each patient.&lt;/p&gt;

&lt;p&gt;This identifies patients with at least one recorded visit.&lt;/p&gt;

&lt;p&gt;4.4 Nested subqueries&lt;/p&gt;

&lt;p&gt;Nested subqueries contain another subquery inside them.&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="nv"&gt;`SELECT
    patient_id,
    patient_name
FROM patients
WHERE patient_id IN (
    SELECT patient_id
    FROM visits
    WHERE department_id IN (
        SELECT department_id
        FROM department
        WHERE department_name = 'Cardiology'
    )
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The innermost query finds the Cardiology department. The next query finds patients with visits in that department. The outer query retrieves their details.&lt;/p&gt;

&lt;p&gt;4.5 Using &lt;code&gt;IN&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IN&lt;/code&gt; checks whether a value matches one of the values returned by a subquery.&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="nv"&gt;`SELECT
    patient_id,
    patient_name
FROM patients
WHERE patient_id IN (
    SELECT patient_id
    FROM visits
    WHERE follow_up_required = 'Yes'
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query returns patients with at least one visit marked as requiring follow-up.&lt;/p&gt;

&lt;p&gt;The value &lt;code&gt;'Yes'&lt;/code&gt; assumes that this is how the database stores the field. Use the actual value from your dataset if it differs.&lt;/p&gt;

&lt;p&gt;4.6 Using &lt;code&gt;EXISTS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXISTS&lt;/code&gt; checks whether a subquery returns at least one 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="nv"&gt;`SELECT
    d.doctor_id,
    d.doctor_name
FROM doctors d
WHERE EXISTS (
    SELECT 1
    FROM visits v
    WHERE v.doctor_id = d.doctor_id
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns doctors with recorded visits.&lt;/p&gt;

&lt;p&gt;To find doctors without visits, use &lt;code&gt;NOT EXISTS&lt;/code&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="nv"&gt;`SELECT
    d.doctor_id,
    d.doctor_name
FROM doctors d
WHERE NOT EXISTS (
    SELECT 1
    FROM visits v
    WHERE v.doctor_id = d.doctor_id
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EXISTS&lt;/code&gt; is useful when the existence of a related record matters more than the values it contains.&lt;/p&gt;

&lt;p&gt;4.7 Using &lt;code&gt;ANY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ANY&lt;/code&gt; makes a comparison against a set of values. The comparison succeeds if it is true for at least one value.&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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    total_bill_kes
FROM visits
WHERE total_bill_kes &amp;gt; ANY (
    SELECT total_bill_kes
    FROM visits
    WHERE triage_level = 'Red'
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query returns visits whose bills exceed at least one non-NULL bill amount from a Red-triage visit.&lt;/p&gt;

&lt;p&gt;This is a financial comparison, not a clinical judgment about severity.&lt;/p&gt;

&lt;p&gt;4.8 Using &lt;code&gt;ALL&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ALL&lt;/code&gt; requires the comparison to be true for every value returned by the subquery.&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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    total_bill_kes
FROM visits
WHERE total_bill_kes &amp;gt; ALL (
    SELECT total_bill_kes
    FROM visits
    WHERE triage_level = 'Red'
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns visits whose bills exceed every non-NULL bill amount in the Red-triage group, provided NULL values do not make the comparison unknown.&lt;/p&gt;

&lt;p&gt;The difference between &lt;code&gt;ANY&lt;/code&gt; and &lt;code&gt;ALL&lt;/code&gt; is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt; ANY&lt;/code&gt; means greater than at least one value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt; ALL&lt;/code&gt; means greater than every value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the subquery returns no rows, &lt;code&gt;ANY&lt;/code&gt; evaluates to false and &lt;code&gt;ALL&lt;/code&gt; evaluates to true under SQL's logical rules. NULL values can also affect comparisons.&lt;/p&gt;

&lt;p&gt;4.9 Subqueries in the &lt;code&gt;SELECT&lt;/code&gt; clause&lt;/p&gt;

&lt;p&gt;A scalar subquery can calculate a value for each row in the outer query.&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="nv"&gt;`SELECT
    p.patient_id,
    p.patient_name,
    (
        SELECT COUNT(*)
        FROM visits v
        WHERE v.patient_id = p.patient_id
    ) AS total_visits
FROM patients p
ORDER BY p.patient_id;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each patient appears with their visit count. Patients without visits receive zero because &lt;code&gt;COUNT(*)&lt;/code&gt; returns zero when no rows match.&lt;/p&gt;

&lt;p&gt;4.10 Subqueries in the &lt;code&gt;FROM&lt;/code&gt; clause&lt;/p&gt;

&lt;p&gt;A subquery in &lt;code&gt;FROM&lt;/code&gt; acts like a temporary table for the outer query. PostgreSQL requires it to have an alias.&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="nv"&gt;`SELECT
    patient_summary.patient_id,
    patient_summary.total_billing_kes
FROM (
    SELECT
        patient_id,
        SUM(total_bill_kes) AS total_billing_kes
    FROM visits
    GROUP BY patient_id
) AS patient_summary
WHERE patient_summary.total_billing_kes &amp;gt; 50000
ORDER BY patient_summary.total_billing_kes DESC;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query calculates total billing per patient. The outer query filters those totals to amounts above KES 50,000.&lt;/p&gt;

&lt;p&gt;The threshold is illustrative and can be changed.&lt;/p&gt;

&lt;p&gt;4.11 Subqueries in the &lt;code&gt;WHERE&lt;/code&gt; clause&lt;/p&gt;

&lt;p&gt;A subquery in &lt;code&gt;WHERE&lt;/code&gt; filters rows based on another query's result.&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="nv"&gt;`SELECT
    visit_id,
    patient_id,
    total_bill_kes
FROM visits
WHERE total_bill_kes &amp;gt; (
    SELECT AVG(total_bill_kes)
    FROM visits
);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query calculates the average bill, and the outer query returns visits with bills above that average.&lt;/p&gt;

&lt;p&gt;Subqueries in &lt;code&gt;WHERE&lt;/code&gt; can work with operators such as &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;IN&lt;/code&gt;, &lt;code&gt;ANY&lt;/code&gt;, &lt;code&gt;ALL&lt;/code&gt;, &lt;code&gt;EXISTS&lt;/code&gt;, and &lt;code&gt;NOT EXISTS&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;*&lt;em&gt;Bringing Everything Together
*&lt;/em&gt;
The real value of SQL comes from combining these techniques.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example 1: Rank departments by billing
&lt;/h3&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;department_billing&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;`SELECT
        d.department_id,
        d.department_name,
        SUM(v.total_bill_kes) AS total_billing_kes
    FROM department d
    JOIN visits v
        ON d.department_id = v.department_id
    GROUP BY d.department_id, d.department_name
)
SELECT
    department_name,
    total_billing_kes,
    RANK() OVER (
        ORDER BY total_billing_kes DESC
    ) AS billing_rank
FROM department_billing
ORDER BY billing_rank, department_name;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combines a join, aggregation, a CTE, and a window function.&lt;/p&gt;

&lt;p&gt;The result shows each department's total recorded billing and its rank.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Find visits above their department's average bill
&lt;/h3&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;department_averages&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nv"&gt;` SELECT
        department_id,
        AVG(total_bill_kes) AS average_bill_kes
    FROM visits
    GROUP BY department_id
)
SELECT
    v.visit_id,
    p.patient_name,
    d.department_name,
    v.total_bill_kes,
    ROUND(da.average_bill_kes, 2) AS average_department_bill
FROM visits v
JOIN patients p
    ON v.patient_id = p.patient_id
JOIN department d
    ON v.department_id = d.department_id
JOIN department_averages da
    ON v.department_id = da.department_id
WHERE v.total_bill_kes &amp;gt; da.average_bill_kes
ORDER BY v.total_bill_kes DESC;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CTE calculates the average bill for each department. The joins add patient and department details. Finally, the &lt;code&gt;WHERE&lt;/code&gt; clause selects visits above their department's average.&lt;/p&gt;

&lt;p&gt;This could help the finance department identify bills for further review, although a higher bill does not automatically indicate an error.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Final Thoughts&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Learning SQL isn't just about memorizing syntax. It's about understanding how to ask questions of data and choosing the right technique to answer them.&lt;/p&gt;

&lt;p&gt;Window functions calculate rankings, totals, averages, and comparisons while keeping individual rows. Joins combine related tables while controlling which unmatched records remain. CTEs break complex queries into named, understandable steps. Subqueries use one query's result inside another query.&lt;/p&gt;

&lt;p&gt;Our hospital database provides practical examples of how these concepts can support patient visit analysis, doctor workload reporting, departmental billing comparisons, and follow-up identification.&lt;/p&gt;

</description>
      <category>database</category>
      <category>learning</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
