DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Data Engineering
  3. Databases
  4. Use RETURNING Clause to Avoid Unnecessary SQL Statements

Use RETURNING Clause to Avoid Unnecessary SQL Statements

Learn more about using the RETURNING clause to avoid unnecessary SQL statements in your database.

Steven Feuerstein user avatar by
Steven Feuerstein
·
Apr. 30, 19 · Tutorial
Like (5)
Save
Tweet
Share
53.78K Views

Join the DZone community and get the full member experience.

Join For Free

The RETURNING clause allows you to retrieve values of columns (and expressions based on columns) that were modified by an insert, delete or update. Without RETURNING, you would have to run a SELECT statement after the DML statement is completed, in order to obtain the values of the changed columns. So, RETURNING helps avoid another roundtrip to the database, another context switch in a PL/SQL block.

The RETURNING clause can return multiple rows of data, in which case you will use the RETURNING BULK COLLECT INTO form.

You can also call aggregate functions in the RETURNING clause to obtain sums, counts, and so on of columns in multiple rows changed by the DML statement.

Finally, you can also use RETURNING with EXECUTE IMMEDIATE (for dynamically constructed and executed SQL statements).

Run this LiveSQL script to see all of the statements shown below "in action."

First, I will create a table to use in my scripts:

CREATE TABLE parts ( 
   part_number    INTEGER 
 , part_name   VARCHAR2 (100))
/

BEGIN
   INSERT INTO parts VALUES (1, 'Mouse');
   INSERT INTO parts VALUES (100, 'Keyboard');
   INSERT INTO parts VALUES (500, 'Monitor');
   COMMIT;
END;
/


Which Rows Did I Update? (the Wrong Way)

The code below issues the update, and then, in a separate SQL statement, it retrieves the part number of the row that was just modified — but only by reproducing the logic ("partname = UPPER (partname)") in the WHERE clause.

This means that I have introduced repetition in my code, and also inefficiency (an extra context switch). This is logically equivalent to using the RETURNING clause, but definitely inferior to RETURNING.

And keep in mind that if you use a SELECTafter your DML statement to determine if the correct changes were made, you need to be very careful about how you specify the WHERE clause of your query to be sure that you identify the same rows that were (possibly) changed. 

DECLARE 
   l_num   PLS_INTEGER; 
BEGIN 
   UPDATE parts 
      SET part_name = UPPER (part_name) 
    WHERE part_name LIKE 'K%'; 

   SELECT part_number 
     INTO l_num 
     FROM parts 
    WHERE part_name = UPPER (part_name); 

   DBMS_OUTPUT.put_line (l_num); 
END;

/


Which Rows Did I Update? (the Right Way)

Don't do an unnecessary SELECT simply to see/verify the impact of a non-query DML statement! Just add RETURNING to the statement and get information back from that single context switch between PL/SQL and SQL. Note that this RETURNING INTO only works because the WHERE clause identifies a single row for changing. If more than one row is or may be changed, you will need to also use BULK COLLECT (see later example).

DECLARE 
   l_num   PLS_INTEGER; 
BEGIN 
      UPDATE parts 
         SET part_name = UPPER (part_name) 
       WHERE part_name LIKE 'K%' 
   RETURNING part_number 
        INTO l_num; 

   DBMS_OUTPUT.put_line (l_num); 
END;


Use RETURNING With BULK COLLECT INTO When Changing Multiple Rows

If your non-query DML statement changes (or might change) more than one row, you will want to add BULK COLLECT to your RETURNING INTO clause and populate an array with information from each changed row.

DECLARE 
   l_part_numbers   DBMS_SQL.number_table; 
BEGIN 
      UPDATE parts 
         SET part_name = part_name || '1' 
   RETURNING part_number 
        BULK COLLECT INTO l_part_numbers; 

   FOR indx IN 1 .. l_part_numbers.COUNT 
   LOOP 
      DBMS_OUTPUT.put_line (l_part_numbers (indx)); 
   END LOOP; 
END;


Return an Entire Row? 

Not with ROW keyword. You can " UPDATE table_name SET ROW =" to perform a record-level update, but you cannot use the ROW keyword in that same way in a RETURNING clause.

DECLARE 
   l_part   parts%ROWTYPE; 
BEGIN 
      UPDATE parts 
         SET part_number = -1 * part_number, part_name = UPPER (part_name) 
       WHERE part_number = 1 
   RETURNING ROW 
        INTO l_part; 

   DBMS_OUTPUT.put_line (l_part.part_name); 
END;


Populate Record in RETURNING With the List of Columns

