INSERT SELECT FROM VALUES casting

5.4k Views Asked by At

It's often desirable to INSERT from a SELECT expression (e.g. to qualify with a WHERE clause), but this can get postgresql confused about the column types.

Example:

CREATE TABLE example (a uuid primary key, b numeric);
INSERT INTO example 
SELECT a, b 
FROM (VALUES ('d853b5a8-d453-11e7-9296-cec278b6b50a', NULL)) as data(a,b);
=> ERROR:  column "a" is of type uuid but expression is of type text

This can be fixed by explicitly casting in the values:

INSERT INTO example 
SELECT a, b 
FROM (VALUES ('d853b5a8-d453-11e7-9296-cec278b6b50a'::uuid, NULL::numeric)) as data(a,b);

But that's messy and a maintenance burden. Is there some way to make postgres understand that the VALUES expression has the same type as a table row, i.e. something like

VALUES('d853b5a8-d453-11e7-9296-cec278b6b50a', NULL)::example%ROWTYPE

Edit:

The suggestion of using (data::example).* is neat, but unfortunately it complete seems to screw up the postgres query planner when combined with a WHERE clause like so:

INSERT INTO example 
SELECT (data::example).* 
FROM (VALUES ('d853b5a8-d453-11e7-9296-cec278b6b50a', NULL)) as data 
WHERE NOT EXISTS (SELECT * FROM example 
                  WHERE (data::example) 
                  IS NOT DISTINCT FROM example);

This takes minutes with a large table.

2

There are 2 best solutions below

3
On BEST ANSWER

You can cast a record to a row type of your table:

INSERT INTO example 
SELECT (data::example).*
FROM (
    VALUES 
      ('d853b5a8-d453-11e7-9296-cec278b6b50a', NULL),
      ('54514c89-f188-490a-abbb-268f9154ab2c', 42)
) as data;

data::example casts the complete row to a record of type example. The (...).* then turns that into the columns defined in the table type example

0
On

You could use VALUES directly:

INSERT INTO example(a, b)
VALUES ('d853b5a8-d453-11e7-9296-cec278b6b50a', NULL);

DBFiddle Demo


Or just cast once:

INSERT INTO example(a, b)
SELECT a::uuid, b::numeric
FROM (VALUES ('d853b5a8-d453-11e7-9296-cec278b6b50a', NULL), 
             ('bb53b5a8-d453-11e7-9296-cec278b6b50a',1) ) as data(a,b);

DBFiddle Demo2

Note, please always explicitly define columns list.