<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Abdullah Rufai - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Abdullah Rufai - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 18 Aug 2026 02:09:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/abdullah797/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Work with Subqueries in SQL ]]>
                </title>
                <description>
                    <![CDATA[ Whenever you see a query nested inside another query in SQL, that's a subquery. A subquery is also known as an inner query while the one that contains it is called the main or outer query. Subqueries  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-work-with-subqueries-in-sql/</link>
                <guid isPermaLink="false">6a837db5b64d34f31873c306</guid>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ subquery ]]>
                    </category>
                
                    <category>
                        <![CDATA[ correlated subquery ]]>
                    </category>
                
                    <category>
                        <![CDATA[ non-correlated subquery ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abdullah Rufai ]]>
                </dc:creator>
                <pubDate>Mon, 17 Aug 2026 21:31:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e9107adb-f183-460c-8978-6320ff1eadfe.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Whenever you see a query nested inside another query in SQL, that's a subquery. A subquery is also known as an inner query while the one that contains it is called the main or outer query.</p>
<p>Subqueries are used to provide the main query with additional data in the form of a derived column or derived table, or they can filter the rows returned by the main query.</p>
<p>Subqueries can be quite difficult to understand, especially for beginners who are just starting out in SQL. This article will help simplify this concept so that it is much easier to understand. By the end, you should be able to use subqueries more easily to solve problems.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-subqueries-work">How Subqueries Work</a></p>
<ul>
<li><a href="#heading-execution-order">Execution Order</a></li>
</ul>
</li>
<li><p><a href="#heading-types-of-subqueries">Types of Subqueries</a></p>
<ul>
<li><p><a href="#heading-non-correlated-subqueries">Non-correlated Subqueries</a></p>
</li>
<li><p><a href="#heading-correlated-subqueries">Correlated Subqueries</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites:</h3>
<p>Subqueries are an advanced SQL concept, so it's important to have a solid understanding of the basics of SQL: SELECT, FROM, WHERE, JOINS, the CASE statement, and the proper order for query execution.</p>
<h2 id="heading-how-subqueries-work">How Subqueries Work</h2>
<p>Let's start by considering an example of a query with a subquery.</p>
<pre><code class="language-sql">SELECT *
FROM registration
WHERE student_id 
                IN (SELECT 
                        id 
                    FROM student
                    WHERE location = 'Lagos')
</code></pre>
<p>The above query has two parts: the main query and the subquery.</p>
<p>This is the main query:</p>
<pre><code class="language-sql">SELECT *
FROM registration
WHERE student_id 
                IN (...)
</code></pre>
<p>Notice that there is currently nothing in the brackets of the main query.</p>
<p>The part of the query that's enclosed in the brackets is the subquery. It's used to filter the rows returned by the main query. Let's look at the subquery code now:</p>
<pre><code class="language-sql">SELECT 
    id 
FROM student 
WHERE location = 'Lagos'
</code></pre>
<h3 id="heading-execution-order">Execution Order</h3>
<p>When you run the whole query in your management system like this:</p>
<pre><code class="language-sql">SELECT *
FROM registration
WHERE student_id 
                IN (SELECT 
                        id 
                    FROM student
                    WHERE location = 'Lagos')
</code></pre>
<p>You'll get the result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/de190883-4682-466e-973e-7a5796e36435.jpg" alt="The result is showing registration details of all students from Lagos" style="display:block;margin:0 auto" width="472" height="174" loading="lazy">

<p>The result is showing the registration details of all students from Lagos. But to get this result, SQL follows an execution order which we'll discuss below.</p>
<p>When you execute the whole query, behind the scenes, the subquery is evaluated first:</p>
<pre><code class="language-sql">SELECT 
    id 
FROM student 
WHERE location = 'Lagos'
</code></pre>
<p>The subquery retrieves the IDs of students from Lagos from the student table. You get a result like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/83636a0e-747c-48c8-9bd5-272893dbd66c.jpg" alt="The result shows values returned by the subquery when executed outside the main query" style="display:block;margin:0 auto" width="237" height="194" loading="lazy">

