1

I am writing an UPDATE sql query within Postgres 12.

The query is very complex and it needs to contain JOIN and CASE in order to work. I can not get my head around it so far. I need to update an approval_status column on the comment_response table.

The team_member_manager table indicates who needs to approve comments and they are only valid ones (even if there are more others in the post_comment_response_approval)

There are 4 Cases I need:

  • If everyone who needs to submit responses has submitted an approval, then the status - should be approved.

  • If there are no associated team_member_manager records, the status also should be approved.

  • If anyone has rejected it, the status should be rejected.

  • Otherwise, the status should be pending.

Here are table structures with right values.

post_comment_response table:

id post_comment_id comment approval_status
1 1173 Hello World NULL

post_comment table:

id post_id
1173 652

post table:

id message_id team_member_id
652 110 60735

team_member_manager table:

id managing_team_member_id managed_team_member_id
55 68893 60735
56 68893 60736

team_member table:

id team_id member_id
68893 91 1

post_comment_response_approval table:

id post_comment_response_id team_member_id approved note
54 1 60735 true This one should be included
70 1 666 true This should not
70 1 60736 false This should be included

NOTE: I'am defining how is manager and who is managed member by doing JOIN managing_team_member_id is the one with defined value (91) managed_team_member_id value I want to get by JOINing post_comment → post

My sql statement and JOINs are working as it should but I got stuck on how to implement them properly with CASEs. Can someone please help?

UPDATE post_comment_response pcr
SET
approval_status = CASE WHEN 'first case'= 'approved'
                  WHEN 'second case' = 'approved',
                  WHEN 'third case' = 'rejected'
                  ELSE 'pending'
JOIN post_comment pc ON pc.id = 1173
JOIN post p ON p.id = pc.post_id
JOIN team_member_manager tmm ON tmm.managed_team_member_id = p.team_member_id
JOIN team_member mgr ON mgr.id = tmm.managing_team_member_id
WHERE mgr.team_id = 91;
jabepa
  • 61
  • 5
  • 1/ your first `JOIN`should be replaced by `FROM` 2/ `ON pc.id = 1173` is not sql ompliant, to be replaced by `AND pc.id = 1173` in the `WHERE` clause 3/ The table `post_comment_response_approval` doesn't appear in the `JOIN` clause, is it an omission ? – Edouard Oct 30 '22 at 20:38
  • In which table to you see that there are zero, one or more managers who have to approve a given comment ? – Edouard Oct 30 '22 at 20:40
  • I know, this is just the basic example of how I would implement the idea but as I am pretty new at this do not know who to finish it. Can you please help? Thanks for replaying @Edouard – jabepa Oct 30 '22 at 21:23
  • In the table by one sql execution there can be one managing_team_member but can be related to various managed_team_member... @Edouard – jabepa Oct 30 '22 at 21:27
  • 2
    Difficult to follow you; it would be valuable if you can add several rows in the different tables so that to illustrate the 4 cases you want to implement. – Edouard Oct 30 '22 at 22:11
  • I've added more rows in team_member_manager and team_member_manager_approval tables.. So by the input i provided, bassucly if all related rows from team_member_manager_approval are approved then approve, if there are no rows also approve, if even one of them is false, then reject, otherwise pending.. @Edouard – jabepa Oct 31 '22 at 08:58

1 Answers1

1

"if all related rows from team_member_manager_approval are approved then approve, if even one of them is false, then reject"

To get this result, you need to use the aggregate function bool_and applied to the column approved of table post_comment_response_approval while grouping the rows on post_comment_response.id

"if there are no rows also approve"

To get this result, you need to LEFT JOIN the table post_comment_response_approval with other tables so that if no row matches, then a NULL value will be populated for the columns coming from that table.

Then you can use a CASE WHEN ... THEN ... END statement to convert the boolean result of bool_and into a text : true => 'approve', false => 'reject'. If no row exists in table post_comment_response_approval then bool_and(...) will return NULL and COALESCE(bool_and(...), true) will return true so that to select the 'approve' value.

