【问题标题】:Fast way to query latest record?快速查询最新记录的方法?
【发布时间】:2011-05-23 10:25:56
【问题描述】:

我有一张这样的桌子:

USER |  PLAN |  START_DATE  |   END_DATE
1    |  A    |  20110101    |   NULL
1    |  B    |  20100101    |   20101231
2    |  A    |  20100101    |   20100505

如果END_DATEnull,则表示该用户当前有该计划。

我想查询的是: (a) 他目前正在实施的计划,或 (b) 他参与的最新计划。我只需要为每个给定用户返回一行。

现在,我设法通过使用联合和子查询来做到这一点,但碰巧表很大,而且效率不够高。 你们中的任何人有更快的方法来查询吗?

谢谢,

[编辑] 这里的大多数答案都返回一个值。那是我的错。我的意思是为每个用户返回一个值,但同时返回所有用户。我已经尽可能地调整了答案(并更正了问题),但只是明确说明以供将来参考。

【问题讨论】:

  • 你在查询的列上有索引吗?
  • 有点。我有我需要的索引并且确实改进了查询,这只是我使用的逻辑让我感到困惑......真的认为这应该是更好的方法。
  • 你有关于 user 和 end_date 的复合索引吗?

标签: sql performance oracle


【解决方案1】:

如果没有关于数据和表格的更多信息,这个问题有点难以回答。当您在评论中说您拥有所需的所有索引时,这些索引是什么?

另外,时间段是否相邻且不重叠?你能得到最晚 START_DATE 的时间段吗?

查看 END_DATE 的问题是普通的 B-Tree 索引不索引 NULL。因此,where end_date is nulll 形式的谓词不太可能使用索引。您可以对列使用位图索引,因为这些类型的索引会索引空值,但由于位图索引的其他一些缺点,这可能并不理想。

由于上述原因,我可能会使用类似于以下的查询:

select user, plan, start_date, end_date
from (
  select 
    user, 
    plan, 
    start_date, 
    end_date, 
    row_number() over (partition by user order start_date desc) as row_num_1,
    row_number() over (partition by user order end_date desc nulls first) as row_num_2
  from user_table
  where user = :userid
)
where row_num_1 = 1

您可以根据具体要求在此处使用row_num_1row_num_2 列。

select user, plan, start_date, end_date
from (
  select 
    user, 
    plan, 
    start_date, 
    end_date, 
  from user_table
  where user = :userid
  order by start_date desc
)
where rownum = 1

无论您是尝试让所有用户返回还是仅返回一个,第一个查询都应该有效。第二个查询仅适用于一个用户。

如果您可以使用架构的更多详细信息(索引、开始/结束日期的含义)来扩充问题,您可能会得到更好的答案。

【讨论】:

  • 经过一点阅读后,我上面关于索引和 NULL 的评论似乎并不完全正确。如果给定索引的所有列值都是 NULL,则不会对 NULL 进行索引。因此,例如,仅 END_DATE 上的索引不会索引 NULL,但 {USER, PLAN, END_DATE} 上的索引会索引 NULL(可能。我认为。)
  • 唷。好点子。整个信息将使问题变得更加复杂。抱歉过于简单化了。我只是认为我的问题是带有子查询和联合的丑陋结构,而不是底层结构。只是为了提供一些细节,没有实际的“用户”表,这已经是两个大型实体的子查询表连接。我有我在连接中使用的所有列和日期(结束日和星日)的索引(bthrees)。谢谢你的好评论!哦,请注意我的最后一次编辑,我正在寻找一次检索所有用户和计划
  • @filippo 我刚刚在我的答案中添加了一些进一步的细节。如果您需要,第一个查询应该适用于所有用户。我还添加了一个选项,您可以使用end_date 而不是start_date。我想很难将一些问题简化到足以提出合理问题的程度。我猜你已经改变了一些东西,因为USER 甚至不是一个有效的列名,因为它是一个保留字。
  • 嗨。我选择你的正确。实际上,来自 srnu 的选项非常快,但我没有时间根据我的需要正确实施它。此外,这里有更多人发布与您相同的答案,但您有更好的解释。谢谢!
  • @filippo - 刚刚看了 srnu 的答案。我们的两个答案都应该给出大致相同的结果,并且在性能方面应该相似。 DENSE_RANKROW_NUMBER 之间的区别在于行号将是严格唯一的。例如,如果给定用户有两行具有相同的结束日期(我猜你不应该),那么ROW_NUMBER 将编号那些 1 和 2(并且可能是不确定的),但 DENSE_RANK将它们都编号为 1 并返回它们。我认为NULLS FIRST 也是完成这项工作所必需的,因为它已经完成了当前的计划。