<p>Behind the scenes, these <code>IDs</code> returned by the subquery are provided to the main query, transforming the query to look like this:</p>
<pre><code class="language-sql">SELECT *
FROM registration
WHERE student_id 
            IN('STU1', 'STU13','STU2','STU4','STU23','STU27')
</code></pre>
<p>The main query compares each value in its <code>student_id</code> column with the <code>IDs</code> returned by the subquery. If a match is found, it returns the registration details of that student. Otherwise, the record is ignored.</p>
<p>You might wonder, "Why not just retrieve the <code>IDs</code> from the student table and directly pass them to the main query instead of using a subquery?"</p>
<p>Well, that's hardcoding. While the query will work at the moment, later when there are new students and you rerun the query, you'll only get details of the old students for whom you manually passed <code>IDs</code> to the main query. It'll exclude the new ones.</p>
<p>The subquery approach is dynamic: it continuously queries the student table to ensure that the results are always current.</p>
<h2 id="heading-types-of-subqueries">Types of Subqueries</h2>
<p>There are two types of subqueries based on dependency: non-correlated (independent) and correlated (dependent).</p>
<h3 id="heading-non-correlated-subqueries">Non-correlated Subqueries</h3>
<p>These are subqueries that are independent of the main query. When executed outside the main query, they'll work. A good example is the one used in the query above.</p>
<pre><code class="language-sql">SELECT *
FROM registration
WHERE student_id 
                IN (SELECT 
                        id 
                    FROM student
                    WHERE location = 'Lagos')
</code></pre>
<p>It's important to note that subqueries, whether correlated or non-correlated, can appear in different parts of a SQL query. Where a subquery appears determines its role in the main query. It can function as a derived column, a derived table, or a filter.</p>
<p>Let's discuss the various parts of the main query where a subquery can appear and how they support the main query.</p>
<h4 id="heading-1-subquery-as-a-derived-column">1. Subquery as a derived column</h4>
<p>When you see a subquery in the <code>SELECT</code> statement, it's a derived column. A derived column is a column created by a query using values from other columns in a table. It's not stored in the database and exists only for the duration of the query.</p>
<p>For example, to calculate the percentage of total registrations for each course, you need the following three pieces of information in the same row:</p>
<ul>
<li><p>Course name</p>
</li>
<li><p>Registration count for each course (numerator)</p>
</li>
<li><p>Total registrations across all courses (denominator)</p>
</li>
</ul>
<p>To start, you can write a main query that returns the names of all courses and the registration count for each of them:</p>
<pre><code class="language-sql">SELECT
    course_name,
    COUNT(reg_id) AS registrations
FROM course AS l
LEFT JOIN registration AS r
ON l.id = r.course_id
GROUP BY course_name
</code></pre>
<p>Result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/48947843-b421-4737-900d-9bddf46e6b07.jpg" alt="The image shows each course name and its registration count" style="display:block;margin:0 auto" width="257" height="216" loading="lazy">

<p>The main query lists each course with its registration count. To calculate each course's percentage of total registrations, the total registrations for all courses must also be included in each row of the main query's result.</p>
<p>To achieve this, you can nest a subquery that returns the total number of registrations for all courses as a column to the main query, like this:</p>
<pre><code class="language-sql">SELECT COUNT(reg_id) FROM registration
</code></pre>
<p>When this subquery is executed outside the main query, it returns the result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/943509ea-6d1d-4906-b59e-e90962c72490.jpg" alt="The image shows the reult of the subquery, calculating total registrations for all courses." style="display:block;margin:0 auto" width="169" height="83" loading="lazy">

<p>30 is total number of registrations for all courses. Now, to make the subquery a column of the main query, first enclose it in brackets like this:</p>
<pre><code class="language-sql">(SELECT COUNT(reg_id) FROM registration)
</code></pre>
<p>Next, insert the subquery into the column list of the main query as shown below:</p>
<pre><code class="language-sql">SELECT
    course_name,
    COUNT(reg_id) AS regs,
    (SELECT COUNT(reg_id) FROM registration) AS total --subquery
