Typically, when working with data, we need to use the results of one query execution in the second query. For example, we need to select the tuples where the value from one column is equal to the max value in another column. We can do it with simple subqueries: select the max value and in the main query select the tuples which are equal to this value. Thankfully, SQL has a powerful tool to combine queries — subqueries. Let's have a look at it!
A subquery (also called an inner or nested query) is a query within another SQL query (called a main or outer query). Subqueries can be nested within SELECT, UPDATE, INSERT, and DELETE statements.
Subqueries nested in SELECT statement
Subqueries nested in SELECT statements are the most common type of subqueries. A subquery may occur in the SELECT clause, in the FROM clause, and in the WHERE clause of the SELECT statement.
Let's have a look at the following example.
Table registered_users has two columns: username and sign_up_date :
username | sign_up_date |
|---|---|
timbrown | 2012-12-04 |
awesometomas | 2014-11-06 |
darlingKate | 2012-12-04 |
frMartin | 2014-07-03 |
We will use a query with a subquery nested into the WHERE clause to select the first registered users:
SELECT
username,
sign_up_date
FROM
registered_users
WHERE
sign_up_date = (
SELECT
MIN(sign_up_date)
FROM
registered_users
); The result of the query will look as follows:
username | sign_up_date |
|---|---|
timbrown | 2012-12-04 |
darlingKate | 2012-12-04 |
The subquery from the example above is a single-row subquery. In this case, it is safe to use simple SQL comparison operators (=, >, <=, e.t.c.), because we can be sure that our query will return only one row with one column.
If we use a subquery along with comparison operators and our subquery returns more than one row, we will get an error. This doesn't mean that you can't use the multiple-row subquery that will return one or more rows, just use a multiple-row comparison operator, such as IN and ANY.
We can use a subquery to select one of the columns in the SELECT statement to add new columns to the result set.
Let's select all data from our table registered_users and add some more info from the table users_info given below:
username | name | birth_date |
|---|---|---|
awesometomas | Tomas Jones | 1995-10-07 |
timbrown | Tim Brown | 2000-11-04 |
frMartin | Martin Brown | 2002-12-04 |
darlingKate | Kate Brown | 2005-03-03 |
We will select all the information from the registered_users table and add all the corresponding information about the actual user names from the users_info table:
SELECT *,
(SELECT
name
FROM
users_info
WHERE
username = registered_users.username) AS name
FROM
registered_usersThe result will look as follows:
username | sign_up_date | name |
|---|---|---|
timbrown | 2012-12-04 | Tim Brown |
awesometomas | 2014-11-06 | Tomas Jones |
darlingKate | 2012-12-04 | Kate Brown |
frMartin | 2014-07-03 | Martin Brown |
As we've already mentioned, you can also use a subquery in the FROM clause of the SELECT statement. In this case, a derived table (a table that we obtain with subquery) has to be aliased.
The subqueries can be correlated. Correlated subqueries use values from the outer query.
Let's have a look at the example of a correlated subquery. Here is the table new_orders with the columns id INT, product VARCHAR(40), product_category VARCHAR(40), quantity INT and unit_price INT:
id | product | product_category | quantity | unit_price |
|---|---|---|---|---|
1234 | table | furniture | 10 | 15 |
3434 | chair | furniture | 15 | 20 |
4546 | bed | furniture | 12 | 10 |
5467 | candle | decor | 45 | 40 |
3244 | sticker | decor | 40 | 14 |
3456 | frame | decor | 34 | 12 |
We will use a correlated subquery to select all the products with a unit price less than the average in the product category:
SELECT
id,
product
FROM
new_orders AS newor
WHERE unit_price < (
SELECT
AVG(unit_price)
FROM
new_orders
WHERE
product_category = newor.product_category);While query results are evaluated, the correlated subquery will be executed for each row in the table, so evaluating the query with subquery for large tables can take a lot of time.
Subqueries nested in the UPDATE statement
The subqueries can also be used with the UPDATE statement.
Let's update the table students with the columns name VARCHAR(40), scholarship INT, and exams_passed BOOLEAN:
name | scholarship | exams_passed |
|---|---|---|
Tom Jones | 200 | FALSE |
Tamara Fetch | 400 | FALSE |
Anthony Pots | 300 | FALSE |
As with the SELECT, we can use the UPDATE statement with a subquery nested into the WHERE clause. We will use this type of query with a subquery to set exams_passed to TRUE if in the exam_results table both exams marks are greater or equal to 18. The table exam_results looks as follows (name VARCHAR(40), math_exam_mark INT, and english_exam_mark INT):
name | math_exam_mark | english_exam_mark |
|---|---|---|
Tom Jones | 22 | 23 |
Tamara Fetch | 18 | 15 |
Anthony Pots | 18 | 18 |
To update the table students we can use this query:
UPDATE
students
SET
exams_passed = TRUE
WHERE
name IN (
SELECT
name
FROM
exam_results
WHERE
math_exam_mark >= 18
AND english_exam_mark >= 18
); After the execution of the query above, our table students will look as follows:
name | scholarship | exams_passed |
|---|---|---|
Tom Jones | 200 | TRUE |
Tamara Fetch | 400 | FALSE |
Anthony Pots | 300 | TRUE |
We can also use the UPDATE query with a subquery to set the modified value. We will use the query below to set scholarship column values equal to the minimal scholarship for all students who did not pass the exams. Suppose that the scholarship amounts are stored in the scholarships table.
UPDATE
students
SET
scholarship = (
SELECT
MIN(amount)
FROM scholarships
)
WHERE
exam_passed = FALSE; Now Tamara Fetch will have a scholarship equal to 200:
name | scholarship | exams_passed |
|---|---|---|
Tom Jones | 200 | TRUE |
Tamara Fetch | 200 | FALSE |
Anthony Pots | 300 | TRUE |
Subqueries nested in the INSERT statement
In the INSERT statement, we can use a subquery to insert the data returned from the subquery into another table.
In the example below we will work with the table employees with the columns name VARCHAR(20), salary INT and department_id INT:
name | salary | department_id |
|---|---|---|
Ann Reed | 4000 | 1 |
All the data about departments is stored in the table departments with the columns id INT and department VARCHAR(20):
id | department |
|---|---|
1 | HR |
2 | IT |
3 | PR |
Let's add to the employees table the info about Tomas Hedwig who works in the PR department and with a salary equal to Ann Reed's salary. To get the salary and department IDs we will use subqueries as shown below:
INSERT INTO employees
VALUES (
'Tomas Hedwig',
(SELECT salary FROM employees WHERE name = 'Ann Reed'),
(SELECT id FROM departments WHERE department = 'PR')
)After the query execution the employees table will have two rows:
name | salary | department_id |
|---|---|---|
Ann Reed | 4000 | 1 |
Tomas Hedwig | 4000 | 3 |
Subqueries nested in the DELETE statement
We can also use subqueries in the DELETE statement as a part of the condition in the WHERE clause.
Assume that we have the table orders with columns order_id INT, customer_id INT, product VARCHAR(20), and city VARCHAR(20):
order_id | customer_id | product | city |
|---|---|---|---|
1 | 1 | shampoo | London |
2 | 1 | hair mask | London |
3 | 2 | hair mask | London |
And another table customers with columns customer_id INT and name VARCHAR(40):
customer_id | name |
|---|---|
1 | Ann Smith |
2 | John Doe |
3 | Sam Brown |
We will delete from the table orders all the orders made by Ann Smith:
DELETE FROM orders
WHERE customer_id = (SELECT customer_id FROM customers WHERE name = 'Ann Smith')After the query execution, the orders table will look as follows:
order_id | customer_id | product | city |
|---|---|---|---|
3 | 2 | hair mask | London |
Same as with the UPDATE, you cannot use the same table in the query and subquery in the DELETE statement.
Conclusion
In this topic, we've learned about the different types of subqueries. Now you know that we can use subqueries in the different parts of the query and with the different types of statements. Let's get to practice then!