Sorry, but you must list each column, with compatible number and type to the fields of the "receiving" record. 

DECLARE 
   l_part   parts%ROWTYPE; 
BEGIN 
      UPDATE parts 
         SET part_number = -1 * part_number, part_name = UPPER (part_name) 
       WHERE part_number = 1 
   RETURNING part_number, part_name 
        INTO l_part; 

   DBMS_OUTPUT.put_line (l_part.part_name); 
END;


OK, let's create another table for some other examples.

CREATE TABLE employees ( 
   employee_id   INTEGER 
 , last_name     VARCHAR2 (100) 
 , salary        NUMBER)
/

BEGIN
   INSERT INTO employees VALUES (100, 'Gutseriev', 1000);
   INSERT INTO employees VALUES (200, 'Ellison', 2000);
   INSERT INTO employees VALUES (400, 'Gates', 3000);
   INSERT INTO employees VALUES (500, 'Buffet', 4000);
   INSERT INTO employees VALUES (600, 'Slim', 5000);
   INSERT INTO employees VALUES (700, 'Arnault', 6000);
   COMMIT;
END;
/


Need Aggregate Information About Impact of DML?

Sure, you could execute ANOTHER SQL statement to retrieve that information, using group functions. As in:

DECLARE 
   l_total   INTEGER; 
BEGIN 
   UPDATE employees 
      SET salary = salary * 2 
    WHERE INSTR (last_name, 'e') > 0; 

   SELECT SUM (salary) 
     INTO l_total 
     FROM employees 
    WHERE INSTR (last_name, 'e') > 0; 

   DBMS_OUTPUT.put_line (l_total); 
END;


Or, you could perform computation in PL/SQL. Use RETURNING to get back all the modified salaries. Then iterate through them, summing up the total along the way. Hmmm. That's a lot of code to write to do a SUM operation.

DECLARE 
   l_salaries   DBMS_SQL.number_table; 
   l_total      INTEGER := 0; 
BEGIN 
      UPDATE employees 
         SET salary = salary * 2 
       WHERE INSTR (last_name, 'e') > 0 
   RETURNING salary 
        BULK COLLECT INTO l_salaries; 

   FOR indx IN 1 .. l_salaries.COUNT 
   LOOP 
      l_total := l_total + l_salaries (indx); 
   END LOOP; 

   DBMS_OUTPUT.put_line (l_total); 
END;


What you should do instead is call the aggregate function right inside the RETURNING clause!

Yes! You can call SUM, COUNT, etc. directly in the RETURNING clause and thereby perform analytics before you return the data back to your PL/SQL block. Very cool. 

DECLARE    l_total   INTEGER; 
BEGIN 
      UPDATE employees 
         SET salary = salary * 2 
       WHERE INSTR (last_name, 'e') > 0 
   RETURNING SUM (salary) 
        INTO l_total; 

   DBMS_OUTPUT.put_line (l_total); 
END;


Use RETURNING With EXECUTE IMMEDIATE

You can also take advantage of the RETURNING clause when executing a dynamic SQL statement! 

DECLARE  
   l_part_number   parts.part_number%TYPE;  
BEGIN  
   EXECUTE IMMEDIATE 
   q'[UPDATE parts  
         SET part_name = part_name || '1' 
       WHERE part_number = 100 
      RETURNING part_number INTO :one_pn]'       
   RETURNING INTO l_part_number;  

   DBMS_OUTPUT.put_line (l_part_number);   
END;


RETURNING Multiple Rows in EXECUTE IMMEDIATE 

In this variation, you see how to use RETURNING with a dynamic SQL statement that modifies more than one row. 

DECLARE  
   l_part_numbers   DBMS_SQL.number_table;  
BEGIN  
   EXECUTE IMMEDIATE 
   q'[UPDATE parts  
         SET part_name = part_name || '1' 
      RETURNING part_number INTO :pn_list]'       
   RETURNING BULK COLLECT INTO l_part_numbers;  

   FOR indx IN 1 .. l_part_numbers.COUNT  
   LOOP  
      DBMS_OUTPUT.put_line (l_part_numbers (indx));  
   END LOOP;  
END;


Resources

  • The RETURNING INTO Clause (doc)

  • DML Returning INTO Clause (Oracle-Base)

sql Database

Published at DZone with permission of Steven Feuerstein, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How To Build an Effective CI/CD Pipeline
  • Top 10 Best Practices for Web Application Testing
  • GitLab vs Jenkins: Which Is the Best CI/CD Tool?
  • Spring Cloud

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: