• Home
  • Blog
  • Using Subqueries in the Select Statement (with examples)
January 20, 2015

This is the second in a series of articles about subqueries.  In this article, we discuss subqueries in the SELECT statement’s column list.  Other articles discuss their uses in other clauses.

All the examples for this lesson are based on Microsoft SQL Server Management Studio and the AdventureWorks2012 database.  You can get started using these free tools using my Guide Getting Started Using SQL Server.

Using Subqueries in the Select Statement

When a subquery is placed within the column list it is used to return single values.  In this case, you can think of the subquery as a single value expression.  The result returned is no different than the expression “2 + 2.”  Of course, subqueries can return text as well, but you get the point!

When working with subqueries, the main statement is sometimes called the outer query.  Subqueries are enclosed in parenthesis, this makes them easier to spot.

Be careful when using subqueries.  They can be fun to use, but as you add more to your query they can start to slow down your query.

Simple Subquery to Calculate Average

Let’s start out with a simple query to show SalesOrderDetail and compare that to the overall average SalesOrderDetail LineTotal.  The SELECT statement we’ll use is:

SELECT SalesOrderID,
(SELECT AVG(LineTotal)
   FROM Sales.SalesOrderDetail) AS AverageLineTotal
FROM   Sales.SalesOrderDetail;

This query returns results as:

Sub Query Results

The subquery, which is shown in red above, is run first to obtain the average LineTotal.

FROM   Sales.SalesOrderDetail

This result is then plugged back into the column list, and the query continues.  There are several things I want to point out:

  1. Subqueries are enclosed in parenthesis.
  2. When subqueries are used in a SELECT statement they can only return one value. This should make sense, simply selecting a column returns one value for a row, and we need to follow the same pattern.
  3. In general, the subquery is run only once for the entire query, and its result reused. This is because the query result does not vary for each row returned.
  4. It is important to use aliases for the column names to improve readability.

Simple Subquery in Expression

As you may expect the result for a subquery can be used in other expressions.  Building on the previous example let’s use the subquery to determine how much our LineTotal varies from the average.

The variance is simply the LineTotal minus the Average Line total.  In the following subquery, I’ve colored it blue.  Here is the formula for the variance:

LineTotal - (SELECT AVG(LineTotal)                             
               FROM   Sales.SalesOrderDetail)

The SELECT statement enclosed in the parenthesis is the subquery.  Like the earlier example, this query will run once, return a numeric value, which is then subtracted from each LineTotal value.

Here is the query in final form:

SELECT SalesOrderID,
       (SELECT AVG(LineTotal)
          FROM   Sales.SalesOrderDetail) AS AverageLineTotal,
       LineTotal - (SELECT AVG(LineTotal)
                    FROM   Sales.SalesOrderDetail) AS Variance
FROM   Sales.SalesOrderDetail

Here is the result:

Suqquery Results in Expression

When working with subqueries in select statements I usually build and test the subquery first. SELECT statements can get complicated very quickly.  It is best to build them up little by little.  By building and testing the various pieces separately, it really helps with debugging.

Correlated Queries

There are ways to incorporate the outer query’s values into the subquery’s clauses.  These types of queries are called correlated subqueries, since the results from the subquery are connected, in some form, to values in the outer query.  Correlated queries are sometimes called synchronized queries.

If you’re having trouble knowing what correlate means, check out this definition from Google:

Correlate:   “have a mutual relationship or connection, in which one thing affects or depends on another.”

A typical use for a correlated subquery is used one of the outer query’s columns in the inner query’s WHERE clause.  This is common sense in many cases you want to restrict the inner query to a subset of data.

Correlated Subquery Example

We’ll provide a correlated subquery example by reporting back each SalesOrderDetail LineTotal, and the Average LineTotal’s for the overall Sales Order.

This request differs significantly from our earlier examples since the average we’re calculating varies for each sales order.

This is where correlated subqueries come into play.  We can use a value from the outer query and incorporate it into the filter criteria of the subquery.

Let’s take a look at how we calculate the average line total.  To do this this I’ve put together an illustration that shows the SELECT statement with subquery.

Diagram to Explain Correlated Subquery

To further elaborate on the diagram.  The SELECT statement consists of two portions, the outer query, and the subquery.  The outer query is used to retrieve all SalesOrderDetail lines.  The subquery is used to find and summarize sales order details lines for a specific SalesOrderID.

Diagram showing one record being processed as correlated subquery

If I was to verbalize the steps we are going to take, I would summarize them as:

  1. Get the SalesOrderID.
  2. Return the Average LineTotal from All SalesOrderDetail items where the SalesOrderID matches.
  3. Continue on to the next SalesOrderID in the outer query and repeat steps 1 and 2.

The query you can run in the AdventureWork2012 database is:

SELECT SalesOrderID,
       (SELECT AVG(LineTotal)
          FROM   Sales.SalesOrderDetail
         WHERE  SalesOrderID = SOD.SalesOrderID)
                AS AverageLineTotal
