【问题标题】:table design + SQL question表设计+SQL题
【发布时间】:2010-12-29 17:16:47
【问题描述】:

我有一个使用以下 DDL 创建的餐桌食物吧。 (我使用的是 mySQL 5.1.x)

CREATE TABLE foodbar (
    id          INT NOT NULL AUTO_INCREMENT,
    user_id     INT NOT NULL,
    weight      double not null,
    created_at  date not null
);

我有四个问题:

  1. 如何编写返回的查询 结果集给了我 以下信息:user_id, weight_gain 其中 weight_gain 是 重量和重量之间的差异 7天记录的重量 以前。
  2. 如何编写一个查询 返回前 N 个用户 最大的体重增加(再说一遍 一周)。?一种“明显”的方式可能是 使用问题1中得到的查询 上面作为子查询,但不知何故 挑选前 N 个。
  3. 从问题 2 开始(实际上 问题1),我正在搜索 表中的记录使用 计算字段,索引将是 最好优化查询 - 但是因为它是一个计算出来的 字段,不清楚是哪个字段 索引(我猜是“重量” 领域是需要的 索引)。我是对的吗 假设?。
  4. 假设我在 食物吧桌(说“高度”)和我 想要从 基于(比如说)产品的表格 (即乘法)“高度” 和“重量”——我会正确吗 再次假设我需要索引 '身高和体重'?。我是否也 需要创建一个复合键(比如 (身高体重))。如果这个问题 不清楚,我很乐意 澄清

