Использование Row_Number() с инструкцией Update

#postgresql

#postgresql

Вопрос:

Надеюсь, просто быстрый вопрос. У меня возникли некоторые трудности с использованием row_number() в инструкции UPDATE.

Предполагая, что у меня есть таблица bookshelf:

 userid  createddate bookid  pagenumber
1       2021-18-01  charper    141
1       2021-17-01  mproust    136
1       2021-17-01  sking       134
1       2021-15-01  charper    128
1       2021-10-01  jausten    122
2       2021-18-01  vwoolf    141
2       2021-17-01  vwoolf    136
2       2021-17-01  charper    134
 

Я хочу (суммировать) тот же номер страницы bookid и записать в правый столбец. Мой ожидаемый результат должен быть таким:

 userid  createddate      bookid pagenumber  countrow
1         2021-18-01    charper  141        269
1         2021-17-01    mproust  136        136
1         2021-17-01    sking    134        134
1         2021-15-01    charper  128        
1         2021-11-01    jausten  122        122
2         2021-18-01    vwoolf   141        277
2         2021-17-01    vwoolf   136        
2         2021-17-01    charper  134        134
 

Мой оператор выбора

 SELECT userid, bookid, pagenumber,
       case when row_number() over (partition by userid, bookid order by bookid) = 1 
            then sum(pagenumber) over (partition by userid, bookid) 
       end as countrow
  from usertable;
 

Приведенный выше sql-запрос очень хорошо работает для отображения countrow в столбце temp. Я хочу сохранить все эти выходные данные. Итак, я изменил оператор на такой:

 UPDATE bookshelf
SET countrow = countrow_new
FROM ( select userid, createddate, bookid, pagenumber,
       case when row_number() over (partition by userid, bookid, order by bookid) = 1
            then sum(pagenumber) over (partition by userid, bookid) 
       end as countrow_new
  from bookshelf
      )x
 

Похоже, этот запрос не работает должным образом. Он обновляет все ячейки в countrow с неправильными значениями. Я бы поискал в Google и нашел что-то полезное, например, SqlHack Rowcount и справочную страницу SQLServer, но ничего особенного. Кто-нибудь имеет представление о том, что здесь происходит, или у кого-нибудь была подобная проблема?
Как это исправить, чтобы получить ожидаемый результат?

Комментарии:

1. Основываясь на ссылках в вашем вопросе, я добавил тег SQL Server. Пожалуйста, помечайте вопросы с базой данных, которую вы используете.

2. SQL Server или Postgres? Они сильно отличаются, когда дело доходит до обновлений с предложением FROM .

Ответ №1:

Вы пытаетесь обновить столбец ( countrow ), которого нет в вашей таблице, поэтому вам нужно будет добавить этот столбец в исходную таблицу и присоединиться к подзапросу:

 /* add the column */
ALTER TABLE usertable ADD countrow INT;

/* do the update */
UPDATE  usertable
SET     countrow = countrow_new
FROM ( 
        SELECT  userid, 
                createddate, 
                bookid, 
                pagenumber,
                CASE 
                    WHEN ROW_NUMBER() OVER (PARTITION BY userid, bookid ORDER BY bookid) = 1
                    THEN SUM(pagenumber) OVER (PARTITION BY userid, bookid) 
                END AS countrow_new
        FROM    usertable
      ) x
      JOIN usertable u
        ON u.bookid = x.bookid AND u.createddate = x.createddate AND u.userid = x.userid;
 

Вышеуказанное предполагает {bookid,createddate,userid} , что оно всегда будет уникальным

Однако хранение вычислений в таблице таким образом является очень плохим дизайном и плохой практикой — вам придется обновлять столбец с помощью триггера при каждой вставке / обновлении / удалении, что будет ужасно для производительности.

Лучшим подходом было бы взять ваш исходный запрос и создать для него представление, которое затем можно использовать, как если бы это была таблица:

создайте представление:

 CREATE VIEW vwMyView
AS
SELECT  userid, 
        bookid, 
        pagenumber,
        CASE 
            WHEN ROW_NUMBER() OVER (PARTITION BY userid, bookid ORDER BY bookid) = 1 
            THEN SUM(pagenumber) OVER (PARTITION BY userid, bookid) 
        END AS countrow
FROM    usertable;
 

Выберите из представления:

 SELECT * FROM vwMyView;
 

Ответ №2:

Я не тестировал это, но я думаю, вам нужно выполнить обновление join. Я думаю, вам может понадобиться дополнительный фильтр — RN — для обновления только последней записи bookid.

 WITH latestreadtotal AS
(
         SELECT   userid,
                  createddate,
                  bookid,
                  pagenumber,
                  row_number() OVER (partition BY userid, bookid order by bookid) AS rn,
                  CASE
                           WHEN row_number() OVER (partition BY userid, bookid, ORDER BY bookid) = 1 THEN sum(pagenumber) OVER (partition BY userid, bookid)
                  END AS countrow_new
         FROM     bookshelf )
UPDATE bsf
FROM   bookshelf bsf
JOIN   latestreadtotal lrt
ON     bsf.userid = lrt.userid
AND    bsf.createddate = lrt.createddate
AND    bsf.bookid = lrt.bookid
AND    bsf.pagenumber = lrt.pagenumber
AND    lrt.rn = 1
 

Ответ №3:

Я думаю, вам просто нужно изменить update на x :

 UPDATE x
    SET countrow = countrow_new
    FROM (select userid, createddate, bookid, pagenumber,
                 (case when row_number() over (partition by userid, bookid, order by bookid) = 1
                       then sum(pagenumber) over (partition by userid, bookid) 
                  end )as countrow_new
          from bookshelf
         ) x;
 

Ваш код ссылается bookshelf в подзапросе. Однако результат вызывается x . Вероятно, это означает, что bookshelf in update считается отдельной ссылкой. Я должен признать, что SQL Server имеет тайные правила о разрешении ссылок на таблицы в update s. Обычно я просто использую CTE для таких преобразований с одной таблицей:

 WITH toupdate AS (
      select userid, createddate, bookid, pagenumber,
             (case when row_number() over (partition by userid, bookid, order by bookid) = 1
                   then sum(pagenumber) over (partition by userid, bookid) 
              end )as countrow_new
      from bookshelf
     )
UPDATE toupdate
    SET countrow = countrow_new;