-1

I've been trying to use SUM() to total up some numeric values in my database, but it seems to be returning unexpected values. Here is the PHP-side for generating the SQL:

public function calcField(string $field, array $weeks)
{
    $stmt = $this->conn->prepare('SELECT SUM(`'. $field .'`) AS r FROM `ws` WHERE `week` IN(?);');
    $stmt->execute([implode(',', $weeks)]);

    return $stmt->fetch(\PDO::FETCH_ASSOC)['r'];
}

Let's give it some example data for you folks at home:

$field = 'revenue';
$weeks = [14,15,16,17,18,19,20,21,22,23,24,25,26];

This returns this value:

4707.92

Without seeing the data, this may have seemed to have worked, but here's the rows for those weeks:

+----+------+------+---------+-------+---------+---------+------+----------+
| id | week | year | revenue | sales | gpm_ave | uploads | pool | sold_ave |
+----+------+------+---------+-------+---------+---------+------+----------+
|  2 |   14 | 2019 | 4707.92 |   292 |      13 |       0 | 1479 |       20 |
|  3 |   15 | 2019 | 4373.32 |   304 |      13 |       0 | 1578 |       19 |
|  4 |   16 | 2019 | 4513.10 |   275 |      14 |       0 | 1460 |       19 |
|  5 |   17 | 2019 | 4944.80 |   336 |      14 |       0 | 1642 |       20 |
|  6 |   18 | 2019 | 4343.87 |   339 |      13 |       0 | 1652 |       21 |
|  7 |   19 | 2019 | 3918.59 |   356 |      14 |       0 | 1419 |       25 |
|  8 |   20 | 2019 | 4091.20 |   247 |      19 |       0 | 1602 |       15 |
|  9 |   21 | 2019 | 4177.22 |   242 |      12 |       0 | 1588 |       15 |
| 10 |   22 | 2019 | 3447.88 |   227 |      18 |       0 | 1585 |       14 |
| 11 |   23 | 2019 | 3334.18 |   216 |      15 |       0 | 1675 |       13 |
| 12 |   24 | 2019 | 4736.15 |   281 |      13 |       0 | 1388 |       20 |
| 13 |   25 | 2019 | 4863.84 |   252 |      12 |       0 | 1465 |       17 |
| 14 |   26 | 2019 | 4465.95 |   281 |      21 |       0 | 1704 |       16 |
+----+------+------+---------+-------+---------+---------+------+----------+

As you can see, the total should be far greater than 4707.92 - and I notice that the first row revenue = 4707.92.

Here's what things get weird, if I add this into the function:

echo 'SELECT SUM(`'. $field .'`) AS r FROM `ws` WHERE `week` IN('. implode(',', $weeks) .');';

Which outputs:

SELECT SUM(revenue) AS r FROM ws WHERE week IN(14,15,16,17,18,19,20,21,22,23,24,25,26);

Copying and pasting this into MySQL CLI returns:

MariaDB [nmn]> SELECT SUM(revenue) AS r FROM ws WHERE week IN(14,15,16,17,18,19,20,21,22,23,24,25,26);
+----------+
| r        |
+----------+
| 55918.02 |
+----------+
1 row in set (0.00 sec)

Which, looks a lot more accurate. However, that very same SQL statement returns the first row value rather than summing the column for those weeks.

This function gets triggered by an AJAX script:

$d = new Page\Snapshot\D();

# at the minute only outputting dump of values to see what happens
echo '<pre>'. print_r(
        $d->getQuarterlySnapshot(new Page\Snapshot\S(), new App\Core\Date(), $_POST['quarter'], '2019'),
        1
    ). '</pre>';

The function $d->getQuarterlySnapshot function:

public function getQuarterlySnapshot(S $s, Date $date, int $q, string $year)
{
    switch($q)
    {
        case 1:
            $start = $year. '-01-01 00:00:00';
            $end = $year. '-03-31 23:59:59';
            break;
        case 2:
            $start = $year. '-04-01 00:00:00';
            $end = $year. '-06-30 23:59:59';
            break;
        case 3:
            $start = $year. '-07-01 00:00:00';
            $end = $year. '-09-30 23:59:59';
            break;
        case 4:
            $start = $year. '-10-01 00:00:00';
            $end = $year. '-12-31 23:59:59';
            break;
    }

    $weeks = $date->getWeeksInRange('2019', 'W', $start, $end);
    foreach ($weeks as $key => $week){$weeks[$key] = $week[0];}

    return [
        'rev' => $s->calcField('revenue', $weeks),
        'sales' => $s->calcField('sales', $weeks),
        'gpm_ave' => $s->calcField('gpm_ave', $weeks),
        'ul' => $s->calcField('uploads', $weeks),
        'pool' => $s->calcField('pool', $weeks),
        'sold_ave' => $s->calcField('sold_ave', $weeks)
    ];
}

So I don't overwrite the value anywhere (that I can see at least). How do I use SUM() with the IN() conditional?

double-beep
  • 5,031
  • 17
  • 33
  • 41
