0

I've inherited a CodeIgniter query that generates this SQL

EXAMPLE:

SELECT `users`.`id`, `users`.`username`, `users`.`email`, `users`.`photo`, `users`.`rating`
FROM `pool_details`
JOIN `users` ON `users`.`id` = `pool_details`.`captain_id`
WHERE `pool_details`.`pool_type` =0
AND `pool_details`.`pool_close` > '2020-01-02 18:39:42'
GROUP BY `pool_details`.`captain_id`
ORDER BY `pool_details`.`members_count` DESC
LIMIT 20

ERROR - 2020-01-02 18:39:42 --> Query error: Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'pool_details.members_count' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by - Invalid query:

Here is the same data with some "extra columns" and the offending clauses removed:

SELECT users.id, users.username, users.email,users.rating,pool_details.members_count,pool_details.pool_type,pool_details.pool_close
FROM  pool_details
JOIN  users ON users.id = pool_details.captain_id
WHERE pool_details.pool_type = 0 AND pool_details.pool_close > '2020-01-02 18:39:42'
ORDER BY pool_details.members_count DESC;
//GROUP BY `pool_details`.`captain_id`
+----+----------+--------------------------+--------+---------------+-----------+---------------------+
| id | username | email                    | rating | members_count | pool_type | pool_close          |
+----+----------+--------------------------+--------+---------------+-----------+---------------------+
| 13 | Ronaldo  | [email protected] | 4.6    |           100 | 0         | 2020-01-04 03:00:00 |
| 13 | Ronaldo  | [email protected] | 4.6    |           100 | 0         | 2020-01-03 23:30:00 |
| 13 | Ronaldo  | [email protected] | 4.6    |           100 | 0         
...
| 13 | Ronaldo  | [email protected] | 4.6    |             0 | 0         | 2020-01-03 00:00:00 |
| 13 | Ronaldo  | [email protected] | 4.6    |             0 | 0         | 2020-01-03 00:30:00 |
| 13 | Ronaldo  | [email protected] | 4.6    |             0 | 0         | 2020-01-04 21:30:00 |
| 13 | Ronaldo  | [email protected] | 4.6    |             0 | 0         | 2020-01-03 03:00:00 |
+----+----------+--------------------------+--------+---------------+-----------+---------------------+
28 rows in set (0.00 sec)

What I want is to:

  1. Show User's username, email and rating
  2. Order by "members_count"
  3. Show the user only once

For example:

SELECT DISTINCT users.id, users.username, users.email,users.rating
FROM  pool_details
JOIN  users ON users.id = pool_details.captain_id
WHERE pool_details.pool_type = 0 AND pool_details.pool_close > '2020-01-02 18:39:42';
+----+----------+--------------------------+--------+
| id | username | email                    | rating |
+----+----------+--------------------------+--------+
|  5 | wheel    | [email protected]         | NULL   |
| 13 | Ronaldo  | [email protected] | 4.6    |
+----+----------+--------------------------+--------+
2 rows in set (0.00 sec)
<= This shows the users individually ... but it's *NOT* ordered by "members_count".

Q: Is there any combination of "GROUP BY" and/or "DISTINCT" that I can use with mySql 5.7 that will give me the result set I need?