FROM   Sales.SalesOrderDetail SOD

Here are the results of the query:

Correlated Subquery Example Results

There are a couple of items to point out.

  1. You can see I used column aliases to help make the query results easier to read.
  2. I also used a table alias, SOD, for the outer query. This makes it possible to use the outer query’s values in the subquery.  Otherwise, the query isn’t correlated!
  3. Using the table aliases make it unambiguous which columns are from each table.

Breaking down the Correlated Subquery

Let’s now try to break this down using SQL.

To start let’s assume we’re going to just get our example for SalesOrderDetailID 20.  The corresponding SalesOrderID is 43661.

To get the average LineTotal for this item is easy

FROM   Sales.SalesOrderDetail
WHERE  SalesOrderID = 43661

This returns the value 2181.765240.

Now that we have the average we can plug it into our query

SELECT SalesOrderID,
       2181.765240 AS AverageLineTotal
FROM   Sales.SalesOrderDetail
WHERE  SalesOrderDetailID = 20

Using subqueries this becomes

SELECT SalesOrderID,
       (SELECT AVG(LineTotal)
          FROM Sales.SalesOrderDetail
         WHERE SalesOrderID = 43661) AS AverageLineTotal
FROM   Sales.SalesOrderDetail
WHERE  SalesOrderDetailID = 20

Final query is:

SELECT SalesOrderID,
       (SELECT AVG(LineTotal)
          FROM Sales.SalesOrderDetail
WHERE  SalesOrderID = SOD.SalesOrderID) AS AverageLineTotal
FROM   Sales.SalesOrderDetail AS SOD

Correlated Subquery with a Different Table

A Correlated subquery, or for that matter any subquery, can use a different table than the outer query.  This can come in handy when you’re working with a “parent” table, such as SalesOrderHeader, and you want to include in result a summary of child rows, such as those from SalesOrderDetail.

Let’s return the OrderDate, TotalDue, and number of sales order detail lines.  To do this we can use the following diagram to gain our bearings:

Correlated Subquery Datamodel

To do this we’ll include a correlated subquery in our SELECT statement to return the COUNT of SalesOrderDetail lines.  We’ll ensure we are counting the correct SalesOrderDetail item by filtering on the outer query’s SalesOrderID.

Here is the final SELECT statement:

SELECT SalesOrderID,
       (SELECT COUNT(SalesOrderDetailID)
          FROM Sales.SalesOrderDetail
         WHERE SalesOrderID = SO.SalesOrderID) as LineCount
FROM   Sales.SalesOrderHeader SO

The results are:

Correlated Subquery Different Table Example Results

Some things to notice with this example are:

  • The subquery is selecting data from a different table than the outer query.
  • I used table and column aliases to make it easier to read the SQL and results.
  • Be sure to double-check your where clause! If you forget to include the table name or aliases in the subquery WHERE clause, the query won’t be correlated.

Correlated Subqueries versus Inner Joins

It is important to understand that you can get that same results using either a subquery or join.  Though both return the same results, there are advantages and disadvantages to each method!

Consider the last example where we count line items for SalesHeader items.

SELECT SalesOrderID,
       (SELECT COUNT(SalesOrderDetailID)
          FROM Sales.SalesOrderDetail
WHERE SalesOrderID = SO.SalesOrderID) as LineCount
FROM  Sales.SalesOrderHeader SO

This same query can be done using an INNER JOIN along with GROUP BY as

SELECT   SO.SalesOrderID,
         COUNT(SOD.SalesOrderDetailID) as LineCount
FROM     Sales.SalesOrderHeader SO
         INNER JOIN Sales.SalesOrderDetail SOD
         ON SOD.SalesOrderID = SO.SalesOrderID
GROUP BY SO.SalesOrderID, OrderDate, TotalDue

Which one is faster?

You’ll find that many folks will say to avoid subqueries as they are slower.  They’ll argue that the correlated subquery has to “execute” once for each row returned in the outer query, whereas the INNER JOIN only has to make one pass through the data.

Myself?  I say check out the query plan.  I followed my own advice for both of the examples above and found the plans to be the same!

That isn’t to say the plans would change if there was more data, but my point is that you shouldn’t just make assumptions.  Most SQL DBMS optimizers are really good at figuring out the best way to execute your query.  They’ll take your syntaxes, such as a subquery, or INNER JOIN, and use them to create an actual execution plan.

Which one is Easier to Read?

Depending upon what you’re comfortable with you may find the INNER JOIN example easier to read than the correlated query.  Personally, in this example, I like the correlated subquery as it seems more direct.  It is easier for me to see what is being counted.

In my mind, the INNER JOIN is less direct.  First you have to see that all the sales details rows are being returned hand then summarized.  You don’t really get this until you read the entire statement.

Which one is Better?