FROM course AS l
LEFT JOIN registration AS r
ON l.id = r.course_id
GROUP BY course_name
</code></pre>
<p>When executed, it returns the following result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/2334a6fb-b2cd-428e-9d8f-afd7e5741f93.jpg" alt="The image shows all course names, registration count for each of them and total registrations for all courses." style="display:block;margin:0 auto" width="324" height="249" loading="lazy">

<p>In the table above, the main query returns the course names and their registration counts, aliased as <code>regs</code>. The subquery adds a <code>total</code> column containing the total number of registrations for all courses, which is 30. The value (30) appears on every row of the result.</p>
<p>With the course names, registration counts, and the total number of registrations across all courses in each row, we can now calculate the percentage of total registrations for each course.</p>
<p><strong>Formula:</strong></p>
<pre><code class="language-markdown">Percentage of Total = regs / total * 100
</code></pre>
<p>First, both the <code>regs</code> and <code>total</code> column have integer values. If divided directly, the result will be 0. To prevent this, always cast the numerator to the <code>FLOAT</code> data type using the <code>CAST()</code> function as shown below:</p>
<pre><code class="language-sql">-- casting numerator to float data type
CAST(
    COUNT(reg_id) 
     AS FLOAT)
</code></pre>
<p>After casting the numerator to <code>FLOAT</code>, proceed with the division like this:</p>
<pre><code class="language-sql">
CAST(COUNT(reg_id) AS FLOAT) -- Numerator

/ -- Divide sign

(SELECT COUNT(reg_id) FROM registration) -- Denominator

* 100 -- Convert to percentage
</code></pre>
<p>Let's bring it all together:</p>
<pre><code class="language-sql">SELECT
    course_name,
    CAST(COUNT(reg_id) AS FLOAT)/
    (SELECT 
        COUNT(reg_id) 
     FROM registration) * 100 AS percent_of_total
FROM course AS l
LEFT JOIN registration AS r
ON l.id = r.course_id
GROUP BY course_name
</code></pre>
<p>When the query above is executed, you get this result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/fd197d8e-16c4-4a1d-a967-1e7c69242c69.jpg" alt="The image shows course names and their percentage of total registrations." style="display:block;margin:0 auto" width="296" height="210" loading="lazy">

<p>The result shows all courses with their percentages of total registrations. To control the number of decimal places of the values in the <code>percent_of_total</code> column, we can use the SQL <code>ROUND()</code> function to round them to one decimal place. See the updated query below:</p>
<pre><code class="language-sql">SELECT
    course_name,
    ROUND(CAST(COUNT(reg_id) AS FLOAT)/
    (SELECT 
        COUNT(reg_id) 
     FROM registration) * 100,1) AS percent_of_total
FROM course AS l
LEFT JOIN registration AS r
ON l.id = r.course_id
GROUP BY course_name
</code></pre>
<p>Result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/0b452557-dc44-489b-9144-7c49c2ad1bc9.jpg" alt="The image shows course names and their percentage of total registrations." style="display:block;margin:0 auto" width="269" height="208" loading="lazy">

<p>Finally, we have the percentage contribution of each course to the total registrations.</p>
<h4 id="heading-2-subquery-as-a-derived-table">2. Subquery as a derived table</h4>
<p>When you see a subquery in the <code>FROM</code> clause, it's a derived table. A derived table is a temporary table created from the result of a query. It's not permanently stored in the database and exists only while the query runs.</p>
<p>As an example, let's say you want to write a query that returns the number of students by region.</p>
<p>But when the student table is queried using the query below:</p>
<pre><code class="language-sql">SELECT * FROM student
</code></pre>
<p>you discover it has no <code>region</code>, and instead it has a <code>state</code> column:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/763e25cf-0b24-4a3f-933a-53661a90dca7.jpg" alt="The image shows all column of the student table" style="display:block;margin:0 auto" width="634" height="234" loading="lazy">