5
  • 1
    if we know that members_count is functionally dependent (i.e. we know the value of members_count column from all rows in a collapsed group are the same), then we could just wrap in an aggregate function. .e.g ORDER BY MAX(pool_details.members_count) DESC. (we could use MIN() in place of MAX(), result will be the same, if we are guaranteed all rows in the collapsed group have the same value of members_count, then we know MIN(members_count) <=> MAX(members_count) Commented Jan 3, 2020 at 20:18
  • 1
    Which "members_count" of all that a user may have do you want to use in ORDER BY?
    – forpas
    Commented Jan 3, 2020 at 20:34
  • @spencer7593: That's the solution! Thank you! Here' s my updated SQL. If you post it an an answer; I'd be happy to upvote and "accept" it. SELECT users.id, users.username, users.email,users.rating FROM pool_details JOIN users ON users.id = pool_details.captain_id WHERE pool_details.pool_type = 0 AND pool_details.pool_close > '2020-01-02 18:39:42' GROUP BY pool_details.captain_id ORDER BY MAX(pool_details.members_count) DESC;
    – paulsm4
    Commented Jan 3, 2020 at 21:09
  • @FoggyDay: my comment wasnt an answer to the question that was asked, if there is a combination of GROUP BY and DISTINCT ... if members_count is not functionally dependent, then echoing the comment from forpas, which of the possible values for members_count do we want to use? it's easy enough to pick out the maximum and minimum values with MAX and MIN , but maybe we want the value associated with the earliest or latest pool_close ... we're only guessing because we don;'t have a more precise specification Commented Jan 4, 2020 at 22:18
  • Sigh... I didn't say "and"; I said "and/or". In the sense "whatever syntax eliminates the error". You gave me the answer: "MAX()". And that answer makes sense: we want an aggregate. Specifically: the captain with the most members should go at the top, the captain with the next most second, etc. If you give a response, I'll "accept" it.
    – paulsm4
    Commented Jan 4, 2020 at 23:52

2 Answers 2

1

Other than the ORDER BY, you seem to just want EXISTS. That said, you can use another subquery in the ORDER BY:

SELECT u.*
FROM users u
WHERE EXISTS (SELECT 1
              FROM pool_details pd
              WHERE pd.captain_id = u.id AND
                    pd.pool_type = 0 AND
                    pd.pool_close > '2020-01-02 18:39:42'
             )
ORDER BY (SELECT MAX(pd2.members_count)
          FROM pool_details pd2
          WHERE pd2.captain_id = u.id AND
                pd2.pool_type = 0 AND
                pd2.pool_close > '2020-01-02 18:39:42'
         ) DESC
LIMIT 20

EDIT:

You can also write this as:

SELECT u.*,
       (SELECT MAX(pd2.members_count)
        FROM pool_details pd2
        WHERE pd2.captain_id = u.id AND
              pd2.pool_type = 0 AND
              pd2.pool_close > '2020-01-02 18:39:42'
      ) as max_members_count
FROM users u
HAVING max_member_count IS NOT NULL
ORDER BY max_member_count DESC
LIMIT 20

Or:

SELECT u.*
FROM users u JOIN
     (SELECT pd2.captain_id, MAX(pd2.members_count) as max_member_count
      FROM pool_details pd2
      WHERE pd2.pool_type = 0 AND
            pd2.pool_close > '2020-01-02 18:39:42'
     ) pd
     ON pd.captain_id = u.id
ORDER BY max_member_count DESC
LIMIT 20l
2
  • Thank you. I took the liberty of fixing the typo in the second subselect, and changed u.* to SELECT u.id, u.username, u.email, u.rating. At which point it does exactly what I wanted. Q: Isn't "where exists()" and the two subselects inefficient? FYI, I was ALSO able to fix the SQL error and get the results I wanted using spencer7593's suggestion: ORDER BY MAX(pool_details.members_count). Isn't that far more efficient?
    – paulsm4
    Commented Jan 3, 2020 at 21:19
  • @FoggyDay . . . I added two equivalent methods. To be honest, I hadn't noticed the ORDER BY when I answered the question and went back and added in the subquery. With appropriate indexes, that might be a good approach, although the first query in the edit might have the best performance. Commented Jan 3, 2020 at 21:25
0

Problem:

A PHP/CodeIgniter application I inherited generated this SQL:

SELECT `users`.`id`, `users`.`username`, `users`.`email`, `users`.`photo`, `users`.`rating`
FROM `pool_details`
JOIN `users` ON `users`.`id` = `pool_details`.`captain_id`
WHERE `pool_details`.`pool_type` =0
AND `pool_details`.`pool_close` > '2020-01-02 18:39:42'
GROUP BY `pool_details`.`captain_id`
ORDER BY `pool_details`.`members_count` DESC
LIMIT 20

.. which, in turn, caused this MySql error:

ERROR - 2020-01-02 18:39:42 --> Query error: Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'pool_details.members_count' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by - Invalid query:

Cause:

forpas asked the right question:

Which "members_count" of all that a user may have do you want to use in ORDER BY?

spencer7593 gave the right solution:

If we know that members_count is functionally dependent ... then we could just wrap in an aggregate function. .e.g ORDER BY MAX(pool_details.members_count) DESC.

Solution:

Modified PHP/CodeIgniter query:

$this->db->select('users.id,users.username,users.email,users.photo,users.rating');
$this->db->from('pool_details');
$this->db->join('users','users.id = pool_details.captain_id');
$this->db->where('pool_details.pool_type',0);
$this->db->where('pool_details.pool_close >',$now);
$this->db->order_by('MAX(pool_details.members_count)','DESC'); // <-- Added "MAX()"
$this->db->group_by('pool_details.captain_id');
$this->db->limit(20, 0);
$query = $this->db->get()->result();

Discussion: Each "Pool" has a "Captain".

We want to "group by" each individual captain among all the captains in all the pools.

The top of the list should be the pool with the most members.

The aggregate MAX(pool_details.members_count) gives us exactly the result we're looking for.

There's no "functional dependency"; the value of "members_count column" from all rows in a collapsed group are typically NOT the same. Nevertheless, "MAX()" does exactly what we need: it resolves the error, and we get the results we want from the schema we have.

Not the answer you're looking for? Browse other questions tagged or ask your own question.