treyBake
  • 6,440
  • 6
  • 26
  • 57
  • 2
    You probably mean to use `where id IN (...` instead of `where week in (..` because the data that you are showing at the end is from id = 1 to 13 – Madhur Bhaiya Sep 04 '19 at 10:33
  • @MadhurBhaiya ah, no I do mean week - but didn't actually check the week values xD one sec, lemme quickly test with another.. more populated range and get back to you xD – treyBake Sep 04 '19 at 10:35
  • 2
    Not a good idea storing money values as floating point numbers – Ed Heal Sep 04 '19 at 10:36
  • `SHOW CREATE TABLE ws` like @EdHeal suggestion it seams you are using [float](https://dev.mysql.com/doc/refman/8.0/en/floating-point-types.html) where you should have used [decimal](https://dev.mysql.com/doc/refman/8.0/en/precision-math-decimal-characteristics.html) .. floats are approximate values so indeed like Ed said not smart to use approximate values to indicate money values. – Raymond Nijland Sep 04 '19 at 10:41
  • @MadhurBhaiya twas that indeed! Will close as off-topic ^^ :) – treyBake Sep 04 '19 at 10:41
  • @EdHeal huh, not used really anything else (never do the DB design haha) - what should be used? – treyBake Sep 04 '19 at 10:41
  • @RaymondNijland huh, didn't know of the decimal type ^.^ what will happen if I update the column type now? Will my current values alter in any way? – treyBake Sep 04 '19 at 10:42
  • 1
    alter the table to add a new column with decimal type `revenue_decimal` .. Run `UPDATE SET revenue_decimal = revenue ....` and check the data between... when the data is correct drop the float type and rename the decimal type.. Or create a new table which is also possible and run a `INSERT INTO new_table SELECT * FROM old_table`.. Anny how don't do a datatype conversion on life data most likely in this case nothing bad will happen but better to be safe then sorry.. – Raymond Nijland Sep 04 '19 at 10:46
  • @RaymondNijland seems it's safe to alter^^ :) ty for help – treyBake Sep 04 '19 at 10:51
  • @MadhurBhaiya edited question with similar issue I've just gone through, that's less obvious haha let me know if it's re-openable^^ – treyBake Sep 04 '19 at 15:32
  • @treyBake problem is in the PHP code. Comma separated week_id values is passing as a single string in the query: `week_id in ('1,2,3...,15')` . MySQL implicitly typecasts this to `1` and thus gets the first row only. You will need to change the query preparation code – Madhur Bhaiya Sep 04 '19 at 18:16
  • @MadhurBhaiya ah I see, that makes sense! So using `str_repeat` to generate the amount of `?` to pass is probably a better way to go about this? – treyBake Sep 04 '19 at 23:00
  • @treyBake yes, maybe! There must be definitely other similar questions on SO for the similar problem. Just need to search them out. – Madhur Bhaiya Sep 05 '19 at 04:22
  • @treyBake here is a C# solution: https://stackoverflow.com/a/650481/2469308 ; you should be able to replicate it in PHP. – Madhur Bhaiya Sep 05 '19 at 06:38
  • @MadhurBhaiya posted a PHP-solution :) – treyBake Sep 05 '19 at 08:27

3 Answers3

2

As pointed out in the comments by @MadhurBhaiya:

Comma separated week_id values is passing as a single string in the query: week_id in ('1,2,3...,15') . MySQL implicitly typecasts this to 1 and thus gets the first row only. You will need to change the query preparation code

Led me to breaking up the single ? into named parameters using a foreach loop:

public function calcField(string $field, array $weeks)
{
    $data = [];
    $endKey = end(array_keys($weeks));

    $sql = 'SELECT SUM(`'. $field .'`) AS r FROM `ws` WHERE `week` IN(';

    foreach ($weeks as $key => $week)
    {
        $sql .= ':field'. $key;
        $sql .= ($key !== $endKey ? ',' : '');

        $data[':field'. $key] = $week;
    }

    $sql .= ');';

    $stmt = $this->conn->prepare($sql);
    $stmt->execute($data);

    return $stmt->fetch(\PDO::FETCH_ASSOC)['r'];
}

Now each numeric value is treated individually, which, is getting me the expected value.

treyBake
  • 6,440
  • 6
  • 26
  • 57
1

Yes, it is showing only first row data in your query because you have mentioned where week IN (1,2,3,4,5,6,7,8,9,10,11,12,13); and your data shows that you have week 13 and 1 to 12 does not exist in your data from IN clause of your query.

Ishpreet
  • 659
  • 5
  • 19
0

You are selecting only the row with week 13, the week 1-12 is not existing in your data. That's why the result of your query is 43.900001525878906

Solution 1:

You might want to change the values in your IN()because you are filtering your data by the column week.

SELECT SUM(`revenue`) as `r` FROM `ws` WHERE `week` IN (13,14,15,16,17,18,19,20,21,22,23,24,25);

Solution 2:

You can change the week in your where clause to id

SELECT SUM(`revenue`) as `r` FROM `ws` WHERE `id` IN (1,2,3,4,5,6,7,8,9,10,11,12,13);
oycarlito
  • 97
  • 1
  • 13