30th Jul 2020 9 minutes read Using Subqueries in INSERT, UPDATE, DELETE Statements Ignacio L. Bisso subqueries INSERT, UPDATE, DELETE Table of Contents Starting Point: A Quick Review of Subqueries First Stop: Using Subqueries in INSERT Statements Second Stop: Subqueries in UPDATE Statements Third Stop: Subqueries in DELETE Statements Arriving at the End of the Subqueries Tour Did you know that subqueries can also be used in UPDATE, INSERT, and DELETE statements? Subqueries in SQL are like building blocks that can be used in a variety of statements, not just SELECT. If you find subqueries useful in SELECT, read this article to find out how useful they can be in other instructions. You will be able to create much more complex and powerful SQL queries in no time! If you want to refresh your knowledge of subqueries, I recommend the interactive SQL Practice Set course. It contains over 80 exercises on subqueries and other challenging SELECT constructions. Starting Point: A Quick Review of Subqueries Let’s start with a quick reminder of what a subquery is. Since subqueries are used most often in SELECT statements, we will review an example of a simple subquery in a SELECT statement. We can define a subquery as a query within another query. While subqueries are used most often in the WHERE clause of SELECT statements, they can be used in several other clauses including WHERE, FROM, and HAVING, among others. Let’s look at the database we will use as an example. Imagine that you are the owner of a wine shop, and you have a simple database with 3 tables to manage the shop operation. The first table is wine, which stores the products you sell, with the name, the price, the number of bottles in stock, etc. for each wine. The second is order, which stores the orders we receive from our customers, including the name of the wine ordered and the quantity ordered, among other information. wine NameTypeStockPriceWineCellar BrilliantChardonnay100022SkyWine BleuBlendCabernet98018LeBleu CatedralMalbec10027SantoRojo SantiagoMalbec204024Wines of Chile West SideCabernet140034Napa Wines Oro RossoCabernet75031Italian Caves High CoastChardonnay256017De la Costa wines order Order_idDateClient_idWine_namequantity 1Jan 10 2020100Catedral50 2Feb 15 2020103Santiago230 3Mar 12 2020102West Side85 4Mar 30 2020100Oro Rosso150 5May 3 2020100Oro Rosso30 6Jun 28 2020103Santiago200 7Jun 28 2020102West Side150 Suppose we want to obtain a list of wines for which we have never received an order. The query will look like this: SELECT name, FROM wine WHERE name NOT IN ( SELECT wine_name FROM order ) The subquery returns the names of all the wines for which we have received orders. Then the outer query, using the NOT IN operator, obtains the names of the wines never included in any order. SQL is as simple as it is powerful! If you want to brush up on some subquery concepts, I suggest the course SQL Basics where you can find a complete section on subqueries. First Stop: Using Subqueries in INSERT Statements Let’s now use a subquery in an INSERT statement. This is very common; the idea is to insert the complete result set from a subquery or a SELECT statement into a table. As an example, imagine we want to create invoices for all wine orders we received during the day. Below is a partial view of our invoice table: Invoice_idDateClient_idAmountDescriptionOrder_id 12020-01-10100$13550 bottles of Catedral1 22020-02-15103$5520230 bottles of Santiago2 32020-03-12102$289085 bottles of West Side3 42020-03-30100$4650150 bottles of Oro Rosso4 52020-05-03100$93030 bottles of Oro Rosso5 Pretend that today’s date is June 28, 2020, and we want to insert the records of the invoices associated with today’s orders. We can use the following SELECT to generate the data for the invoices: SELECT o.date, o.client_id, o.quantity * w.price as amount, o.quantity || ’ bottles of ‘ || o.wine_name, o.order_id FROM order o JOIN wine w ON w.name = o.wine_name WHERE o.date = ‘2020-06-28’ The result of this query is below: DateClient_idAmountDescriptionOrder_id 2020-06-28103$4800200 bottles of Santiago6 2020-06-28102$5100150 bottles of West Side7 This is exactly what we want to insert into the table invoice. If we simply add an INSERT clause before the query, we can insert the result of the query into the table wine, as we can see in the following example: INSERT INTO invoice (date, client_id, amount, wine_name, order_id) SELECT o.date, o.client_id, o.quantity * w.price as amount, o.quantity || ’ bottles of ‘ || o.wine_name, o.order_id FROM order o JOIN wine w ON w.name = o.wine_name WHERE o.date = ‘2020-06-28’ Note the following: We need to explicitly name the columns of the invoice table into which we are inserting. The columns in the SELECT list must be in the same order as the columns in the table. We omitted the column invoice_id to allow the database to choose the next value using a sequence generator by default. After executing the INSERT, the table invoice will have the new invoice records for today’s orders. We can see this below, with the new records in red: Invoice_idDateClient_idAmountDescriptionOrder_id 12020-01-10100$13550 bottles of Catedral1 22020-02-15103$5520230 bottles of Santiago2 32020-03-12102$289085 bottles of West Side3 42020-03-30100$4650150 bottles of Oro Rosso4 52020-05-03100$93030 bottles of Oro Rosso5 62020-06-28103$4800200 bottles of Santiago6 72020-06-28102$5100150 bottles of West Side7 Suppose that on a particular day, we accidentally ran the INSERT twice, and our customers received two invoices for each order as a result. We don’t want to repeat the same error in the future! To prevent this, we add a subquery to the INSERT statement to see if an invoice with the same order_id already exists. The following is the new version of the INSERT query. The subquery added at the end identifies the invoices that already exist, and the outer query discards them by using the NOT IN operator. INSERT INTO invoice (date, client_id, amount, wine_name, order_id) SELECT order.date, order.client_id, o.quantity * w.price as amount, o.quantity || ’ bottles of ‘ || o.wine_name, o.order_id FROM order o JOIN wine ON wine.name = order.wine_name WHERE order.date = ‘2020-06-28’ AND o.order_id NOT IN (SELECT order_id FROM invoice i WHERE i.order_id=o.order_id ) If you want to improve your skills in the use of INSERT, UPDATE, and DELETE statements, I suggest the course How to INSERT, UPDATE, and DELETE Data in SQL where you can see several examples of these statements. Second Stop: Subqueries in UPDATE Statements Like SELECT, the UPDATE statement can have a subquery in several places or clauses. In an UPDATE, the two clauses in which subqueries are used most commonly are SET and WHERE. The SET clause is where we define the new value for the column being modified by the UPDATE. We can use a subquery to obtain this new value, which can be selected from any table or any valid subquery, as long as we return only one record with only one column for each record being updated. The data type of the column returned by the subquery must be of the same type as that of the column being modified. Let’s create an UPDATE to maintain our wine bottle stock up to date. We will run an UPDATE at the end of the day to reflect what we sold today. The code will look like this: UPDATE wine w SET stock = stock - ( SELECT SUM (quantity) FROM order WHERE date = CURRENT_DATE AND order.wine_name = w.name ) If we don’t use a WHERE clause in the UPDATE, we would end up modifying all the records in the table wine, including the records for the wines we did not sell today. The subquery returns a NULL for any wine not sold today, and we would erroneously SET the stock column to NULL, since the result of the expression “stock - NULL” is NULL. We need to fix this. There are two approaches for the fix. The first is to modify the expression SUM(quantity) to return a zero instead of a NULL. For this, we simply need to use the COALESCE function, like this: UPDATE wine w SET stock = stock - ( SELECT coalesce(SUM (quantity), 0) FROM order WHERE date = CURRENT_DATE AND order.wine_name = w.name ) The second approach is to add a subquery in the WHERE clause to modify only the wines that were ordered today and maintain the expression SUM(quantity) as is. The following query shows this approach: UPDATE wine w SET stock = stock - ( SELECT SUM (quantity) FROM order WHERE date = CURRENT_DATE AND order.wine_name = w.name ) WHERE w.name IN (SELECT order.wine_name FROM order WHERE date = CURRENT_DATE) This UPDATE is not ideal: this SET clause uses a correlated subquery. A correlated subquery is one that the database needs to execute many times—one time for each row being modified in the table. In our example, the query obtains the SUM(quantity) for each wine sold today. While correlated subqueries can be powerful, they are best avoided whenever possible as a matter of best practice. Here, we can avoid the correlated subquery by using a FROM clause in the UPDATE, as we can see below: UPDATE wine w SET stock = stock - subquery.total_in_orders FROM ( SELECT wine_name, sum( order_quantity ) AS total_in_orders FROM order WHERE date = TODAY GROUP BY wine_name ) subquery WHERE w.name = subquery.wine_name As a supplementary resource, I suggest the article Beginners Guide to the SQL Subquery, where you can learn the ABC of subqueries through examples of varying complexities. Third Stop: Subqueries in DELETE Statements With the DELETE statement, subqueries can be used only inside a WHERE clause. Let’s say we want to eliminate the records of wines for which we received no order in the last 6 months. We can create a subquery that returns the wines sold in the last 6 months, then identify the records we want to remove in the wine table by using a NOT IN operator. Let’s see how SQL does this: DELETE FROM wine w WHERE name NOT IN ( SELECT wine_name FROM order WHERE date >= CURRENT_DATE - interval ‘6 Months’ ) Now, suppose we want to eliminate the wines for which the total orders in the last 6 months were fewer than 10 units. The DELETE statement will look like this: DELETE FROM wine w WHERE 10 > ( SELECT SUM(quantity) FROM order o WHERE o.wine_name = w.name AND date >= CURRENT_DATE - interval ‘6 Months’ ) Here, the subquery returns the quantity of bottles ordered in the last 6 months for each wine. By comparing this quantity to 10, we can determine whether a given wine should be eliminated. Arriving at the End of the Subqueries Tour Subqueries are like building blocks in SQL. We saw how they can be used in a variety of places, such as in SELECT statements or in any modification statements like INSERT, UPDATE, and DELETE. We saw how we could use a subquery in different clauses and that there were different ways to use subqueries in INSERT, UPDATE, and DELETE statements. If you want to learn more about subqueries, I suggest the article Subquery vs. CTE: A SQL Primer, in which a type of subquery called CTE is explained with a lot of examples and details. Finally, there are two courses with a lot of useful information on subqueries with plenty of examples: SQL Basics where you find a complete section on subqueries, and How to INSERT, UPDATE, and DELETE Data in SQL which is a more advanced course. Let’s go improve your SQL skills! Tags: subqueries INSERT, UPDATE, DELETE