MySQL查询问题与Math不会产生预期的输出

时间:2017-01-07 20:21:11

标签: mysql math operator-precedence

概述:

我构建了一个我在本地运行的应用程序,这使我能够跟踪我的孩子每天的家务和行为。该系统具有我可以分配给它们的消极和积极行为,这些行为对应于100点量表上的点值。

逻辑:

  • 查询仅查看当天计算点数。如果前一天收到评级,那么这些评分将不会达到他们的每日总数。
  • 100分是孩子当天可以拥有的最高分,即使他们的评分超过此值,它也会始终返回100
  • 如果他们当天没有任何评分(正面或负面),则会将其点数默认为起点100
  • 当他们收到积分时,他们的总数将相应调整,根据行为设置的值上升或下降。

方案:

  1. 没有任何评分的新的一天意味着孩子从100点开始。他们收到的行为具有-3值。这会将totalPoints作为97返回。
  2. 上述孩子会获得一个价值2积分的正面评分,这些评分会为99提升totalPoints
  3. 他们获得了另一个值5分的积极评分。由于我们最高为100,因此我们会将totalPoints作为100返回,无论其超出100多少。
  4. 问题:

    我构建了查询,并认为一切正常,但似乎有一个轻微的数学问题。如果孩子获得-3点评分,则会将他们带到预期的97。然后我给了他们一个积极的4,它将他们的得分改为99而不是100,就像我预期的那样。

    查询:

     SELECT c.id,
           c.NAME,
           Date_format(From_days(Datediff(CURRENT_DATE, c.age)),
           '%y Years %m Months %d Days')                                 AS age,
           c.photoname,
           c.photonamesmall,
           (SELECT CASE
                     WHEN ( Ifnull(Sum(t.points), (SELECT settingvalue
                                                   FROM   settings
                                                   WHERE  settingname = 'MaxPoints')
                            ) >= (
                            SELECT
                                   settingvalue
                            FROM
                            settings
                            WHERE
                                     settingname = 'MaxPoints') ) THEN 100
                     WHEN ( Sum(t.points) <= 0 ) THEN ( (SELECT settingvalue
                                                         FROM   settings
                                                         WHERE  settingname =
                                                                'MaxPoints')
                                                        + Sum(t.points) )
                     ELSE ( (SELECT settingvalue
                             FROM   settings
                             WHERE  settingname = 'MaxPoints') -
                            Ifnull(Sum(t.points), (SELECT
                            settingvalue
                                                   FROM   settings
                                                   WHERE
                            settingname = 'MaxPoints')) )
                   END
            FROM   behaviorratings AS r
                   JOIN behaviortypes AS t
                     ON r.behaviorid = t.behaviortypeid
            WHERE  r.childid = c.id
                   AND Date_format(r.timestamp, '%Y-%m-%d') = Curdate()) AS
           totalPoints,
           (SELECT definitionname
            FROM   behaviordefinitions AS d
            WHERE  totalpoints BETWEEN d.min AND d.max)                  AS
           behaviorRating
    FROM   children AS c  
    

    小提琴:

    以下是SQL小提琴的链接:http://sqlfiddle.com/#!9/fa06c/1/0

    我希望看到Child 2(Brynlee)的结果是100而不是99

    她从100开始,收到了-3,收到了+4。虽然我知道这个操作顺序的数学运算是正确的,但我需要对其进行调整以反映我对它的预期反映。 100 - 3 = 97然后97 + 4 = 101(我们在100最多,因此100将为totalPoints

2 个答案:

答案 0 :(得分:1)

试试这个

SELECT c.id,
   c.name,
   DATE_FORMAT(
    FROM_DAYS(
        DATEDIFF(CURRENT_DATE, c.age)
    ),
    '%y Years %m Months %d Days'
  ) AS age,
   c.photoName,
   c.photoNameSmall,
   (SELECT CASE
             WHEN ( Ifnull(Sum(t.points), 0
                    ) + (SELECT settingValue
                                           FROM   settings
                                           WHERE  settingName = 'MaxPoints') >= (
                    SELECT
                           settingValue
                    FROM
                    settings
                    WHERE
                             settingName = 'MaxPoints') ) THEN 100
             WHEN ( Sum(t.points) <= 0 ) THEN ( (SELECT settingValue
                                                FROM   settings
                                                WHERE  settingName =
                                                       'MaxPoints')
                                               + Sum(t.points) )
             ELSE ( (SELECT settingValue
                     FROM   settings
                     WHERE  settingName = 'MaxPoints') -
                    Ifnull(Sum(t.points), (SELECT
                    settingvalue
                                           FROM   settings
                                           WHERE
                    settingName = 'MaxPoints')) )
           END
    FROM   behaviorRatings AS r
           JOIN behaviorTypes AS t
             ON r.behaviorID = t.behaviorTypeID
    WHERE  r.childid = c.id
           AND Date_format(r.timestamp, '%Y-%m-%d') = Curdate()) AS
   totalPoints,
   (SELECT definitionName
    FROM   behaviorDefinitions AS d
    WHERE  totalPoints BETWEEN d.min AND d.max)                  AS
   behaviorRating
   FROM   children AS c

基本上,使用

WHEN ( Ifnull(Sum(t.points), (SELECT settingvalue
                              FROM   settings
                              WHERE  settingname = 'MaxPoints')
)
当sum(t.points)为空时,

只会给你100。要获得你需要做的总分

Ifnull(Sum(t.points), 0) + (SELECT settingvalue
                              FROM   settings
                              WHERE  settingname = 'MaxPoints')

这个sql可以让你更容易看到

SET @maxPoints := (SELECT settingValue
              FROM   settings
              WHERE  settingName = 'MaxPoints');

SELECT c.id,
   c.name,
   DATE_FORMAT(
    FROM_DAYS(
        DATEDIFF(CURRENT_DATE, c.age)
    ),
    '%y Years %m Months %d Days'
  ) AS age,
   c.photoName,
   c.photoNameSmall,
   (SELECT CASE
             WHEN ( Ifnull(Sum(t.points), 0) + @maxPoints > @maxPoints ) THEN 100
             ELSE ( Ifnull(Sum(t.points), 0) + @maxPoints )
           END
    FROM   behaviorRatings AS r
           JOIN behaviorTypes AS t
             ON r.behaviorID = t.behaviorTypeID
    WHERE  r.childid = c.id
           AND Date_format(r.timestamp, '%Y-%m-%d') = Curdate()) AS
   totalPoints,
   (SELECT definitionName
    FROM   behaviorDefinitions AS d
    WHERE  totalPoints BETWEEN d.min AND d.max)                  AS
   behaviorRating
   FROM   children AS c

以50为起点:

SET @maxPoints := (SELECT settingValue
          FROM   settings
          WHERE  settingName = 'MaxPoints');

SET @startingPoint := 50;

SELECT c.id,
c.name,
DATE_FORMAT(
FROM_DAYS(
    DATEDIFF(CURRENT_DATE, c.age)
),
'%y Years %m Months %d Days'
) AS age,
c.photoName,
c.photoNameSmall,
(SELECT CASE
         WHEN ( Ifnull(Sum(t.points), 0) + @startingPoint > @maxPoints ) THEN 100
         ELSE ( Ifnull(Sum(t.points), 0) + @startingPoint )
       END
FROM   behaviorRatings AS r
       JOIN behaviorTypes AS t
         ON r.behaviorID = t.behaviorTypeID
WHERE  r.childid = c.id
       AND Date_format(r.timestamp, '%Y-%m-%d') = Curdate()) AS
totalPoints,
(SELECT definitionName
FROM   behaviorDefinitions AS d
WHERE  totalPoints BETWEEN d.min AND d.max)                  AS
behaviorRating
FROM   children AS c

用于在超过限制的总分数时应用封顶的SQL

SET @maxPoints := (SELECT settingValue
            FROM   settings
            WHERE  settingName = 'MaxPoints');

SET @startingPoint := 50;

SELECT 
    c.id,
    c.name,
    DATE_FORMAT(
    FROM_DAYS(DATEDIFF(CURRENT_DATE, c.age)), '%y Years %m Months %d Days') AS age,
    c.photoName,
    c.photoNameSmall,
    (
        select x.tp 
        from 
        (
            SELECT t.childid,
                @rn:=CASE WHEN @cid <> t.childid THEN 0 ELSE @rn+1 END AS rn,
                @startingPoint + @tp:= CASE 
                    WHEN @cid <> t.childid 
                    THEN ifnull(t.points, 0)
                    ELSE (
                        case when @startingPoint + t.points + @tp > @maxPoints 
                        then @maxPoints - @startingPoint
                        else t.points + @tp end)
                    END AS tp,
                @cid:=t.childid AS clset,
                t.timestamp
            FROM
                (SELECT @tp:= -1) p,
                (SELECT @rn:= -1) n,
                (SELECT @cid:= -1) cd,
                (
                    SELECT r.childid, t.points, r.timestamp
                    FROM behaviorRatings AS r
                    JOIN behaviorTypes AS t ON r.behaviorID = t.behaviorTypeID
                    ORDER BY r.childid, r.timestamp
                ) t
        ) x
        where x.childid = c.id AND Date_format(x.timestamp, '%Y-%m-%d') = Curdate()
        order by x.childid, x.rn desc
        limit 1
    ) AS totalPoints,
    (
        SELECT definitionName
        FROM   behaviorDefinitions AS d
        WHERE  totalPoints BETWEEN d.min AND d.max
    ) AS behaviorRating
FROM   children AS c

答案 1 :(得分:0)

不要让事情变得更加复杂。为您的任务选择合适的语言。在你的情况下它是PHP:

$query = "select settingValue from settings where settingName = 'MaxPoints'";
$result = $this->db->query($query);
$row = $result->fetchAssoc();
$maxPoints = $row['settingValue'];

$query = "select * from children";
$result = $this->db->query($query);
$children = array();
while ($row = $result->fetchAssoc()) {
    $row['totalPoints'] = $maxPoints;
    $children[$row['id']] = $row;
}

$query = "
    select c.id, coalesce(bt.points, 0) as points
    from children c
    join behaviorRatings br on  br.childID = c.id
    join behaviorTypes bt on bt.behaviorTypeID = br.behaviorID
    where date(br.timestamp) = current_date()
    order by c.id, br.timestamp
";

$result = $this->db->query($query);
while ($row = $result->fetchAssoc()) {
    $childId = $row['id'];
    $totalPoints = $children[$row['id']]['totalPoints'];
    $totalPoints = $totalPoints + $row['points'];
    $totalPoints = min($totalPoints, $maxPoints);
    $children[$row['id']]['totalPoints'] = $totalPoints;
}

var_dump($children);

获得总分的所有逻辑都在最后一个循环中。现在将它与您的查询进行比较。

但是 - 如果您更改规则,允许在白天超过限制并仅在当天结束时削减点数,则可以在一个查询中完成:

select c.*, sub.totalPoints, bd.definitionName
from (
  select c.id, least(100+coalesce(sum(bt.points), 0), mp.settingValue) as totalPoints
  from children c
  join settings mp on settingName = 'MaxPoints'
  left join behaviorRatings br
    on  br.childID = c.id
    and date(br.timestamp) = current_date()
  left join behaviorTypes bt on bt.behaviorTypeID = br.behaviorID
  group by c.id
) sub
join children c on c.id = sub.id
join behaviorDefinitions bd on sub.totalPoints between bd.min and bd.max

http://sqlfiddle.com/#!9/fa06c/71

虽然这不是一个简单的查询,但它远没有你的尝试那么复杂。接受的解决方案是在忽略规则的情况下做同样的事情,每次获得点数时,总点数必须减少到100(Sum(t.points))。

正如我在评论中所写:要遵循该规则,您需要某种迭代。 MySQL使用用户变量有一个技巧:

select c.id, c.name, sub.totalPoints, bd.definitionName
from (
  select sub.childId, sum(sub.cuttedPoints) + sp.settingValue as totalPoints
  from (
    select 
      @points := coalesce(bt.points,0) as points,
      @lastTotalPoints := case when (c.id = @childId)
        then @totalPoints
        else sp.settingValue
      end lastTotalPoints,
      @totalPoints := least(@lastTotalPoints + @points, mp.settingValue) as totalPoints,
      @totalPoints - @lastTotalPoints as cuttedPoints,
      @childId := c.id as childId
    from children c
    join settings sp on sp.settingName = 'StartPoints'
    join settings mp on mp.settingName = 'MaxPoints'
    left join behaviorRatings br 
      on  br.childID = c.id
      and date(br.timestamp) = current_date()
    left join behaviorTypes bt on bt.behaviorTypeID = br.behaviorID
    cross join (select @childId := null) init_var
    order by c.id, br.timestamp
  ) sub
  join settings sp on sp.settingName = 'StartPoints'
  group by sub.childId
) sub
join children c on c.id = sub.childId
join behaviorDefinitions bd on sub.totalPoints between bd.min and bd.max

结果(Brynlee行为:+4 -3 +4 -3):

| id |    name | totalPoints |         definitionName |
|----|---------|-------------|------------------------|
|  2 | Brynlee |          97 | Having an amazing day! |
|  1 |    Maya |         100 | Having an amazing day! |

Brynlee获得了97分(+4 =&gt; 100,-3 =&gt; 97,+ 4 =&gt; 100,-3 =&gt; 97)

http://sqlfiddle.com/#!9/751c51/28

如果您更改新设置&#34; StartPoints&#34;到50岁你会得到:

| id |    name | totalPoints |   definitionName |
|----|---------|-------------|------------------|
|  2 | Brynlee |          52 | Not looking good |
|  1 |    Maya |          50 | Not looking good |

Brynlee得到52分,因为从未达到100分(+4 => 54,-3 => 51,+ 4 => 55,-3 => 52)。

http://sqlfiddle.com/#!9/db020/13

这是因为MySQL的处理顺序。但是这个顺序取决于MySQL的内部实现。在未来版本中可以更改此实现,而不会发出任何警告。事实上 - MySQL开发人员明确警告过这样使用用户变量。

  

作为一般规则,除了在SET语句中,你永远不应该   为用户变量赋值并读取其中的值   声明。例如,要增加变量,这没关系:

     

SET @a = @a + 1;

     

对于其他语句,例如SELECT,您可能会得到结果   期待,但这不能保证。在以下声明中,您   可能会认为MySQL会首先评估@a然后做一个   第二个任务:

     

SELECT @a, @a:=@a+1, ...;

     

但是,涉及用户的表达式的评估顺序   变量未定义。

(User-Defined Variables)

我只使用&#34;技巧&#34;就像单向报告一样 - 但从来没有出现在生产代码中。

所以我的建议是:更改规则或使用过程语言(PHP)。