【问题讨论】:

    标签: sql mysql database-design


    【解决方案1】:

    我不明白您为什么需要合成密钥,所以我将使用此表:

    CREATE TABLE foodbar (
      user_id     INT NOT NULL
    , created_at  date not null
    , weight      double not null
    , PRIMARY KEY (user_id, created_at)
    );
    

    我如何编写一个返回结果集的查询,该结果集为我提供以下信息:user_id, weight_gain 其中 weight_gain 是重量与 7 天前记录的重量之间的差异。

    SELECT curr.user_id, curr.weight - prev.weight
    FROM foodbar curr, foodbar prev
    WHERE curr.user_id = prev.user_id
      AND curr.created_at = CURRENT_DATE
      AND prev.created_at = CURRENT_DATE - INTERVAL '7 days'
    ;
    

    日期算术语法可能是错误的,但你明白了

    我如何编写一个查询来返回体重增加最多的前 N ​​个用户(再次说一周以上)。?一种“明显”的方法可能是将上述问题 1 中获得的查询用作子查询,但以某种方式选择前 N 个。

    见上文,添加ORDER BY curr.weight - prev.weight DESCLIMIT N

    对于最后两个问题:不要推测,检查执行计划。 (postgresql 有EXPLAIN ANALYZE,不知道mysql)你可能会发现你需要索引参与WHEREJOIN 的列,而不是形成结果集的列。

    【讨论】:

    • MySQL 有解释计划 - 只需将 EXPLAIN 关键字放在 SELECT 之前并运行查询以查看计划。
    • +1(尤其是因为您丢弃了任意合成密钥)。然而,我必须指出,这是假设数据每天都非常定期且一致地填充。差距会严重影响您的查询。
    • 我不喜欢你的连接语法,但有些可能
    • 这对我来说看起来“最直观”。我将进行一些测试/检查并查看。也感谢您指出合成索引。
    【解决方案2】:

    我认为“只是某人”涵盖了您要问的大部分内容,但我只想补充一点,除非它恰好是覆盖索引,否则参与计算的索引列不太可能对您有帮助。

    例如,如果我想按产品 X * Y 的顺序获取以下行,则按 X、Y 排序是没有帮助的:

    X     Y
    1     8
    2     2
    4     4
    

    产品将按以下顺序排列它们:

    X     Y     Product
    2     2     4
    1     8     8
    4     4     16
    

    如果 mySQL 支持表中的计算列并允许对这些列建立索引,那么这可能会有所帮助。

    【讨论】:

    • 正确,正如您所说明的:在这种情况下,在数学上不可能确定如何使用各个列上的索引来有效地查询计算。在某些情况下,查询优化器可以弄清楚如何重新安排计算并利用适当的索引。然而,这通常很难做到,甚至难以识别,这就是为什么我们应该尽可能让列远离表达式“另一端”的计算。
    【解决方案3】:

    我同意just somebody 关于主键的观点,但是对于您所要求的权重计算,您最好存储增量而不是权重:

    CREATE TABLE foodbar (
      user_id      INT NOT NULL, 
      created_at   date not null,
      weight_delta double not null, 
      PRIMARY KEY (user_id, created_at)
    );
    

    这意味着您将用户的初始权重存储在用户表中,当您将记录写入foodbar 表时,用户可以提供当时的权重,但查询会减去初始权重从目前的体重。所以你会看到如下值:

    user_id   weight_delta
    ------------------------
    1         2
    1         5
    1         -3
    

    看看,你知道用户 1 增加了 4 磅/公斤/石头/等等。

    这样您可以使用 SUM,因为有人可能每天都称重 - 无论时间跨度如何,使用 just somebodycurr.weight - prev.weight 等式都行不通。

    在 MySQL 中获取顶部 x 很容易 - 使用 LIMIT 子句,但请注意您提供 ORDER BY 以确保正确应用限制。

    【讨论】:

    • 这是一种非常巧妙的重新安排事物的方式,可以更有效地解决特定需求。请注意,第一次为用户捕获读数时,您可以只输入当前重量(因为 delta 只是反映了之前“读数”“零”的变化)。尽管您可能希望在真实增量和初始读数之间建立更明显的区别。即使是单独的表格也是合理的。请记住全面考虑所有要求,以确保此设计符合您的需求。
    【解决方案4】:

    这并不明显,但您要解决的问题中缺少一些重要信息。当您考虑进入此表的实际数据时,它会变得更加明显。问题是您不太可能拥有一致的每日用户体重记录。因此,您需要澄清一些关于确定“当前体重”和“x 天前体重”的规则。我将假设以下简单的规则:

    • 最近的重量读数是“当前重量”。 (尽管那可能是几个月前的事了。)
    • x 天前的最新重量读数将是 x 天前假定的重量。 (例如,在确定 7 天前的体重时,6 天前的读数比 21 天前的读数更可靠。)

    现在回答问题:

    1&2:使用上述额外规则提供了生成两个结果集的机会:当前权重和以前的权重:

    当前权重:

    select  rd.*,
            w.Weight
    from    (
            select  User_id,
                    max(Created_at) AS Read_date
            from    Foodbar
            group by User_id
            ) rd
            inner join Foodbar w on
                w.User_id = rd.User_id
            and w.Created_at = rd.Read_date
    

    x 天前的阅读类似:

    select  rd.*,
            w.Weight
    from    (
            select  User_id,
                    max(Created_at) AS Read_date
            from    Foodbar
            where   Created_at < DATEADD(dd, -7, GETDATE()) /*Or appropriate MySql equivalent*/
            group by User_id
            ) rd
            inner join Foodbar w on
                w.User_id = rd.User_id
            and w.Created_at = rd.Read_date
    

    现在只需将这些结果作为子查询加入

    select  cur.User_id,
            cur.Weight as Cur_weight,
            prev.Weight as Prev_weight
            cur.Weight - prev.Weight as Weight_change
    from    (
            /*Insert query #1 here*/
            ) cur
            inner join (
            /*Insert query #2 here*/
            ) prev on
                prev.User_id = cur.User_id
    

    如果我没记错的话,获得前 N 个体重增加的 MySql 语法是简单地添加:

    ORDER BY cur.Weight - prev.Weight DESC limit N
    

    2&3:选择索引需要稍微了解查询优化器将如何处理查询:

    在选择索引时,重要的是您要过滤或加入的列。如果确定索引足够选择性,优化器将使用该索引(请注意,有时您的过滤器必须非常选择性地返回

    3:尽管权重在您显示的内容中占有重要地位,但在过滤(或选择)方面唯一相关的是在 #2 中以获得前 N 个权重增益。这是一个基于大量查询和大量处理的复杂计算;所以权重作为指标将提供零收益。

    另一个注意事项是,即使对于#2,您也必须计算所有用户的权重变化,以确定哪些用户获得的收益最大。因此,除非您的每个用户有大量读数,否则您将阅读该表的大部分内容。 (即表扫描将用于获取大量数据)

    索引可以受益的地方:

    • 您正在尝试根据 User_id 和 Created_at 识别特定的 Foodbar 行。
    • 您还将使用 User_id 和 Created_at 再次加入到 Foodbar 表。

    这意味着 User_id 上的索引,Created__at 将很有用(如果这是聚集索引,则更是如此)。

    4:不,不幸的是,在数学上不可能确定单个值 H 和 W 如何独立确定产品的排序。例如。 H=3 和 W=3 均小于 5,但如果 H=5 且 W=1,则乘积 3*3 大于 5*1。 您必须将计算实际存储在该附加列上的索引中。但是,正如我在上面对 #3 的回答中指出的那样,它仍然不太可能证明是有益的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-18
      • 2012-05-22
      • 2010-12-01
      • 1970-01-01
      相关资源
      最近更新 更多