【问题标题】:ROW_NUMBER() shows unexpected valuesROW_NUMBER() 显示意外值
【发布时间】:2012-08-02 21:23:08
【问题描述】:

我的表有如下值(RowCount 由下面的查询生成):

ID       Date_trans   Time_trans  Price  RowCount
-------  -----------  ----------  -----  --------
1699093  22-Feb-2011  09:30:00    58.07  1
1699094  22-Feb-2011  09:30:00    58.08  1
1699095  22-Feb-2011  09:30:00    58.08  2
1699096  22-Feb-2011  09:30:00    58.08  3
1699097  22-Feb-2011  09:30:00    58.13  1
1699098  22-Feb-2011  09:30:00    58.13  2
1699099  22-Feb-2011  09:30:00    58.12  1
1699100  22-Feb-2011  09:30:08    58.13  3
1699101  22-Feb-2011  09:30:09    57.96  1
1699102  22-Feb-2011  09:30:09    57.95  1
1699103  22-Feb-2011  09:30:09    57.93  1
1699104  22-Feb-2011  09:30:09    57.96  2
1699105  22-Feb-2011  09:30:09    57.93  2
1699106  22-Feb-2011  09:30:09    57.93  3
1699107  22-Feb-2011  09:30:37    58     1
1699108  22-Feb-2011  09:30:37    58.08  4
1699109  22-Feb-2011  09:30:38    58.08  5
1699110  22-Feb-2011  09:30:41    58.02  1
1699111  22-Feb-2011  09:30:41    58.02  2
1699112  22-Feb-2011  09:30:41    58.01  1
1699113  22-Feb-2011  09:30:41    58.01  2
1699114  22-Feb-2011  09:30:41    58.01  3
1699115  22-Feb-2011  09:30:42    58.02  3
1699116  22-Feb-2011  09:30:42    58.02  4
1699117  22-Feb-2011  09:30:45    58.04  1
1699118  22-Feb-2011  09:30:54    58     2
1699119  22-Feb-2011  09:30:57    58.05  1

ID 列是 IDENTITY 列。
我正在使用此查询来获取 连续行数

  SELECT   ID, Date_trans, Time_trans, Price
          ,ROW_NUMBER() OVER(PARTITION BY Price  ORDER BY ID) RowCount
  FROM     MyTable
  ORDER    BY ID;

我得到的RowCount 对于大多数值是正确的,但对于某些值是错误的。例如:

  • ID 1699100 价格 58.13 – 计数应为 1(显示 3)。
  • ID 1699104 价格 57.96 – 计数应为 1(显示 2)。
  • ID 1699105、1699106 价格 57.93 – 计数应为 1、2(显示 2、3)。

我在 PostgreSQL 中尝试了相同的查询并找到了相同的结果。
我已经上传了a csv data sample here

我对分区的这种意外结果感到困惑。有人可以帮帮我吗?

【问题讨论】:

  • 我认为您不太了解分析查询。 ID 1699100 的价格是 58.13,它是这个价格的第三高 ID,所以答案是正确的。你想做什么?计算特定价格的行数?
  • 我认为ID 1699100 Price 58.0 – count should be 1 (showing 3); 中的58.0 实际上应该是58.13(或者第1699097、1699098、1699100 行中的价格应该是58.0 而不是58.13)。
  • 我冒昧地根据@AndriyM 的调查结果修复了问题中的示例。如果我错了,请更正。

标签: sql sql-server aggregate-functions window-functions


【解决方案1】:

ROW_NUMBER() 函数的PARTITION BY 子句指示它对由Price 值设置的整个行进行分区,并按照IDs 的升序分配行号。 p>

您似乎想要区分具有相同Price 值的任意两组行,这些行被分隔至少一个具有不同Price 的行。

可能有多种方法可以实现这一目标。在 SQL Server 中(我认为在 PostgreSQL 中也是如此),我将首先使用两个 ROW_NUMBER() 调用来获取额外的分区标准,然后使用该标准再次对行进行排名,如下所示:

WITH partitioned AS (
  SELECT
    ID,
    Date_trans,
    Time_trans,
    Price,
    ROW_NUMBER() OVER (                   ORDER BY ID) -
    ROW_NUMBER() OVER (PARTITION BY Price ORDER BY ID) AS PriceGroup
  FROM MyTable
)
SELECT
  ID,
  Date_trans,
  Time_trans,
  Price,
  ROW_NUMBER() OVER (PARTITION BY Price, PriceGroup ORDER BY ID) AS RowCount
FROM partitioned
ORDER BY ID
;

这是SQL Fiddle demo

