表设计+ SQL问题

时间:2009-12-24 03:58:32

标签: sql mysql database-design

我有一个表格foodbar,使用以下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),我正在寻找 使用a表中的记录 计算字段,索引将是 最好优化查询 - 但是因为它是计算的 现场,目前尚不清楚哪个领域 索引(我猜的是'重量' 字段是需要的字段 索引)。我是对的 假设?
  4. 假设我有另一个领域 foodbar table(比如'height')和我 想从中选择记录 基于(比如说)产品的表格 ('倍增''高度' 和“重量” - 我会是正确的 再次假设我需要索引 '身高和体重'?。我也是 需要创建一个复合键(比如说 (身高体重))。如果这个问题 目前尚不清楚,我很乐意 澄清

4 个答案:

答案 0 :(得分:3)

我不明白为什么你需要合成密钥,所以我将改用这个表:

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的dunno)你可能会发现你需要索引参与WHEREJOIN的列,而不是构成结果集的列。

答案 1 :(得分:1)

我认为“只是某个人”涵盖了你所要求的大部分内容,但我只想补充说,参与计算的索引列根本不可能对你有所帮助,除非它恰好是覆盖索引。

例如,如果我想按产品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支持表中的计算列并允许对这些列建立索引,那么这可能会有所帮助。

答案 2 :(得分:1)

我同意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 somebody的{​​{1}}等式无论时间跨度如何都无效。

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

答案 3 :(得分:0)

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

  • 最近的体重读数是'当前体重'。 (即使这可能是几个月前。)
  • 超过x天前的最新体重读数将是x天前的体重。 (尽管例如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

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

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

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

在索引选择方面,重要的是您要过滤或加入的列。如果确定选择性,优化器将使用该索引(请注意,有时您的过滤器必须选择性返回&lt; 1%的数据才算有用)。总是在导航索引的慢磁盘搜索时间与简单处理内存中的所有数据之间进行交易。

3:尽管权重在您显示的内容中具有显着特征,但唯一的相关性在于过滤(或选择)在#2中以获得前N个权重增益。这是一个基于大量查询和大量处理的复杂计算;因此,权重将提供零利益作为指数。

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

索引可以从中受益:

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

这意味着User_id上的索引,Created__at会很有用(如果这是聚集索引,那么更多)。

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