<p>To solve this problem, you can leverage values in the <code>state</code> column to create a derived <code>region</code> column using the SQL <code>CASE</code> statement like this:</p>
<pre><code class="language-sql">SELECT
    *, -- all columns of the student table
 CASE
    WHEN location 
       IN('Abeokuta','Ibadan','Mokola','Iyana Ipaja','Lagos') 
    THEN 'West'

    WHEN location 
       IN('Anambra','Owerri','Enugu','Port Harcourt') 
    THEN 'East'

    WHEN location 
       IN('Abuja','Ilorin','Kaduna','Kano','Jos') 
    THEN 'North'
 END AS region -- a derived region column
FROM student
</code></pre>
<p>When the above query is executed, it returns the result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/f3691d80-d267-4062-b8e0-aaae3497e9a2.jpg" alt="The result shows all column from the student table with the region column created using the CASE statement." style="display:block;margin:0 auto" width="686" height="331" loading="lazy">

<p>The result now shows all column from the student table with the region column created using the <code>CASE</code> statement.</p>
<p>Now that we have a region column, let's convert the entire query into a table subquery.</p>
<p>First, enclose the whole query in brackets and give it an alias:</p>
<pre><code class="language-sql">(SELECT
    *, 
    CASE
        WHEN location 
           IN('Abeokuta','Ibadan','Mokola','Iyana Ipaja','Lagos') 
        THEN 'West'

        WHEN location 
           IN('Anambra','Owerri','Enugu','Port Harcourt') 
        THEN 'East'

        WHEN location 
           IN('Abuja','Ilorin','Kaduna','Kano','Jos') 
        THEN 'North'
    END AS region 
FROM student) AS data_prep -- aliased data_prep
</code></pre>
<p>To use <code>data_prep</code>, place the entire query, including the brackets and alias, into a <code>FROM</code> clause, just as you would with a regular table:</p>
<pre><code class="language-sql">FROM (SELECT
    *, 
    CASE
        WHEN location 
           IN('Abeokuta','Ibadan','Mokola','Iyana Ipaja','Lagos') 
        THEN 'West'

        WHEN location 
           IN('Anambra','Owerri','Enugu','Port Harcourt') 
        THEN 'East'

        WHEN location 
           IN('Abuja','Ilorin','Kaduna','Kano','Jos') 
        THEN 'North'
    END AS region 
FROM student) AS data_prep
</code></pre>
<p>From this moment, you can select any data from <code>data_prep</code>. It will work like a regular table.</p>
<p>Now, let's use data from <code>data_prep</code> to calculate the number of students by region:</p>
<pre><code class="language-sql">SELECT
    region,
    COUNT(id) AS students
FROM (SELECT
        *, 
        CASE
          WHEN location 
            IN('Abeokuta','Ibadan','Mokola','Iyana Ipaja','Lagos') 
          THEN 'West'

          WHEN location 
            IN('Anambra','Owerri','Enugu','Port Harcourt') 
          THEN 'East'

          WHEN location 
            IN('Abuja','Ilorin','Kaduna','Kano','Jos') 
          THEN 'North'
         END AS region 
        FROM student) AS data_prep 
GROUP BY region 
</code></pre>
<p>When the above query is executed, it returns the result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/9b8abd34-fc5d-4532-b4ee-9f96ab882ba2.jpg" alt="The result shows the count of students by region." style="display:block;margin:0 auto" width="157" height="109" loading="lazy">

<p>The result shows the count of students by region.</p>
<h4 id="heading-3-subquery-as-a-filter">3. Subquery as a filter</h4>
<p>When you see a subquery in the <code>WHERE</code> clause, it's a filter to the main query. There are different group of operators you can use to filter the main query.</p>
<p><strong>Logical operators</strong></p>
<p>These include <code>IN</code>, <code>ANY</code>, and <code>ALL</code>. You can use them when a subquery returns multiple values, allowing you to compare column values in your dataset against those returned by the subquery.</p>
<p>You saw an example using <code>IN</code> earlier, so we'll move on to the remaining two.</p>
<p>Scenario: Retrieve records of all male students who are older than any female student. Below is how to achieve this using <code>ANY</code>:</p>
<pre><code class="language-sql">SELECT 
	*
