Comparing Data Handling in MySQL and PostgreSQL: the RETURNING clause and CTE's
Published: 2020-09-14 23:14:21

As a follow-up to my last post titled "Comparing Data in MySQL and PostgreSQL", I'd like to discuss some differences between these two databases when it comes to actually working with data. And like my previous post, many of these observations were enlightening as to the benefits PostgreSQL can offer. 

One major inconvenience of working with MySQL is the need to run an additional query in order to get a row that was *just inserted*. At best, MySQL only returns the id of the most recently inserted row, but no additional columns (the id is critical since it was auto-incremented and the user would presumably not know what it is). If using MySQL in node.js, for example, an `INSERT` query returns a json message such as this, where the id of the inserted row is 6:
```
{
    "fieldCount": 0,
    "affectedRows": 1,
    "insertId": 6,
    "serverStatus": 3,
    "warningCount": 0,
    "message": "",
    "protocol41": true,
    "changedRows": 0
}
```
In command-line MySQL, however, one would have to use the `LAST_INSERT_ID()` function to run a query to get said id:
```
mysql> SELECT LAST_INSERT_ID();
       +------------------+
       | LAST_INSERT_ID() |
       +------------------+
       |                6 |
       +------------------+
       1 row in set (0.03 sec)
```
...and then you would *still* have to perform another query to get any other columns from the row. 

PostgreSQL, however, has the unbelievably sensible `RETURNING` clause, which can't be lived without after knowing about it (guilty). `RETURNING` is essentially a bridge from an `INSERT` query directly into a `SELECT` query in which you decide what parts of the inserted row are returned and how. Here's an example in a PostgreSQL database `sports`, where a sport is inserted into a table `sports`, and its id is returned, along with the `name` (aliased as "`sportName`"), but NOT the `season`:
```
sports=# INSERT INTO sports(name, season)
         VALUES('football', 'fall')
         RETURNING sports.id, sports.name AS "sportName";

         id | sportName 
         ----+-----------
           3 | football
         (1 row)

         INSERT 0 1

```
`RETURNING` is a not-so-subtle difference in functionality between these two databases that's hard to ignore. But its functionality has more ramifications when considering Common Table Expressions. CTE's are essentially a means of aliasing subqueries that present the user with a very readable code block. Both MySQL and Postgres have CTE's, but when combined with Postgres's `RETURNING` clause, a CTE can accomplish streamlined sequential insertions without the need for multiple transactions. In the example below, a CTE is used to get the id of the sport we added previously, then a new league 'NFL' is inserted with the id of the selected sport as foreign key `id_sport`, and once the league exists, a team is inserted using the league's id and sport's id. 

```
sports=# WITH sport AS (
           SELECT id
           FROM sports
           WHERE name = 'football'
         ), league AS (
           INSERT INTO leagues (name, id_sport, level)
           VALUES ('NFL', (SELECT sport.id FROM sport), 'professional')
           RETURNING id, id_sport
         )
         INSERT INTO teams (designation, nickname, id_sport, id_league)
         SELECT 'New Orleans', 'Saints', sport.id, league.id
         FROM sport
         JOIN league ON TRUE
         RETURNING * \gx

         -[ RECORD 1 ]------------
         id          | 7
         designation | New Orleans
         nickname    | Saints
         id_sport    | 3
         id_league   | 2

         INSERT 0 1
```

Accomplishing the same thing in MySQL might look something like the following example, where the league is inserted, then in a new query, the id of the just-inserted league is selected to use as the foreign key to insert the team. And if any information beyond the id is needed for the team, another query will certainly have to be made of course. 
```
mysql> INSERT INTO leagues (name, id_sport, level)
    -> VALUES ('NFL', (
    ->     SELECT sports.id
    ->     FROM sports
    ->     WHERE name = 'football'
    ->   ), 'professional');
       Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO teams (designation, nickname, id_sport, id_league)
    -> SELECT 'New Orleans', 'Saints', (
    ->     SELECT sports.id
    ->     FROM sports
    ->     WHERE name = 'football'
    ->   ), (
    ->     SELECT LAST_INSERT_ID()
    ->   );
       Query OK, 1 row affected (0.00 sec)
       Records: 1  Duplicates: 0  Warnings: 0

mysql> select * from teams \G
       *************************** 1. row ***************************
                id: 7
       designation: New Orleans
          nickname: Saints
          id_sport: 3
         id_league: 2
       1 row in set (0.00 sec)
```
The difference here may seem trivial, but Postgres's singular query capability could have a significant impact when writing queries away from the command line and within an application. This is, in fact, something I personally have been inconvenienced by when using MySQL before I knew any different. It wasn't until writing these comparisons that I came to realize some of the things I thought a database should logically be able to do were, indeed, possible—with another database. This is not to say that MySQL is without merit, of course (it *is* the most popular open-source database after all—over twice as popular as Postgres, in fact), but that sometimes, branching out into unfamiliar territory can produce solutions you may not have known existed. Fittingly, other database users appear to be making some of the same conclusions, as PostgreSQL and others continue to make gains in evening out the playing field of relational databases. 
source: db-engines.com/