The problem with your query is that the subquery is not correlated. You dodn't need and you shouldn't use lessons inside the subquery.
UPDATE lessons
SET minicourse_id = subquery.minicourse_id
FROM (
SELECT topics.minicourse_id
FROM topics
WHERE topics.id = lessons.topic_id -- this refers to the "lesson"
-- of the main query
) AS subquery ;
In fact, it can be written without a subquery at all:
UPDATE lessons
SET minicourse_id = topics.minicourse_id
FROM topics
WHERE topics.id = lessons.topic_id ;
or with a different subquery:
UPDATE lessons
SET minicourse_id
= ( SELECT minicourse_id
FROM topics
WHERE id = lessons.topic_id
) ;
Regarding your design, I assume you have added a lessons.minicourse_id column and this foreign key:
ALTER TABLE lessons
ADD minicourse_id INT ;
ALTER TABLE lessons
ADD FOREIGN KEY (minicourse_id)
REFERENCES minicourses (id) ;
While this achieves what you want, there is a small issue: you may end up with rows in child (lessons) that refer to a grandparent (minicourse) A and also to a parent (topics) that refers to a different grandparent B.
Of course if all your applications and users have code that is correct, this won't happen. But I suggest you enforce this in the database level and not (only) in the application level. This is quite easy to do, with the following change in the foreign keys.
It basically sets the FK to minicourses to be "through" lessons, without a direct FK. You'll still be able to use direct joins between lessons and minicourses:
ALTER TABLE lessons
ADD minicourse_id INT ;
-- the UPDATE is the same!
UPDATE lessons
SET minicourse_id = topics.minicourse_id
FROM topics
WHERE topics.id = lessons.topic_id ;
ALTER TABLE lessons
ALTER minicourse_id SET NOT NULL ;
-- we need this for the FK below
ALTER TABLE topics
ADD CONSTRAINT minicourse_topic_UQ
UNIQUE (minicourse_id, id) ;
-- the FK is "lessons -> topics"
ALTER TABLE lessons
ADD CONSTRAINT lessons_to_topics_FK2
FOREIGN KEY (minicourse_id, topic_id)
REFERENCES topics (minicourse_id, id) ;
-- drop the previous FK to topics
ALTER TABLE lessons
DROP CONSTRAINT lessons_to_topics_FK ;
Answer from ypercubeᵀᴹ on Stack ExchangePostgres allows:
UPDATE dummy
SET customer=subquery.customer,
address=subquery.address,
partn=subquery.partn
FROM (SELECT address_id, customer, address, partn
FROM /* big hairy SQL */ ...) AS subquery
WHERE dummy.address_id=subquery.address_id;
This syntax is not standard SQL, but it is much more convenient for this type of query than standard SQL. I believe Oracle (at least) accepts something similar.
You're after the UPDATE FROM syntax.
UPDATE
table T1
SET
column1 = T2.column1
FROM
table T2
INNER JOIN table T3 USING (column2)
WHERE
T1.column2 = T2.column2;
References
- Code sample here: GROUP BY in UPDATE FROM clause
- And here
- Formal Syntax Specification