FROM student 
WHERE gender = 'Male' 
      AND age &gt; ANY (SELECT 
                       DISTINCT age 
                     FROM student 
                     WHERE gender = 'Female')
</code></pre>
<p>The above query has two parts: the main query and subquery.</p>
<p>Here's the main query:</p>
<pre><code class="language-sql">SELECT 
	*
FROM student 
WHERE gender = 'Male' 
      AND age &gt; ANY (...)
</code></pre>
<p>And here's the subquery:</p>
<pre><code class="language-sql">SELECT 
    DISTINCT age 
FROM student 
WHERE gender = 'Female'
</code></pre>
<p>When you execute the whole query in your management system, it looks like this:</p>
<pre><code class="language-sql">SELECT 
	*
FROM student 
WHERE gender = 'Male' 
      AND age &gt; ANY (SELECT 
                       DISTINCT age 
                     FROM student 
                     WHERE gender = 'Female')
</code></pre>
<p>Behind the scenes, the subquery is evaluated first:</p>
<pre><code class="language-sql">SELECT 
   DISTINCT age 
FROM student 
WHERE gender = 'Female'
</code></pre>
<p>The subquery returns distinct ages of female students from the student table:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/df782b01-f781-4a5c-a91a-9298405bdeaa.jpg" alt="The image shows the result of the above subquery; distinct ages of female students." style="display:block;margin:0 auto" width="168" height="181" loading="lazy">

<p>The result of the subquery is passed to the main query, making the entire query appear as shown below, behind the scenes:</p>
<pre><code class="language-sql">SELECT 
	*
FROM student 
WHERE gender = 'Male' 
      AND age &gt; ANY (23, 25, 26, 27, 28, 29, 30)
</code></pre>
<p>Now, the age of each male student is compared to those of the female students returned by the subquery. The main query will return only those male students whose age is greater than at least one of the female students' ages:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/9e33f7f5-8984-492b-bb84-a6f41805c109.jpg" alt="The result shows the details of male students older than at least one female student." style="display:block;margin:0 auto" width="617" height="194" loading="lazy">

<p>The result shows the details of male students older than at least one female student.</p>
<p>In the query above, the <code>ANY</code> operator needs the male student to be older than at least one of the female students for his record to be returned by the main query.</p>
<p>With <code>ALL</code>, things work a bit differently. The male student has to be older than <strong>all</strong> female students for his record to be returned. See the query example using <code>ALL</code> below:</p>
<pre><code class="language-sql">SELECT 
	*
FROM student 
WHERE gender = 'Male' 
      AND age &gt; ALL (SELECT 
                       DISTINCT age 
                     FROM student 
                     WHERE gender = 'Female')
</code></pre>
<p>Result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/3c585712-610d-4a4b-b1d3-9e0d2535a1aa.jpg" alt="The query returned the only male student older than all the female students." style="display:block;margin:0 auto" width="595" height="75" loading="lazy">

<p>The query returned the only male student older than all the female students.</p>
<p><strong>Comparison operators</strong></p>
<p>These are used to compare column values with a single scalar value returned by the subquery. They include:</p>
<ol>
<li><p>Equals to (==)</p>
</li>
<li><p>Greater than (&gt;)</p>
</li>
<li><p>Greater than or equal to (&gt;=)</p>
</li>
<li><p>Less than (&lt;)</p>
</li>
<li><p>Less than or equal to (=&lt;)</p>
</li>
<li><p>Not equal to (&lt;&gt; or !=)</p>
</li>
</ol>
<p>For example, say you want to retrieve the details of students whose ages are greater than the average age of all students. See the query below:</p>
<pre><code class="language-sql">SELECT *
FROM student
WHERE age &gt; (SELECT AVG(age) FROM student)
</code></pre>
<p>When executed, the query returns the details of student whose age is greater than the average age of all students:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/875185ed-80e9-4f4d-8706-ef83d36c9c43.jpg" alt="The image shows the records of student whose age is greater than the average age of all students" style="display:block;margin:0 auto" width="629" height="309" loading="lazy">

