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

时间:2010-05-16 18:32:42

标签: sql mysql history

假设我有一个包含3个表的MySQL数据库:

表1:具有1列ID(int)的人员
表2:简报,1列ID(int)
表3:订阅,列为Person_ID(int),Newsletter_ID(int),订阅(bool),更新(日期时间)

Subscriptions.Person_ID指向一个人,而Subscription.Newsletter_ID指向一个时事通讯。因此,每个人可以同时对0个或更多个杂志订阅0个或更多。表格订阅还将存储每个人订阅每个时事通讯的完整历史记录。如果特定的Person_ID-Newsletter_ID对在Subscriptions表中没有行,那么它等同于订阅状态为'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,Person 1没有订阅,Person 2订阅了Newsletter 1,Person 3订阅了Newsletter 2. Person 3订阅了Newsletter 1一段时间,但现在不行了。

我正在尝试做两种查询。

  1. 从查询时间开始显示每个人的有效订阅的查询(我们可以假设更新将永远不会在将来 - 因此,这意味着为每个Person_ID-Newsletter_ID返回具有最新'updated'值的记录对,只要Subscribed为真(如果Person_ID-Newsletter_ID对的最新记录的订阅状态为false,那么我不希望返回该记录))。

  2. 返回特定时事通讯的所有有效订阅的查询 - 与1.关于已订阅列中包含'false'的记录的相同资格。

  3. 我不经常使用SQL /数据库来判断这种设计是否合适,或者如果所需的SQL查询在具有(例如)订阅表中的1M记录的数据库上会很慢。

    我在Visual Studio 2010中使用了可视化查询构建器工具,但我甚至无法获取查询以返回每个Person_ID-Newsletter_ID对的最新更新记录。

    是否有可能提出不涉及使用子查询的SQL查询(可能是因为它们对于更大的数据集会变得太慢)?如果没有,拥有单独的Subscriptions_History表是一个更好的设计,并且每次将Person_ID-Newsletter-ID对的订阅状态添加到Subscriptions时,该对的任何现有记录都将移动到Subscriptions_History(这样订阅table只包含任何Person_ID-Newsletter_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 个答案:

答案 0 :(得分:2)

将您的Subscriptions分成两个表:

  • 首先会存储实际的订阅列表(订阅,暂时为true):Person_Id | Newsletter_Id
  • 第二个将存储订阅日志(其更新或状态更改)

答案 1 :(得分:2)

我最近遇到了类似的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列(我将在一秒内解释原因)。

现在,让我们分解一下这是如何运作的(或者我认为它的运作方式,无论如何;如果我错了,我会很高兴得到纠正。)

首先,检索给定人员/简报的所有记录。这就是子查询的作用(是的,我知道你说你宁愿没有子查询,但我不确定你能不能没有子查询)。我是按person_id和newsletter_id分组的。这可以返回多行。注意我正在选择MAX(ID)。如果您使用自动增量ID,并且可以安全地假设ID列中具有最高编号的行是该组的最新行(即,如果您未手动插入ID),则此子查询将获取最后一个ID每个人/时事通讯的行。

因此,您可以将其与订阅表连接:连接条件是订阅行的ID必须与从子查询中检索的MAX 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))是错误的。您无法确定子查询中选定的Newsletter_ID和Person_ID是与MAX(更新)行对应的Newsletter_ID和Person_ID。由于这些列用于连接条件,因此该查询可能会产生伪造结果。

答案 2 :(得分:1)

有序分析功能“是此类问题的标准方法.1M记录,没问题......当然,取决于您机器的功率。

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

答案 3 :(得分:0)

我认为你的设计非常好。子查询没有固有的缓慢 - 如果是表达查询的最佳方式,请使用它们。

这是获取所有最新(即未被覆盖)指令的查询:

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上有索引,因为此查询可能非常具有选择性。

编辑: 哎呀,子查询中的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