【解决方案2】:
CREATE TABLE XY
( USERID      INTEGER                 NOT NULL
, PLAN        VARCHAR2(8)             NOT NULL
, START_DATE  DATE                    NOT NULL
, END_DATE    DATE                    )
  TABLESPACE USERS;


INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 1, 'A', To_Date('22-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), To_Date('22-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS') );
INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 1, 'B', To_Date('01-04-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), NULL );
INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 2, 'A', To_Date('03-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), To_Date('04-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS') );
INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 2, 'B', To_Date('15-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), To_Date('20-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS') );
COMMIT WORK;

SELECT USERID, PLAN, END_DATE, START_DATE
  FROM (SELECT USERID,
               PLAN,
               END_DATE,
               START_DATE,
               ROW_NUMBER() OVER(PARTITION BY USERID ORDER BY END_DATE DESC) SEQUEN
          FROM XY)
 WHERE SEQUEN < 2

【讨论】:

  • 谢谢。这和 MikeyByCrikey 说的差不多。他只是得到了一个更长的解释。
【解决方案3】:

这可能会有所帮助:

SELECT user,plan,end_date,start_date 
FROM ( SELECT users,plans,end_date,start_date, DENSE_RANK() OVER ( PARTITION BY user 
                                                                   ORDER BY end_date DESC) sequen 
        FROM table_name 
     ) 
WHERE sequen <= 2

【讨论】:

  • 是的.. 这确实很快,但让我研究了 dense_rak 的东西。你介意提供更多关于你在这里所做的事情的细节吗? (对不起,新手!)谢谢
  • 可能在ORDER BY 子句中加上NULLS FIRST 会更清楚。
  • 不应该 sequen 严格
  • @APC,太坏了小伙子。我做我的谷歌搜索,请注意。不明白为什么在简短的回答中询问细节会得到如此粗鲁的反馈。标记。
【解决方案4】:

您是否尝试使用rownum 限制结果集?

select  plan
from    (
        select  plan
        from    YourTable
        where   User = 1
        order by
                case when end_date is null then '99991231' else end_date end desc
        )
where   rownum < 2

【讨论】:

    【解决方案5】:

    AFAIK 使用CASE 和子查询将导致您的查询变得非常缓慢。所以最好小心使用它们。怎么样:

    SELECT User, Plan, start_Date, MAX(End_Date) FROM Plans WHERE User NOT IN 
    (SELECT User FROM Plans WHERE End_Date IS NULL)
    GROUP BY Start_Date, Plan, User  
    UNION  
    SELECT User,Plan,Start_Date FROM Plans WHERE End_Date IS NULL
    

    我不是 SQL 专家。这只是一个建议。
    希望这会有所帮助。

    【讨论】:

    • Funny :) 这正是我目前使用的选择......不幸的是,这似乎太慢了。我真的可以用更少的子查询来做到这一点:/
    • 哇。我应该在发布之前阅读我自己的评论,糟糕的拼写,对不起。我正在尝试人们在此处发布的大多数选项,我将设置为最适合的答案。
    • @filippo:谢谢。以后可能会很有帮助。
    【解决方案6】:

    这行得通吗?

    SELECT U.user 
    ,(SELECT Plan FROM t WHERE t.user=u.user AND end_date IS NULL LIMIT 1) AS Current_Plan
    ,(SELECT Plan FROM t WHERE t.user=u.user AND end_date IS NOT NULL ORDER BY end_date DESC LIMIT 1) AS Last_Plan
    FROM 
    ( SELECT DISTINCT USER FROM t ) AS U
    

    如果速度很慢,请将查询的 EXPLAIN 输出发送给我们。

    【讨论】:

      【解决方案7】:

      这个怎么样?

      select PLAN
      from USER_TABLE
      where END_DATE is null or END_DATE = (
              select max(END_DATE)
              from USER_TABLE
              where USER = 1 and END_DATE is not null)
          and USER = 1
      

      【讨论】:

        【解决方案8】:

        我建议如下:

        with t as 
        (select 1 as col_id, 1 as USER_id, 'A' as PLAN , 20110101 as START_DATE, NULL as  END_DATE from dual union all
         select 2,1,'B', 20100101,20101231 from dual union all
         select 3,2,'A', 20100102,20100505 from dual union all
         select 4,2,'C', 20100101,20100102 from dual)
        --
        SELECT user_id, plan
          FROM (SELECT user_id,
                       plan,
                       MAX(nvl(END_DATE, 99999999)) over(PARTITION BY user_id) max_date,
                       nvl(END_DATE, 99999999) END_DATE
                  FROM t)
         WHERE max_date = end_date
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-09
          • 1970-01-01
          • 1970-01-01
          • 2020-11-12
          • 1970-01-01
          • 1970-01-01
          • 2012-11-05
          • 1970-01-01
          相关资源
          最近更新 更多