代码之家  ›  专栏  ›  技术社区  ›  Jimmy

SQL查询-选择组中的“上次更新”记录,更好的数据库设计?

  •  1
  • Jimmy  · 技术社区  · 15 年前

    假设我有一个MySQL数据库,有三个表:

    表1:人员,1列ID(int)
    表2:新闻稿,列ID为1(int)
    表3:订阅,包含person_id(int)、newsletter_id(int)、subscribed(bool)、updated(datetime)列

    subscriptions.person_id指向一个人,subscription.newsletter_id指向一个newsletter。因此,每个人可以同时订阅0或多个杂志。表格订阅还将存储每个人订阅每个时事通讯的整个历史记录。如果一个特定的人的“id-newsletter”对在订阅表中没有行,那么它就相当于订阅状态为“false”的那一对。

    这是一个示例数据集

    Persons
    ID
    1
    2
    3
    
    Newsletters
    ID
    1
    2
    3
    
    Subscriptions
    Person_ID  Newsletter_ID  Subscribed  Updated
    2                1           true     2010-05-01
    3                1           true     2010-05-01
    3                2           true     2010-05-10
    3                1           false    2010-05-15
    

    因此,截至2010-05-16,1号人物没有订阅,2号人物有新闻稿1的订阅,3号人物有新闻稿2的订阅。第3个人订阅了一段时间的时事通讯1,但现在没有了。

    我想问两种问题。

    1. 显示每个人在查询时的活动订阅的查询(我们可以假设将来永远不会更新--因此,这意味着只要订阅为真,就返回每个人的最新“更新”值的记录(如果个人的最新记录的订阅状态为如果为假,则我不希望返回该记录)。

    2. 返回特定新闻稿的所有活动订阅的查询-与1中的条件相同。关于订阅列中带有“false”的记录。

    我不经常使用SQL/数据库来判断这种设计是否好,或者所需的SQL查询在订阅表中有一百万条记录的数据库上是否慢。

    我在Visual Studio 2010中使用了Visual Query Builder工具,但我甚至无法让查询返回每个人对的最新更新记录。

    是否有可能提出不涉及使用子查询的SQL查询(可能是因为使用较大的数据集时,查询速度太慢)?如果没有,最好设计一个单独的订阅历史记录表,并且每次向订阅中添加一个人的订阅状态时,该人对的任何现有记录都将移动到订阅历史记录(这样,订阅表只包含任何人的最新状态更新)Wslette_id对?

    我在Windows上使用.NET,那么使用LINQ进行这种查询会更容易(或相同,或更难)吗?实体框架?

    编辑:下面是使用此查询时的情况:

    SELECT     Person_ID, Newsletter_ID, Allocation, Updated, MAX(Updated) AS Expr1
    FROM         subscriptions
    GROUP BY Person_ID, Newsletter_ID
    

    我将订阅表中的第2行和第4行混合在一起(在下面结果集的第2行中):

    Person_ID Newsletter_ID Subscribed Updated     Expr1 
    2         1             true       2010-05-01  2010-05-01 
    3         1             true       2010-05-01  2010-05-15 
    3         2             true       2010-05-10  2010-05-10
    

    谢谢!

    4 回复  |  直到 15 年前
        1
  •  2
  •   Community CDub    8 年前

    我最近遇到了一个类似的 problem .

    我不是一个SQL专家,所以我不能给出很多关于什么是最好的设计的建议。但在专业人士介入之前,这可能有助于:

    SELECT s.Person_ID, s.Newsletter_ID  
    FROM (
     SELECT MAX(ID) AS mid
     FROM Subscriptions
     GROUP BY 
      Person_ID,Newsletter_ID
    ) q
    JOIN Subscriptions s
    ON q.mid = s.ID
    WHERE s.Subscribed = 1
    

    注意,我在您的订阅表中添加了一个ID列(稍后我将解释原因)。

    现在,让我们来分析一下这是如何工作的(或者我认为它是如何工作的,不管怎样,如果我错了,我很乐意被纠正)。

    首先,检索给定人员/新闻稿的所有记录。这就是子查询所做的(是的,我知道你说过你不希望有子查询,但我不确定没有子查询你能做到)。我按个人ID和新闻稿ID分组。这可以返回多行。注意,我选择的是max(id)。如果使用自动增量ID,并且假设ID列中数字最大的行是组中最新的行(即,如果不手动插入ID),则此子查询将为每个人/新闻稿获取最后一行的ID。

    因此,可以将其与订阅表联接:联接条件是订阅行的ID必须与从子查询中检索到的最大ID匹配。这里,您只考虑每个新闻稿/个人的最新记录。然后,使用where条件来计算非活动订阅。

    如果要将结果限制在给定的新闻稿(或给定的人)中,请将该条件添加到WHERE子句中。

    索引应该有助于使这个查询运行得更快。

    希望这有帮助。

    补充

    如果出于某种原因,您不能保证max(subscriptions.id)将对应于最后插入的行,那么您可以这样做(我认为,这遵循相同的逻辑,但有点冗长,而且可能效率较低):

    SELECT Person_ID, Newsletter_ID  
    FROM (
     SELECT MAX(Updated) AS upd, Newsletter_ID AS nid, Person_ID AS pid 
     FROM Subscriptions
     GROUP BY 
      Person_ID,Newsletter_ID
    ) q
    JOIN Subscriptions s
    ON q.pid = s.Person_ID AND q.nid = s.Newsletter_ID and q.upd = s.Updated
    WHERE Subscribed = 1
    

    新编辑

    在第二个想法上,我添加了一个备选方案 MAX(Updated) )我想是错的。您不能确定子查询中选择的新闻稿ID和个人ID将是对应于max(更新)行的新闻稿ID和个人ID。由于这些列用于联接条件,因此此查询可能会给出虚假的结果。

        2
  •  2
  •   zerkms    15 年前

    分开你 Subscriptions 2张表:

    • 首先将存储实际订阅列表(订阅,即 true 现在): Person_Id γ Newsletter_Id
    • 第二个将存储订阅日志(其更新或状态更改)
        3
  •  1
  •   Eddie    15 年前

    有序分析函数”是解决这类问题的标准方法。一百万张唱片,没问题…当然,这取决于你的机器的力量。

    MAX( Updated) OVER( PARTITION BY 要“max”的字段列表 )

    SELECT
      x.*
    FROM
      (
        SELECT
           Person_ID
           , Newsletter_ID
           --, Subscribed
           , Updated
           , MAX(Updated) OVER( PARTITION BY Person_ID, Newsletter_ID, Subscribed) AS myUpdated
       FROM Subscriptions
      ) x 
    WHERE Updated = myUpdated
    
        4
  •  0
  •   Keith Randall    15 年前

    我觉得你的设计不错。子查询没有固有的缓慢性-如果是表达查询的最佳方式,请使用它们。

    以下是获取所有最新(即未重写)指令的查询:

    SELECT Person_ID, Newsletter_ID, Subscribed, MAX(Updated)
    FROM Subscriptions GROUP BY Person_ID, Newsletter_ID
    

    然后,可以将此查询用作另一个查询的子查询,以获取所需的内容。对于您的查询1:

    SELECT x.Person_ID, x.Newsletter_ID FROM
      (SELECT Person_ID, Newsletter_ID, Subscribed, MAX(Updated) 
       FROM Subscriptions GROUP BY Person_ID, Newsletter_ID) x
    WHERE x.Subscribed;
    

    查询2:

    SELECT x.Person_ID FROM
      (SELECT Person_ID, Newsletter_ID, Subscribed, MAX(Updated)
       FROM Subscriptions GROUP BY Person_ID, Newsletter_ID) x
    WHERE x.Subscribed AND x.Newsletter_ID = ?
    

    你肯定想要一个索引 Newsletter_ID Subscriptions 表,因为这个查询可能是非常有选择性的。

    编辑: 哎呀,子查询中的订阅列可以来自任意行,而不是生成max(更新)的行。您必须重新加入原始表:

    SELECT x.Person_ID, x.Newsletter_ID, y.Subscribed FROM
      (SELECT Person_ID, Newsletter_ID, MAX(Updated) as MaxUpdated
       From Subscriptions GROUP by Person_ID, Newsletter_ID) x
      JOIN Subscriptions y WHERE x.Person_ID = y.Person_ID AND
                                 x.Newsletter_ID = y.Newsletter_ID AND
                                 x.MaxUpdated = y.Updated