<p>Let's talk about the execution order of the query.</p>
<p>First, when you execute the whole query, behind the scenes, the subquery gets evaluated first:</p>
<pre><code class="language-sql">SELECT AVG(age) FROM student
</code></pre>
<p>The subquery returns a single value of 25 which is the average age of all students:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/c00a6312-2002-4e55-9b53-7c4f1341cc3b.jpg" alt="The image shows the average age of all student" style="display:block;margin:0 auto" width="152" height="50" loading="lazy">

<p>Once the subquery returns the value 25, behind the scenes, the whole query looks like the one below:</p>
<pre><code class="language-sql">SELECT * FROM student WHERE age &gt; 25
</code></pre>
<p>The main query compares each student's age in the student table with the 25 returned by the subquery. If a student's age is greater than 25, their record is included in the result. Otherwise, it's ignored.</p>
<p>Check out the result of the main query below. Notice that all the values in the age column exceed 25.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/c4dd8a07-8c30-45bf-bcc4-e0fa48a8f32c.jpg" alt="The image shows the records of student whose age is greater than the average age." style="display:block;margin:0 auto" width="629" height="309" loading="lazy">

<p>You can also change the comparison operator depending on what you want to see. Below are some use cases of other comparison operators.</p>
<pre><code class="language-sql">-- retrieve students whose age is greater or equal to the average age of all students

SELECT *
FROM student
WHERE age &gt;= (SELECT AVG (age) FROM student)

-- retrieve students whose age is less than the average age of all students

SELECT *
FROM student
WHERE age &lt; (SELECT AVG (age) FROM student)

-- retrieve students whose age is less than or equal the average age of all students

SELECT *
FROM student
WHERE age =&lt; (SELECT AVG (age) FROM student)

-- retrieve students whose age is equal to the average age of all students

SELECT *
FROM student
WHERE age = (SELECT AVG (age) FROM student)

-- retrieve student whose age is not equal to the average age of all students

SELECT *
FROM student
WHERE age &lt;&gt; (SELECT AVG (age) FROM student)

--Alternatively, you can also use !=, which stands for not equal to

SELECT *
FROM student
WHERE age != (SELECT AVG (age) FROM student)
</code></pre>
<h3 id="heading-correlated-subqueries">Correlated Subqueries</h3>
<p>These types of subqueries depend on a value of the main query in order to work. If executed outside the main query, it won't work.</p>
<p>As an example, say you want to count the number of registrations for each course using a correlated subquery.</p>
<p>First, let's begin with a query that includes a non-correlated subquery as a column, returning the total number of registrations for all courses.</p>
<pre><code class="language-sql">SELECT 
	course_name,
	(SELECT COUNT(reg_id) FROM registration) AS regs --subquery
FROM course 
</code></pre>
<p>When executed, the main query retrieves the name of each course from the <code>course</code> table, while the subquery calculates the total number of registrations from the <code>registration</code> table. The subquery's result is returned as a derived column and displayed alongside the <code>course_name</code>. See the result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/099cae53-1128-4620-a454-c371e588c7ca.jpg" alt="The image shows the course names and the total registrations for all courses." style="display:block;margin:0 auto" width="203" height="178" loading="lazy">

<p>But the result above isn't what we need. We actually want the registration counts for each course to appear next to it, not the total registrations for all courses.</p>
<p>To address this, we'll modify the initial non-correlated subquery to be filtered based on a value from the main query, transforming it into a correlated subquery.</p>
<p>See the non-correlated subquery here:</p>
<pre><code class="language-sql">(SELECT COUNT(reg_id) FROM registration) AS regs
</code></pre>
<p>To update the above subquery to a correlated subquery, do the following.</p>
<p>First, copy the whole query used earlier and alias the tables of the main and subquery:</p>
<pre><code class="language-sql">-- Alias the course table l and registrations as r
SELECT 
	course_name,
	(SELECT COUNT(reg_id) FROM registration AS r) AS regs 
FROM course AS l
</code></pre>
<p>Next, you connect the table of the main query to that of the subquery using the <code>id</code> column from the course table and the <code>course_id</code> column from the registration table, like this:</p>
<pre><code class="language-sql">SELECT 
    COUNT(reg_id) 
