【问题标题】:CQL (cassandra) - Select only the rows with maximum value in one of the columnsCQL (cassandra) - 仅选择其中一列中具有最大值的行
【发布时间】:2020-10-22 22:36:13
【问题描述】:

我需要找到具有给定 stationid 且 time1 大于指定时间和最大 time2 的行。

表是这样创建的:

CREATE TABLE forec (
    stationid int,
    time1 timestamp,
    time2 timestamp,
    value double,
    PRIMARY KEY ((stationid), time1, time2)
) WITH CLUSTERING ORDER BY (time1 DESC)

假设表中的数据是这样的:

    +------------+-----------------------+----------------------+--------+
    | stationid  | time1                 |  time2               |  value |
    +------------+-----------------------+----------------------+--------+
    | 1          | 2020-10-21 06:00:00   | 2020-10-21 05:00:00  | 1      |                                  
    | 1          | 2020-10-21 06:00:00   | 2020-10-21 04:00:00  | 2      |                                   
    | 1          | 2020-10-21 06:00:00   | 2020-10-21 03:00:00  | 3      |                                   
    | 1          | 2020-10-21 05:00:00   | 2020-10-21 04:00:00  | 4      |
    | 1          | 2020-10-21 05:00:00   | 2020-10-21 03:00:00  | 5      |
    | 1          | 2020-10-21 04:00:00   | 2020-10-21 02:00:00  | 6      |
    +------------+-----------------------+----------------------+--------+

我想查询: 给我所有 stationid = 1 和 time1 >= 2020-10-21 05:00:00 并且 time2 具有最大值的行。查询应返回以下行:

    +------------+-----------------------+----------------------+--------+
    | stationid  | time1                 |  time2               |  value |
    +------------+-----------------------+----------------------+--------+
    | 1          | 2020-10-21 06:00:00   | 2020-10-21 05:00:00  | 1      |        
    | 1          | 2020-10-21 05:00:00   | 2020-10-21 04:00:00  | 4      | 
    +------------+-----------------------+----------------------+--------+

我知道我可以这样查询:

SELECT * FROM forec WHERE stationid = 1 AND time1 >= '2020-10-21 05:00:00';

然后在客户端过滤结果(并仅保留具有最大时间的行2),但是我想知道这是否可以更有效地完成(在 Cassandra 端过滤结果)。

或者我应该改变表格模型?

【问题讨论】:

  • 并非如此 - CQL 的功能非常有限

标签: cassandra cql


【解决方案1】:

使用 UDA/UDF 的解决方案:

状态函数:

CREATE OR REPLACE FUNCTION curValState ( state tuple<timestamp,double>, time timestamp, value double ) CALLED ON NULL INPUT RETURNS tuple<timestamp, double> LANGUAGE java AS 'if (time != null && value != null) { if(state == null) {com.datastax.driver.core.TupleType tupleType = com.datastax.driver.core.TupleType.of(com.datastax.driver.core.ProtocolVersion.NEWEST_SUPPORTED, com.datastax.driver.core.CodecRegistry.DEFAULT_INSTANCE, com.datastax.driver.core.DataType.timestamp(), com.datastax.driver.core.DataType.cdouble()); state = tupleType.newValue(time, value);} else {if(state.getTimestamp(0).compareTo(time)<0){ state.setTimestamp(0, time); state.setDouble(1, value);}}} return state;';

最终功能:

CREATE OR REPLACE FUNCTION finalVal ( state tuple<timestamp, double> ) CALLED ON NULL INPUT RETURNS double LANGUAGE java AS 'return state.getDouble(1);';

聚合函数:

CREATE OR REPLACE AGGREGATE valueatlatesttime (timestamp, double) SFUNC curValState STYPE tuple<timestamp, double> FINALFUNC finalVal INITCOND null;

查询:

SELECT
  stationid,
  time1,
  max(time2) AS max_time2,
  valueatlatesttime(time2, value) AS value_at_max_time2
FROM
  forec
WHERE
  stationid = 1
AND
  time1 >= '2020-10-21 05:00:00'
GROUP BY time1;

【讨论】:

    【解决方案2】:

    编辑:根据Cassandra document,“如果在没有聚合函数的情况下选择了列,则在带有 GROUP BY 的语句中,将返回每个组中遇到的第一个值。”因此,下面的示例仅在 time2DESC 顺序存储时有效。

    如果您使用的是最新版本的 Cassandra(如 3.11.x),那么您可以使用 GROUP BY 来执行类似的操作

    SELECT
      stationid,
      time1,
      max(time2) AS max_time2,
      value
    FROM
      forec
    WHERE
      stationid = 1
    AND
      time1 >= '2020-10-21 05:00:00'
    GROUP BY time1;
    

    你得到

    cqlsh:test> SELECT stationid, time1, max(time2) as max_time2, value FROM forec WHERE stationid = 1 AND time1 >= '2020-10-21 05:00:00' GROUP BY  time1;
    
     stationid | time1                           | max_time2                       | value
    -----------+---------------------------------+---------------------------------+-------
             1 | 2020-10-21 06:00:00.000000+0000 | 2020-10-21 05:00:00.000000+0000 |     1
             1 | 2020-10-21 05:00:00.000000+0000 | 2020-10-21 04:00:00.000000+0000 |     4
    
    (2 rows)
    

    请注意,这会扫描您的分区,因此请注意分区大小,尤其是当您在集群列中使用时间戳时。

    【讨论】:

    • 我已经尝试过了 - 但是似乎不能保证该值是“正确的”(属于组中最大时间 2 的那个)。根据文档 (cassandra.apache.org/doc/latest/cql/dml.html):如果在没有聚合函数的情况下选择了列,则在具有 GROUP BY 的语句中,将返回每个组中遇到的第一个值。第一个值可能不是“正确的”......我错过了什么吗?
    • 也许可以定义将应用于“值”列(在 GROUP BY 之后)的 UDA/UDF,以便检索属于每个组中最大时间 2 的值?有人知道怎么做吗?
    • 是的,你是对的,我用 time2 DESC 进行了测试,但是当它的顺序是 ASC 时,value 是第一个遇到的值,正如 cassandra 文档指出的那样。如果您可以将 time2 的顺序更改为 DESC,那么您将始终在 time1 组的第一行中拥有 time2 的最大值,并且可以获得与其关联的 value 列。
    • 如果 time2 被订购为 DESC 并且只要 GROUP BY 保持原始的行顺序(显然它确实如此),您的解决方案就可以工作。在这种情况下,您可以省略 time2 的最大值 - time2 的“第一次遇到”值将是“正确的”(即最大值) - 与“值”相同的方式。如果 time2 的顺序不能更改为 DESC,我想出了一个使用 UDF/UDA 的解决方案 - 答案中提供了解决方案...
    猜你喜欢
    • 2013-07-15
    • 2021-07-23
    • 2018-10-06
    • 2011-12-06
    相关资源
    最近更新 更多