【讨论】:

    【解决方案2】:

    纯 SQL

    WITH x AS (
        SELECT id, date_trans, time_trans, price
             ,(price <> lag(price) OVER (ORDER BY id))::int AS step
        FROM   tbl
        )
        ,y AS (
        SELECT *, sum(step) OVER (ORDER BY id) AS grp
        FROM   x
        )
    SELECT id, date_trans, time_trans, price
          ,row_number() OVER (PARTITION BY grp ORDER BY id) As row_ct
    FROM   y
    ORDER  BY id;
    

    逻辑:

    1. 请记住,与step 中的最后一行相比,价格何时发生变化。 (第一行的特殊情况也可以。)
    2. 总结步骤,使顺序相同的价格最终在同一组中grp
    3. 每组的行数。

    老实说,我认为@Andriy's solution 更优雅一点。它也需要三个窗口函数,但只需两个查询步骤即可完成。在对小样本的快速测试中,它也稍微快了一点。所以,向我 +1。

    如果性能至关重要,那么更专业的解决方案具有

    PL/pgSQL 函数

    应该会快很多,因为它只需要扫描和排序表一次。

    CREATE OR REPLACE FUNCTION f_my_row_ct()
      RETURNS TABLE (
        id         int
       ,date_trans date
       ,time_trans time
       ,price      numeric
       ,row_ct     int
      ) AS
    $BODY$
    DECLARE
       _last_price numeric;   -- remember price of last row
    BEGIN
    
    FOR id, date_trans, time_trans, price IN 
       SELECT t.id, t.date_trans, t.time_trans, t.price
       FROM   tbl t
       ORDER  BY t.id
    LOOP
       IF _last_price = price THEN   -- works with 1st row, too
          row_ct := row_ct + 1;
       ELSE
          row_ct := 1;
       END IF;
    
       RETURN NEXT;
       _last_price = price;   -- remember last price
    END LOOP;
    
    END;
    $BODY$  LANGUAGE plpgsql;
    

    呼叫:

    SELECT * FROM f_my_row_ct()
    

    在对小样本的另一项快速测试中,速度提高了 3-4 倍。用EXPLAIN ANALYZE测试看看。


    顺便说一句:您可以通过将date_trans datetime_trans time 合并到ts_trans timestamp 中来简化表(和查询)并节省一些存储字节。

    使用演员表从timestamp 中提取datetime 非常简单快速:

    ts_trans::date
    ts_trans::time
    

    The manual about date/time types.

    【讨论】:

    • 这里有一堆好点,希望我能多次投票。
    • 非常感谢。实际上我正在寻找 MS SQL 中的解决方案;刚刚在 PostgreSQL 中测试了结果。
    • @Mainuddin:您知道您标记了问题 [PostgreSQL] 而不是 MS SQL?
    • @Mainuddin:也许你认为sql 标签的意思是“MS SQL”。事实并非如此。 SQL 是许多数据库产品中使用的一种语言的名称,“MS SQL”这个东西更准确地称为SQL Server。对于与该产品相关的问题,我们使用sql-server 标签。请牢记这一点,以备不时之需。
    • 是的,我一直在寻找sql server,但当时没有找到,并认为sqlsql server。谢谢。
    【解决方案3】:
    • 1699100 价格 58.0 - 显示 3,因为 1699097,8 是 1,2

    • 1699104 价格 57.96 – 显示为 2,因为 1669101 为 1。

    • 1699105, 1699106 价格 57.93 – 显示 2, 3,因为 1699103 是 1

    如果你想在一个序列中找到相同值的项目,一个选择是将数据连接到前一个ID,看看值是否相同

    【讨论】:

    • 这基本上是我评论的略微扩展版本,但并不能真正帮助 OP 实现他们想要做的任何事情。
    • 谁知道 OP 想要做什么?他们不说。
    • 为什么要回答这个问题:-)。
    • 问题是“为什么显示这些值”。这就是答案。您可以将“为什么回答这个问题”应用于此处的任何问题。
    【解决方案4】:

    根据您对结果的期望,我可以收集到,您也需要在 Time_trans 上进行分区:

      SELECT   ID, Date_trans, Time_trans, Price
               ,ROW_NUMBER() OVER(PARTITION BY Time_trans, Price ORDER BY ID) RowCount
      FROM     MyTable
      ORDER BY ID
    

    我相信情况就是这样,因为您希望当 Time-trans 值随着您处理数据而发生变化时,ROW_NUMBER 会重新开始。

    如果表中可能有多个日期,您可能也想在其中添加 Date_trans,这是我所期望的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-28
      • 1970-01-01
      • 1970-01-01
      • 2021-07-30
      • 1970-01-01
      • 2021-08-08
      相关资源
      最近更新 更多