FROM registration AS r 
WHERE r.course_id = l.id -- the connection
</code></pre>
<p>Now, the above subquery is a correlated subquery. If executed in isolation of the main query, you'll get the result below.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/f0abf249-156b-4185-9f99-52d02779a18d.jpg" alt="The image shows error returned when the correlated subquery is executed outside the main query" style="display:block;margin:0 auto" width="433" height="109" loading="lazy">

<p>This error simply means that the <code>l.id</code> in the <code>WHERE</code> clause of the subquery is not recognized because it comes from the main query.</p>
<p>For it to work, it must be nested as a column to the main query. See the whole query below:</p>
<pre><code class="language-sql">SELECT 
    course_name,
    (SELECT 
        COUNT(reg_id) 
     FROM registration AS r
     WHERE r.course_id = l.id) AS regs -- correlated subquery
FROM course AS l
</code></pre>
<p>When executed, it returns the result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/7c34ef18-e77a-4dc9-b5cc-cc26ec3126d2.jpg" alt="The image shows the name of courses and the registration count for each of them" style="display:block;margin:0 auto" width="195" height="176" loading="lazy">

<p>The result above shows the name of courses and the registration count for each of them.</p>
<p>Now, let's understand the execution order of the query.</p>
<p>When the whole query is executed, behind the scenes, the main query gets evaluated first:</p>
<pre><code class="language-sql">SELECT 
    course_name 
FROM course AS l
</code></pre>
<p>The main query doesn't run just once and return all the course names. Instead, it returns the course names one at a time. Also, for each course name returned by the main query, the subquery gets executed for that particular course.</p>
<p>For instance, during the first iteration of the whole query, the first course name the main query returned was data analytics and its ID is <code>CS01</code>. The main query then provides <code>CS01</code> to the subquery, making the subquery to look like the one below behind the scenes:</p>
<pre><code class="language-sql">SELECT 
    COUNT(reg_id) 
FROM registration AS r
WHERE r.course_id = CS01 -- ID for data analytics
</code></pre>
<p>The subquery above counts only the registrations where the course_id matches the value provided by the main query, in this case, CS01. The registration count is then returned alongside the course name.</p>
<p>In the next iteration,</p>
<ul>
<li><p>The main query retrieves a course name and provides its <code>ID</code> to the subquery.</p>
</li>
<li><p>The subquery counts the registrations for that specific course.</p>
</li>
<li><p>The registration count is displayed alongside the course name.</p>
</li>
</ul>
<p>This process continues until all courses in the main query table have been processed. Essentially, the number of courses in the table determines the number of iterations the query will perform.</p>
<p>See final result below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/1107803b-e4bc-476d-b540-612b84086553.png" alt="The image shows the name of courses and the registration count for each of them" style="display:block;margin:0 auto" width="212" height="182" loading="lazy">

<p>The final result displays all course names along with their registration counts. Courses with no registrations yet are shown with a count of 0.</p>
<h4 id="heading-the-exists-operator">The EXISTS operator</h4>
<p><code>EXISTS</code> checks whether a matching row exists in a related table and returns <code>TRUE</code> if one is found and <code>FALSE</code> if none is found.</p>
<p>For example, if you want to identify courses with at least a registration:</p>
<pre><code class="language-sql">SELECT 
    course_name
FROM course AS l
WHERE EXISTS (SELECT 1
              FROM registration AS r
              WHERE r.course_id = l.id)
</code></pre>
<p>The above query is made up of a main query and a correlated subquery.</p>
<p>Here's the main query:</p>
<pre><code class="language-sql">SELECT 
    course_name