Let me know what you think.  I would like to hear whether you would prefer to use the correlated subquery or INNER JOIN example.

About the author 

Kris Wenzel

Kris Wenzel has been working with databases over the past 30 years as a developer, analyst, and DBA. He has a BSE in Computer Engineering from the University of Michigan and a MBA from the University of Notre Dame. Kris has written hundreds of blog articles and many online courses. He loves helping others learn SQL.

  • Kris, Excellent article and I like the very detailed explaination about Correlated sub queries. Thanks so much.
    I Have question – Which query will be executed first in a correlated sub query? The outer one or the inner one?

    • Hi Yogesh,

      That is a good question. On paper the outer query would run once for a row, then the correlated value would be used to “drive” the inner query, but that is just in concept.

      In reality, the DBMS query optimizer takes the SQL statement, analyzes it, and then decides on a how to run it. This is called the execution plan. Many variables, such as table size and indexes are taken into account.

      It is pretty easy to view the execution plan for any query.

      Run SQL Execution Plan

      Reading an execution plan can be tricky, but in this once you can see the plan is running a merge join. In simple terms, that mean the DBMS took my subquery and “rewrote” it as a join and then ran it.

      The lesson to learn from this is:
      1. Be skeptical when others tell you one way, such a sub queries, are slower than another. How do they know which plan your DBMS for your DB will create?
      2. The truth lies in the execution plan. If you want to optimize your queries, this is a good place to start.


  • For the first example (below), do you actually need to do the subquery? Or could you just use “Select Sales OrderID, LineTotal, Ave(LineTotal)…” and get the same result?

    SELECT SalesOrderID,
    (SELECT AVG(LineTotal)
    FROM Sales.SalesOrderDetail) AS AverageLineTotal
    FROM Sales.SalesOrderDetail;

    • Hi,
      Good question! It would seem that would do the same thing, but

      SELECT SalesOrderID,
      FROM Sales.SalesOrderDetail;

      Is an invalid statement. To make it right, you need to add GROUP BY clause. As soon as you do that, the average that is calculated is for LineTotal value within the GROUP.

      In the example I give, the one with the subquery, the Average LineTotal if for ALL SalesOrders, not those from any group.

      I suppose you could get around that if you knew there was a column that had the same value for every row. Then the grouping would effectively be the entire table, but that is a special case indeed.

  • Hi Kris,

    Kudos to your brilliant explanation at first. However, I figured out that we can achieve the same result for the last query using Cross Apply besides sub-query and Inner join. Example code follows:

    SELECT SO.SalesOrderID,
    FROM Sales.SalesOrderHeader SO
    cross apply (select count(sod.SalesOrderDetailID) as LineCount
    from sales.SalesOrderDetail as sod
    where so.SalesOrderID = sod.SalesOrderID) SOD

    However, Can you please kindly comment on Cross Apply’s performance in contrast to sub query and inner join.


    • Thank for pointing out the CROSS APPLY clause. At some time in the future I’ll focus on this and some of the other newer SQL features that help with Business Intelligence solutions.
      When I ran your example and looked at the execution plan, I didn’t see any significant differences between it and the plans from the examples I provided.
      That said, in certain situations, or other databases, that could be different.

  • What’s up,I read your blog named “Using Subqueries in the Select Statement (with examples) – Essential SQL” on a regular basis.Your writing style is witty, keep doing what you’re doing! And you can look our website about free proxy.

  • Kris,
    Thank you for a great article! What about the need for a join within the subquery? In this instance, I need to be able to add a column that will give me a dividend, but I need to qualify my WHERE to only be a particular value from another table? Can this be done?

    Thanks for your time,

    (SELECT(sum(case when cl.direction = 1 then 1 else 0 end)/(sum(1)) from CLog cl JOIN PLog pl on cl.ID = pl.CallID where datediff(second, pl.StartTime, pl.StopTime) > 180 )) as Ratio

  • Hello! thank you for a great blog. Do you think we can use window functions to substitute the AverageLineTotal part? New to subqueries and window functions, and always get confused when to use which one. Thank you for reply in advance!

    • Hi,

      You could but notice later in the article I do the same calculation with an INNER JOIN. There you’ll see I do the same thing just using a simple GROUP BY, no need to up the ante with window functions…


  • Kris,

    Thank you so much for the clear explanation. I’m learning MySQL for the first time and was getting so lost with subqueries in different clauses with no help from my prof. Correlated subqueries were hard for me to understand because I wasn’t getting the big picture. Thank you for the explanation!

  • I have searched tons of articles on this topic. This site is the first site to offer actuall step by step quality credible information for any beginner or intermediate. Thank you

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}


    Nothing is worse than, being excited to learn a new tool but not knowing where to start, wasting time learning the wrong features, and being overwhelmed .

    But it doesn't have to be this way.

    I'm Putting together a free email course to help you get started learning SQL Server.