1

I'm new to PostgreSQL (and even Stackoverflow).
Say, I have two tables Order and Delivery:

Order

id     product      address           delivery_id
--------------------------------------------------
1      apple        mac street        (null)
3      coffee       java island       (null)
4      window       micro street      (null)

Delivery

id     address
----------------

Delivery.id and Order.id are auto-incrementing serial columns.
The table Delivery is currently empty.

I would like to move Order.address to Delivery.address and its Delivery.id to Order.delivery_id to arrive at this state:

Order

id     product      address           delivery_id
--------------------------------------------------
1      apple        mac street        1
5      coffee       java island       2
7      window       micro street      3

Delivery

id     address
---------------------
1      mac street
2      java island
3      micro street

I'll then remove Order.address.

I found a similar question for Oracle but failed to convert it to PostgreSQL:

I still think it should be possible to use a plain SQL statement with the RETURNING clause and a following INSERT in Postgres.

I tried this (as well as some variants):

WITH ids AS (
    INSERT INTO Delivery (address)
    SELECT address
    FROM Order
    RETURNING Delivery.id AS d_id, Order.id AS o_id
)
UPDATE Order
SET Delivery_id = d_id
FROM ids
WHERE Order.id = ids.o_id;

This latest attempt failed with:

ERROR: missing FROM-clause entry for table "Delivery" LINE 1: ...address Order RETURNING Delivery.id...

How to do this properly?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
dobyek
  • 109
  • 1
  • 8

2 Answers2

4

First of all, ORDER is a reserved word. Don't use it as identifier. Assuming orders as table nae instead.

WITH ids AS (
   INSERT INTO delivery (address)
   SELECT DISTINCT address
   FROM   orders
   ORDER  BY address -- optional
   RETURNING *
   )
UPDATE orders o
SET    delivery_id = i.id
FROM   ids i
WHERE  o.address = i.address;

You have to account for possible duplicates in order.address. SELECT DISTINCT produces unique addresses.

In the outer UPDATE we can now join back on address because delivery.address is unique. You should probably keep it that way beyond this statement and add a UNIQUE constraint on the column.

Effectively results in a one-to-many relationship between delivery and orders. One row in delivery can have many corresponding rows in orders. Consider to enforce that by adding a FOREIGN KEY constraint accordingly.

This statement enjoys the benefit of starting out on an empty delivery table. If delivery wasn't empty, we'd have to work with an UPSERT instead of the INSERT. See:

Related:

About the cause for the error message you got:

Use legal, lower-case identifiers exclusively, if you can. See:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • This logic fails if the addresses aren't guaranteed to be unique, I think? – Don R May 03 '21 at 00:09
  • @DonR: This statement is designed to deal with duplicate addresses. How would you expect it to fail? – Erwin Brandstetter May 03 '21 at 00:17
  • I was, perhaps mistakenly, assuming the OP wanted a 1-1 relationship; I ought to have asked for clarification before proceeding on that assumption. – Don R May 03 '21 at 00:41
  • In fact, I intended 1-1 relationship, you assumed correcly @DonR. Because there is much more columns in real ```Delivery``` table. It's my fault. I had to more clarify my question. – dobyek May 03 '21 at 00:44
  • However anyway, @ErwinBrandstetter 's answer is also super helpful. If someone see this post and is looking for a measure for 1-many relationship this will be the best answer for him/her. – dobyek May 03 '21 at 00:48
1

You can't return columns from the FROM relation in the RETURNING clause of the CTE query. You'll have to either manage this in a cursor, or add an order_id column to the Delivery table, something like this:

ALTER TABLE Delivery ADD COLUMNN order_id INTEGER:

INSERT INTO Delivery (address, order_id)
    SELECT address, id
    FROM Order
;

WITH q_ids AS
(
    SELECT id, order_id
    FROM Delivery
)
UPDATE Order
SET delivery_id = q_ids.id
FROM q_ids
WHERE Order.id = q_ids.order_id;
Don R
  • 573
  • 4
  • 10