0

I'm using MySQL with PHP. This is like my table: (I'm using 3 values, but there are more)

id | 1 | 2 | 3
---+---+---+----
1  | 3 |12 |-29
2  | 5 |8  |8
3  | 99|7  |NULL

I need to get the greatest value's column name in a certain row. It should get:

id | maxcol
---+-------
1  |   2
2  |   2
3  |   1

Are there any queries that will do this? I've been trying, but I can't get it to work right.

outis
  • 75,655
  • 22
  • 151
  • 221
noryb009
  • 548
  • 2
  • 10
  • 20
  • I do not see how your example output maps to the data you showed. Do you mean it should return (1,99),(2,12),(3,8) or should it return (1,12),(2,8),(3,99)? I.e., is it finding the largest value down or across? – Thomas Apr 21 '10 at 23:38
  • It is finding the column of the largest value. – noryb009 Apr 21 '10 at 23:40
  • So you want the index or name of the column with the largest value for each row, correct? – Thomas Apr 21 '10 at 23:48
  • Correct, so row 1 would output "2". (Because it is 12, the highest) – noryb009 Apr 21 '10 at 23:49
  • What if I just do it in PHP? It would have to be allowing negative numbers and NULLs though... – noryb009 Apr 22 '10 at 00:09

6 Answers6

4

Are you looking for something like the GREATEST function? For example:

SELECT id, GREATEST(col1, col2, col3)
    FROM tbl
    WHERE ...

Combine it with a CASE statement to get column names:

SELECT id, CASE GREATEST(COALESCE(`1`, -2147483646), COALESCE(`2`, -2147483646), COALESCE(`3`, -2147483646))
         WHEN `1` THEN 1
         WHEN `2` THEN 2
         WHEN `3` THEN 3
         ELSE 0
      END AS maxcol
    FROM tbl
    WHERE ...

It's not pretty. You'd do better to follow Bill Karwin's suggestion and normalize, or simply take care of this in PHP.

function findcol($cmp, $arr, $cols=Null) {
   if (is_null($cols)) {
      $cols = array_keys($arr);
   }
   $name = array_shift($cols);
   foreach ($cols as $col) {
       if (call_user_func($cmp, $arr[$name], $arr[$col])) {
           $name = $col;
       }
   }
   return $name;
}

function maxcol($arr, $cols=Null) {
   return findcol(create_function('$a, $b', 'return $a < $b;'), $arr, $cols);
}
outis
  • 75,655
  • 22
  • 151
  • 221
  • This is very close: it gets me the largest value, but I want the largest column with that value. – noryb009 Apr 21 '10 at 23:46
  • 1
    I believe this is on the track. Caveat - according to http://dev.mysql.com/doc/refman/5.1/en/comparison-operators.html#function_greatest since 5.0.3 if any column is NULL it'll return NULL; so COALESCE to some min value will have to be used, but even that'll fail if all null (well not really fail, but it would be indistinguishable from case when min value is actually in the data and it is the max of the data, etc...) – Unreason Apr 21 '10 at 23:53
  • @Unreason: noted about `GREATER`'s treatment of NULLs. Seems odd behavior. The sensible thing would seem to be to ignore NULLs completely. – outis Apr 22 '10 at 05:39
  • especially since the old behaviour seemed better (returning NULL only when all are NULLs). Anyway if my comment/answer was useful... – Unreason Apr 22 '10 at 06:02
3

This is a great example of the way normalization helps make query design easier. In First Normal Form, you would create another table so all the values would be in one column, on separate rows.

Since you have used repeating groups to store your values across three columns, you can find the column with the greatest value this way:

SELECT id, IF(col1>col2 AND col1>col3, 'col1', IF(col2>col3, 'col2', 'col3')) 
  AS column_with_greatest_value
FROM mytable;
Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
1

The short answer is that there is no simple means to do this via a query. You would need to transpose your data and then determine the largest value that way. So something like:

Select Id, ColumnName, Value
From    (
        Select '1' As ColumnName, Id, [1] As Value
        From Table
        Union All
        Select '2', Id, [2]
        From Table
        Union All
        Select '3', Id, [3]
        From Table
        ) As Z
Where Exists(
            Select 1
            From    (
                    Select '1' As ColumnName, Id, [1] As Value
                    From Table
                    Union All
                    Select '2', Id, [2]
                    From Table
                    Union All
                    Select '3', Id, [3]
                    From Table
                    ) As Z2
            Where Z2.Id = Z.Id
            Group By Z2.Id
            Having Max(Z2.Value) = Z.Value
            )
Order By Id

This solution depends on a fixed set of columns where you basically name the columns in the UNION ALL queries. In addition, if you have two columns with identical values for the same Id, you will get duplicate rows.

Thomas
  • 63,911
  • 12
  • 95
  • 141
  • Argh, you typed faster :) I'll give you +1 but I believe you have an error. one related to the 'clarification' - 'finding column with the largest value' was imprecise, it was better in the original - 'greatest value's column name in a certain row' (so correlation should not be on column name but on id) – Unreason Apr 22 '10 at 00:12
  • @Unreason - You are correct. I should get a duplicate in the output (for Id = 2) which I've corrected. – Thomas Apr 22 '10 at 00:20
0

This query will return the max value regardless of NULLs

SELECT MAX(value)
FROM
(SELECT 1 column_no, col1 value
FROM anotherunamedtable
UNION ALL
SELECT 2, col2
FROM anotherunamedtable
UNION ALL
SELECT 3, col3
FROM anotherunamedtable) t

If you really need the column number then

SELECT id,
       (SELECT column_no
       FROM
              (SELECT 1 column_no, col1 value
              FROM anotherunamedtable
              WHERE id = t.id
              UNION ALL
              SELECT 2, col2
              FROM anotherunamedtable
              WHERE id = t.id
              UNION ALL
              SELECT 3, col3
              FROM anotherunamedtable
              WHERE id = t.id) s
        ORDER BY max_value DESC
        LIMIT 1)) as column_no
FROM anotherunamedtable t

But I think that the last query might perform exceptionally horrible. (Queries are untested)

Unreason
  • 12,556
  • 2
  • 34
  • 50
0

In the php side, you could do something like this:

foreach ($rows as $key => $row) {
  $bestCol = $best = -99999;
  foreach ($row as $col => $value) {
    if ($col == 'id') continue; // skip ID column
    if ($value > $best) {
      $bestcol = $col;
      $best = $value;
    }
  }
  $rows[$key]['best'] = $bestCol;
}

Or something similar...

gnarf
  • 105,192
  • 25
  • 127
  • 161
0

Forests and trees, here's a trivial and fastest solution (providing I didn't fumble); the expression simply looks for the largest column in the row

SELECT id,
       CASE COALESCE(col1, -2147483648) >= COALESCE(col2, -2147483648)
       WHEN 
           CASE COALESCE(col2, -2147483648) >= COALESCE(col3, -2147483648)
           WHEN true THEN 1
           ELSE 
                CASE COALESCE(col1, -2147483648) >= COALESCE(col3, -2147483648)
                WHEN true THEN 1
                ELSE 3
                END 
           END 
       ELSE 
           CASE COALESCE(col2, -2147483648) >= COALESCE(col3, -2147483648)
           WHEN true 2
           ELSE 3
           END 
       END 
FROM table t

a version with IF() would maybe be more readable, but the above should perform a bit better To deal with NULLS an INT value with minimum of -2147483648 was assumed, the expression could be rewritten to deal explicitly with nulls but would have to branch into 8 different cases and is left as an exercise for the OP.

Unreason
  • 12,556
  • 2
  • 34
  • 50