VALUES Statement in MySQL

In MySQL, the VALUES statement returns a set of one or more rows as a table. Basically, it’s a table value constructor in accordance with the SQL standard, which also functions as a standalone SQL statement.

The VALUES statement was introduced in MySQL 8.0.19.

Syntax

The official syntax goes like this:

VALUES row_constructor_list [ORDER BY column_designator] [LIMIT number]

row_constructor_list:
    ROW(value_list)[, ROW(value_list)][, ...]

value_list:
    value[, value][, ...]

column_designator:
    column_index

Example

Here’s a simple example to demonstrate how it works:

VALUES ROW(1, 2, 3), ROW(4, 5, 6);

Result:

+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |        2 |        3 |
|        4 |        5 |        6 |
+----------+----------+----------+

The resulting columns are implicitly named column_0, column_1, column_2, and so on, always beginning with 0.

We can see that each ROW() row constructor clause results in a new row in the resulting table.

Each ROW() contains a value list of one or more scalar values enclosed in the parentheses. A value can be a literal of any MySQL data type or an expression that resolves to a scalar value.

Therefore we can also do the following:

VALUES ROW("Black", "Cat"), ROW("Yellow", "Dog");

Result:

+----------+----------+
| column_0 | column_1 |
+----------+----------+
| Black    | Cat      |
| Yellow   | Dog      |
+----------+----------+

Or stuff like this:

VALUES 
   ROW(CURDATE(), DATE_ADD(CURDATE(), INTERVAL 10 YEAR)),
   ROW(CURTIME(), DATE_ADD(CURTIME(), INTERVAL 2 HOUR));

Result:

+---------------------+---------------------+
| column_0            | column_1            |
+---------------------+---------------------+
| 2022-02-17 00:00:00 | 2032-02-17 00:00:00 |
| 2022-02-17 09:30:46 | 2022-02-17 11:30:46 |
+---------------------+---------------------+

The ORDER BY Clause

The syntax allows for the use of the ORDER BY clause in order to order the results. However, I’ve found that the ORDER BY clause doesn’t work as expected on the systems I’ve tried running it against.

Here’s how it should work (according to the MySQL documentation):

VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8) ORDER BY column_1;

Result:

+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |       -2 |        3 |
|        4 |        6 |        8 |
|        5 |        7 |        9 |
+----------+----------+----------+

But on the two systems that I ran that statement against (MySQL 8.0.26 on Ubuntu 20.04.3 and MySQL 8.0.27 Homebrew on MacOS Monterery), the ORDER BY clause doesn’t work at all. Perhaps this is a bug.

The LIMIT Clause

We can use the LIMIT clause to limit the number of rows that are output:

VALUES 
   ROW('Black', 'Cat'), 
   ROW('Yellow', 'Dog'), 
   ROW('Aqua', 'Fish')
LIMIT 2;

Result:

+----------+----------+
| column_0 | column_1 |
+----------+----------+
| Black    | Cat      |
| Yellow   | Dog      |
+----------+----------+

With a SELECT Statement

We can also use the VALUES statement within a SELECT statement, as if the VALUES table constructor were an actual table:

SELECT
   PetName,
   PetType
FROM
   (VALUES 
      ROW(1, "Fluffy", "Cat"),
      ROW(2, "Bark", "Dog"),
      ROW(3, "Gallop", "Horse")
   ) AS Pets(PetId, PetName, PetType)
WHERE PetId = 2;

Result:

+---------+---------+
| PetName | PetType |
+---------+---------+
| Bark    | Dog     |
+---------+---------+

ROW() Cannot be Empty

A row constructor cannot be empty, unless it’s being used as source in an INSERT statement.

Here’s what happens if we try to use an empty row constructor:

VALUES ROW();

Result:

ERROR 3942 (HY000): Each row of a VALUES clause must have at least one column, unless when used as source in an INSERT statement.

ROW() Can Contain Null Values

Although row constructors cannot be empty, they can contain Null values:

VALUES ROW(null, null);

Result:

+----------+----------+
| column_0 | column_1 |
+----------+----------+
|     NULL |     NULL |
+----------+----------+

Each ROW() Must Contain the Same Number of Values

Each ROW() in the same VALUES statement must have the same number of values in its value list.

Therefore, we can’t do this:

VALUES ROW(1, 2), ROW(3);

Result:

ERROR 1136 (21S01): Column count doesn't match value count at row 2

Using VALUES to Insert Data

We can use the VALUES statement in conjunction with the INSERT and REPLACE statements to insert data into a table.

Example:

INSERT INTO Pets VALUES 
   ROW(9, 3, 1, 'Woof', '2020-10-03'), 
   ROW(10, 4, 5, 'Ears', '2022-01-11');

That inserted two rows into a table called Pets. This assumes that the table already exists.

We can now use a SELECT statement to see the new values in the table:

SELECT * FROM Pets
WHERE PetId IN (9, 10);

Result:

+-------+-----------+---------+---------+------------+
| PetId | PetTypeId | OwnerId | PetName | DOB        |
+-------+-----------+---------+---------+------------+
|     9 |         3 |       1 | Woof    | 2020-10-03 |
|    10 |         4 |       5 | Ears    | 2022-01-11 |
+-------+-----------+---------+---------+------------+

The above INSERT statement is the equivalent of doing the following:

INSERT INTO Pets VALUES 
   (9, 3, 1, 'Woof', '2020-10-03'), 
   (10, 4, 5, 'Ears', '2022-01-11');

When Creating Tables

The VALUES statement can also be used in place of the source table in CREATE TABLE … SELECT and CREATE VIEW … SELECT statements.

Here’s an example:

CREATE TABLE t1 VALUES ROW(1,2,3), ROW(4,5,6);
SELECT * FROM t1;

Result:

+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |        2 |        3 |
|        4 |        5 |        6 |
+----------+----------+----------+

We can also do this:

CREATE TABLE t2 SELECT * FROM (VALUES ROW(1,2,3), ROW(4,5,6)) AS v;
SELECT * FROM t2;

Result:

+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |        2 |        3 |
|        4 |        5 |        6 |
+----------+----------+----------+

Those two CREATE TABLE statements are like doing this:

CREATE TABLE t3 SELECT * FROM t2;
SELECT * FROM t3;

Result:

+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |        2 |        3 |
|        4 |        5 |        6 |
+----------+----------+----------+

In this case, I used the t2 table as the source table, instead of providing the values in a VALUES statement.