FROM course AS l
WHERE EXISTS (...)
</code></pre>
<p>In the main query above, notice that there's no column name before the <code>EXISTS</code> operator. This is because the <code>EXISTS</code> doesn't compare a value in a column of the main query to those returned by the subquery. Instead, <code>EXISTS</code> checks whether the subquery returns at least one row. It evaluates to <code>TRUE</code> if a row is found and <code>FALSE</code> if no row is found:</p>
<pre><code class="language-sql">SELECT 1
FROM registration AS r
WHERE r.course_id = l.id
</code></pre>
<p>The subquery's role is to check the registration table to see if the <code>ID</code> provided by the main query has any registrations. In the subquery's SELECT statement, you'll notice that no columns are listed, just a placeholder of 1. This is used because the actual value returned by the subquery is irrelevant. What matters is whether a row exists or not.</p>
<p>Now that we've discussed the anatomy of the query, let's dive into understanding it execution.</p>
<p>Let's look at the whole query once again:</p>
<pre><code class="language-sql">SELECT 
    course_name
FROM course AS l
WHERE EXISTS (SELECT 1
              FROM registration AS r
              WHERE r.course_id = l.id)
</code></pre>
<p>Once the whole query above is executed, behind the scenes, its evaluation starts from the <code>FROM</code> clause:</p>
<pre><code class="language-sql">FROM course AS l
</code></pre>
<p>Then, the first row of the course table is considered, and the <code>EXISTS</code> checks whether a matching row exists in the subquery:</p>
<pre><code class="language-sql">WHERE EXISTS (SELECT 1
              FROM registration AS r
              WHERE r.course_id = l.id)
</code></pre>
<p>Now, assume the first row it considered from the course table is from data analytics and its <code>ID</code> is <code>CS01</code>. This <code>ID</code> will be passed to the subquery, making the subquery look like the one below:</p>
<pre><code class="language-sql">SELECT 1
FROM registration AS r
WHERE r.course_id = CS01 --- ID for data analytics
</code></pre>
<p>The value, <code>CS01</code> is compared with values in the <code>course_id</code> column of the registration table. If a matching row is found, <code>EXISTS</code> evaluates to <code>TRUE</code> and data analytics will be included among the courses returned by the main query.</p>
<p>See how <code>EXISTS</code> evaluates the condition below:</p>
<pre><code class="language-sql">EXISTS → TRUE → course included
</code></pre>
<p>But if an <code>ID</code> supplied to the subquery finds no matching row in the <code>course_id</code> column of the registration table, <code>EXISTS</code> evaluates to <code>FALSE</code> and the course name won't be included in the result.</p>
<p>See the evaluation flow below:</p>
<pre><code class="language-sql">EXISTS → FALSE → course excluded
</code></pre>
<p>This process continues for each row in the main query until all rows have been evaluated. See final result of the whole query below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/e0650400-a3e8-41c7-b0b5-4c7eaa84a507.png" alt="This image shows all courses with at least a registration" style="display:block;margin:0 auto" width="175" height="142" loading="lazy">

<p>The result shows all courses with at least one registration.</p>
<p>What if we want to show those with no registrations yet?</p>
<pre><code class="language-sql">SELECT 
    course_name
FROM course AS l
WHERE NOT EXISTS (SELECT 1
              FROM registration AS r
              WHERE r.course_id = l.id)
</code></pre>
<p>By adding <code>NOT</code> before <code>EXISTS</code>, we reverse the condition. Instead of returning courses with at least one registration, the query returns courses with no registrations:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6927e67b50965f71a86749d9/8881414d-d706-4502-866d-1daab8f557fd.png" alt="This image shows the two courses with no registrations yet" style="display:block;margin:0 auto" width="156" height="67" loading="lazy">

<p>The result shows the two courses with no registrations yet.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Subqueries can look intimidating at first, especially when they're nested inside larger queries. But once you understand that their purpose is simply to <strong>support the main query</strong>, they become much easier to work with.</p>
<p>The important thing isn't to memorize every possible subquery pattern. Instead, learn to recognize <strong>what the main query needs and how a subquery can provide it</strong>. With that mindset, concepts such as derived columns, derived tables, <code>IN</code>, <code>EXISTS</code>, and <code>NOT EXISTS</code> become practical tools rather than complicated SQL syntax.</p>
<p>With enough practice, you'll start seeing subqueries not as a complicated feature of SQL, but as a natural way to break down and solve more complex questions.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
