Sqlite /填充对现有行进行排名的新列

Sqlite / populate new column that ranks the existing rows

我有一个 SQLite 数据库 table,其中包含以下列:

| day         | place | visitors |
-------------------------------------
|  2021-05-01 | AAA   |   20 |
|  2021-05-01 | BBB   |   10 |
|  2021-05-01 | CCC   |    3 |
|  2021-05-02 | AAA   |    5 |
|  2021-05-02 | BBB   |    7 |
|  2021-05-02 | CCC   |    2 |

现在介绍一列'rank',表示每天访问量的排名。预期 table 看起来像:

| day         | place | visitors | Rank  |
------------------------------------------
|  2021-05-01 | AAA   |   20     |  1    |
|  2021-05-01 | BBB   |   10     |  2    |
|  2021-05-01 | CCC   |    3     |  3    |
|  2021-05-02 | AAA   |    5     |  2    |
|  2021-05-02 | BBB   |    7     |  1    |
|  2021-05-02 | CCC   |    2     |  3    |

填充新列 Rank 的数据可以使用类似(伪代码)的程序来完成。

for each i_day in all_days:
    SELECT
    ROW_NUMBER () OVER (ORDER BY `visitors` DESC) Day_Rank, place
    FROM mytable
    WHERE `day` = 'i_day'
    
    for each i_place in all_places:
        UPDATE mytable 
        SET rank= Day_Rank
        WHERE `Day`='i_day'
        AND place = 'i_place'

由于这种逐行更新的效率很低,我正在搜索如何使用 SQL 子查询结合 UPDATE 来优化它。

(目前还不行...)

for each i_day in all_days:
    UPDATE mytable
    SET rank= (
    SELECT
        ROW_NUMBER () OVER (ORDER BY `visitors` DESC) Day_Rank
        FROM mytable
        WHERE `day` = 'i_day'
        )

通常,这可以通过子查询来完成,该子查询计算 visitors 大于当前行的 visitors 值的行数:

UPDATE mytable
SET Day_Rank = (
  SELECT COUNT(*) + 1
  FROM mytable m 
  WHERE m.day = mytable.day AND m.visitors > mytable.visitors 
);

请注意,如果 visitors.

的值有联系,结果实际上是 RANK() 会 return 的结果

参见demo

或者,您可以在 CTE 中使用 ROW_NUMBER() 计算排名并将其用于子查询:

WITH cte AS (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY day ORDER BY visitors DESC) rn
  FROM mytable
)
UPDATE mytable
SET Day_Rank = (SELECT rn FROM cte c WHERE (c.day, c.place) = (mytable.day, mytable.place));

demo.

或者,如果您的 SQLite 版本是 3.33.0+,您可以使用类似连接的 UPDATE...FROM... 语法:

UPDATE mytable AS m
SET Day_Rank = t.rn
FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY day ORDER BY visitors DESC) rn
  FROM mytable
) t
WHERE (t.day, t.place) = (m.day, m.place);