【问题标题】:Sqlite3: Need to Cartesian On dateSqlite3:需要笛卡尔日期
【发布时间】:2009-11-02 20:13:26
【问题描述】:

我有一个表格,其中列出了在 sqlite3 数据库中玩过的游戏。 “日期时间”字段是游戏结束的日期时间。 “持续时间”字段是游戏持续的秒数。我想知道在过去 24 小时中,至少有 5 场比赛同时运行的百分比。我计算出在给定时间运行的游戏数量:

select count(*)
from games
where strftime('%s',datetime)+0 >= 1257173442 and
      strftime('%s',datetime)-duration <= 1257173442

如果我有一个简单的每秒(或每 30 秒或其他)列表的表格,我可以做一个像这样的有意识的笛卡尔积:

select count(*)
from (
  select count(*) as concurrent, d.second
  from games g, date d
  where strftime('%s',datetime)+0 >= d.second and
        strftime('%s',datetime)-duration <= d.second and
        d.second >= strftime('%s','now') - 24*60*60 and
        d.second <= strftime('%s','now')
  group by d.second) x
where concurrent >=5

有没有办法即时创建此日期表?或者我可以得到与此类似的效果,而无需实际创建一个新表,它只是本周所有秒数的列表?

谢谢

【问题讨论】:

    标签: sql sqlite


    【解决方案1】:

    首先,我想不出一种方法来解决您的问题,即通过动态创建表或不借助额外表来解决问题。对不起。

    我的建议是让您依赖静态 Numbers 表。

    使用以下格式创建一个固定表:

    CREATE TABLE Numbers (
        number INTEGER PRIMARY KEY
    );
    

    用 24 小时的秒数填充它 (24*60*60 = 84600)。我会使用任何脚本语言来使用插入语句来做到这一点:

    insert into numbers default values;
    

    现在 Numbers 表的数字从 1 到 84600。您的查询将它们修改为:

    select count(*)
      from (
            select count(*) as concurrent, strftime('%s','now') - 84601 + n.number second
              from games g, numbers n
             where strftime('%s',datetime)+0 >= strftime('%s','now') - 84601 + n.number and
                   strftime('%s',datetime)-duration <= strftime('%s','now') - 84601 + n.number
             group by second) x
     where concurrent >=5
    

    如果不使用程序语言,我认为这是你能做的最好的事情。

    【讨论】:

    • 当您不能使用递归 CTE/子查询因式分解时,数字表技巧很常见......
    • 是的,而且数字表在这个特定场景之外的用途更多。你总是可以限制它并只获得你需要的子集:select number from numbers limit 100; 这只是一个方便的构造......
    【解决方案2】:

    好问题!

    这是一个我认为的查询,它可以在不使用单独的表的情况下为您提供所需的内容。请注意,这是未经测试的(因此可能包含错误),并且我假设 datetime 是一个 int 列,具有 # of seconds 以避免大量的 strftime。

    select sum(concurrent_period) from (
      select min(end_table.datetime - begin_table.begin_time) as concurrent_period
      from (
        select g1.datetime, g1.num_end, count(*) as concurrent
        from (
          select datetime, count(*) as num_end
                 from games group by datetime
        ) g1, games g2
        where g2.datetime >= g1.datetime and
              g2.datetime-g2.duration < g1.datetime and
              g1.datetime >= strftime('%s','now') - 24*60*60 and
              g1.datetime <= strftime('%s','now')+0
      ) end_table, (
        select g3.begin_time, g1.num_begin, count(*) as concurrent
        from (
          select datetime-duration as begin_time,
                 count(*) as num_begin
                 from games group by datetime-duration
        ) g3, games g4
        where g4.datetime >= g3.begin_time and
              g4.datetime-g4.duration < g3.begin_time and
              g3.begin_time >= strftime('%s','now') - 24*60*60 and
              g3.begin_time >= strftime('%s','now')+0
      ) begin_table
      where end_table.datetime > begin_table.begin_time
            and begin_table.concurrent < 5
            and begin_table.concurrent+begin_table.num_begin >= 5
            and end_table.concurrent >= 5
            and end_table.concurrent-end_table.num_end < 5
      group by begin_table.begin_time
    ) aah
    

    基本思想是制作两张表:一张是每场比赛开始时的并发游戏数,另一张是结束时间的并发游戏数。然后将表格连接在一起,仅在并发游戏数超过 5 的“关键点”处取行。对于每个关键开始时间,取发生最快的关键结束时间,并希望给出至少有 5 场游戏正在运行的所有时间段同时进行。

    希望这不会太复杂而无济于事!

    【讨论】:

      【解决方案3】:

      Kevin 宁愿把我打到那里的妙语 (+1),但我会发布这个变化,因为它至少有一点不同。

      关键思想是

      • 将数据映射到具有时间和“极性”属性的事件流(=游戏开始或结束)
      • 记录每次活动时打开的游戏总数 (这是通过在事件流上形成自联接来完成的)
      • 找出游戏数量(如 Kevin 所说)最多变为 5 或变为 4 的事件时间
      • 一个小技巧:将所有 down-to-4 加起来,去掉 up-to-5 - 顺序并不重要
      • 结果是打开 5 个或更多游戏所花费的秒数

      我没有 sqllite,所以我一直在使用 MySQL 进行测试,并且我没有费心限制时间窗口以保持一些理智。修改应该不难。

      另外,更重要的是,我还没有考虑过如果游戏在期初或期末开放怎么办!

      有些事情告诉我这里有一个很大的简化,但我还没有发现它。

      SELECT SUM( event_time )  
      FROM (
      SELECT  -ga.event_type * ga.event_time AS event_time,
          SUM(  ga.event_type * gb.event_type ) event_type
      FROM
          ( SELECT UNIX_TIMESTAMP( g1.endtime - g1.duration ) AS event_time
                , 1 event_type
            FROM    games g1
            UNION
            SELECT UNIX_TIMESTAMP( g1.endtime )
                , -1
            FROM    games g1 ) AS ga,
          ( SELECT UNIX_TIMESTAMP( g1.endtime - g1.duration ) AS event_time
                , 1 event_type
            FROM    games g1
            UNION
            SELECT UNIX_TIMESTAMP( g1.endtime )
                , -1
            FROM    games g1 ) AS gb
      WHERE
          ga.event_time >= gb.event_time
      GROUP BY ga.event_time
      HAVING SUM( ga.event_type * gb.event_type ) IN ( -4, 5 )
      ) AS gr
      

      【讨论】:

        【解决方案4】:

        如果您过滤任何给定日期的数据,每次都是唯一的,您为什么不修剪日期并只保留时间。这样,您只需要一个数字从 1 到 86400 的表格(如果您采用更大的间隔,则更少),您可以创建两列“from”和“to”来定义间隔。 我不熟悉 SQLite 函数,但根据手册,您必须使用以下格式的 strftime 函数:HH:MM:SS。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-13
          • 2012-04-13
          • 1970-01-01
          • 2021-11-18
          相关资源
          最近更新 更多