As you can't use an aggregate function directly in the SET clause of an UPDATE, then you have to implement the query in a cte, and to refer to the cte result in the UPDATE.

Starting from your data sample, and trying to follow your business logic, the solution to update the approval_status in the post_comment_response table and which corresponds to post_comment = 1173 and team_member.team_id = 91 is :

WITH list AS (
SELECT 
       pcr.id, pcr.post_comment_id, pcr.comment
     , CASE WHEN COALESCE(bool_and(pcra.approved), true) THEN 'approve' WHEN NOT bool_and(pcra.approved) THEN 'reject' ELSE 'pending' END AS approval_status
  FROM post_comment_response pcr
  LEFT JOIN post_comment_response_approval pcra ON pcra.post_comment_response_id = pcr.id
  JOIN team_member_manager tmm ON tmm.managed_team_member_id = pcra.team_member_id
  JOIN team_member tm ON tm.id = tmm.managing_team_member_id
 WHERE pcr.post_comment_id = 1173
   AND tm.team_id = 91
  GROUP BY pcr.id, pcr.post_comment_id, pcr.comment
)
UPDATE post_comment_response pcr
   SET approval_status = l.approval_status
  FROM list l
 WHERE pcr.id = l.id ;

But you can also update all the rows of the post_comment_response table at once with the following query :

WITH list AS (
SELECT 
       pcr.id, pcr.post_comment_id, pcr.comment
     , CASE WHEN COALESCE(bool_and(pcra.approved), true) THEN 'approve' WHEN NOT bool_and(pcra.approved) THEN 'reject' ELSE 'pending' END AS approval_status
  FROM post_comment_response pcr
  LEFT JOIN post_comment_response_approval pcra ON pcra.post_comment_response_id = pcr.id
  JOIN team_member_manager tmm ON tmm.managed_team_member_id = pcra.team_member_id
  JOIN team_member tm ON tm.id = tmm.managing_team_member_id
  JOIN post_comment pc ON pc.id = pcr.post_comment_id
  JOIN post p ON p.id = pc.post_id
  JOIN team_member_manager tmm2 ON tmm2.managed_team_member_id = p.team_member_id AND tmm2.managing_team_member_id = tm.id
 GROUP BY pcr.id, pcr.post_comment_id, pcr.comment
)
UPDATE post_comment_response pcr
   SET approval_status = l.approval_status
  FROM list l
 WHERE pcr.id = l.id ;

see the test result in dbfiddle

Edouard
  • 6,577
  • 1
  • 9
  • 20
  • A load of thanks for this. I saved your answer as I need to get into it a little more in detail in order to figure out everything you did. Thanks a lot for your time and effort! I tested it on my side, it works! @Edouard – jabepa Oct 31 '22 at 12:08
  • And also this CASE covers all cases except one. On first one we are returning approved even if the field is NULL.. Which is okay but we also need a CASE where we want to set approved even if no row was SELECTED (If there are no associated team_member_manager records) @Edouard – jabepa Oct 31 '22 at 13:12
  • in order to set to 'approve' when no rows selected from `team_member_manager ` you can try to `LEFT JOIN` the table `team_member_manager` – Edouard Oct 31 '22 at 13:47
  • It is already JOINed? Did you mean to use another SELECT with JOIN in the separate CASE? @Edouard – jabepa Oct 31 '22 at 14:01
  • No just put `LEFT JOIN team_member_manager tmm ON tmm.managed_team_member_id = pcra.team_member_id` in the `FROM` clause instead of `JOIN team_member_manager tmm ON tmm.managed_team_member_id = pcra.team_member_id` – Edouard Oct 31 '22 at 14:36
  • I am not sure I understand? To put LEFT JOIN in FROM clause? @Edouard – jabepa Oct 31 '22 at 14:41
  • 1
    yes just replace `JOIN` by `LEFT JOIN` in front of `team_member_manager tmm ` – Edouard Oct 31 '22 at 14:49