MySQL: общая группа с любопытством ROLLUP

У меня есть два запроса. Один из них имеет смысл для меня, а другой нет. Первый:

SELECT gender AS 'Gender', count(*) AS '#'
    FROM registrations 
    GROUP BY gender WITH ROLLUP

Это дает мне следующее:

Gender       #
Female      20
Male        19
NULL        39

Итак, я получаю счет и общее количество. Чего я ожидал. Следующий:

SELECT c.printable_name AS 'Country', count(*) AS '#' 
    FROM registrations r 
    INNER JOIN country c ON r.country = c.country_id 
    GROUP BY country WITH ROLLUP

Country         #
Denmark         9
Norway         10
Sweden         18
United States   1
Uzbekistan      1
Uzbekistan     39

Тот же результат. Но почему я получаю Узбекистан за общую сумму?

Ответы

Ответ 1

Но почему я получаю Узбекистан за общую сумму?

Потому что вы НЕ ВЫБРАТЬ элемент, который вы GROUPING BY. Если вы сказали:

GROUP BY c.printable_name

Вы получите ожидаемый NULL. Однако вы группируете другой столбец, поэтому MySQL не знает, что имя_таблицы принимает участие в группе rollup-group и выбирает любое старое значение из этого столбца при объединении всех регистраций. (Таким образом, вы можете увидеть другие страны, кроме Узбекистана.)

Это часть более широкой проблемы с MySQL, которая разрешима для того, что вы можете SELECT в запросе GROUP BY. Например, вы можете сказать:

SELECT gender FROM registrations GROUP BY country;

и MySQL будет с радостью выбирать один из гендерных значений для регистрации из каждой страны, даже если нет прямой причинно-следственной связи (например, "функциональной зависимости" ) между страной и полом. Другие СУБД откажутся от вышеуказанной команды на том основании, что на страну не гарантируется один пол. (*)

Теперь, это:

SELECT c.printable_name AS 'Country', count(*) AS '#' 
FROM registrations r 
INNER JOIN country c ON r.country = c.country_id 
GROUP BY country

в порядке, потому что существует функциональная зависимость между r.country и c.printable_name (если вы правильно описали свой country_id как ПЕРВИЧНЫЙ КЛЮЧ).

Однако MySQL с расширением ROLLUP немного взломан в том, как он работает. На этапе свертки в конце он запускает весь набор результатов перед группировкой, чтобы захватить его значения, а затем устанавливает столбец "по-порядку" в NULL. Он также не имеет нулевых других столбцов, которые имеют функциональную зависимость от этого столбца. Это, вероятно, должно, но MySQL в настоящее время не совсем понимает всю суть функциональных зависимостей.

Итак, если вы выберете c.printable_name, он покажет вам, какое значение имени страны выбрано в случайном порядке, и если вы выберете c.country_id, он покажет вам, какой из идентификаторов страны он случайно выбрал - даже если c.country_id является критерием соединения, поэтому он должен быть таким же, как r.country, что равно NULL!

Что вы можете сделать, чтобы решить эту проблему:

  • группа вместо имени для печати; должно быть ОК, если имена для печати уникальны или
  • выберите "r.country", а также имя для печати, и убедитесь, что для NULL или
  • забудьте WITH ROLLUP и выполните отдельный запрос для конечной суммы. Это будет немного медленнее, но также будет совместимым с ANSI SQL-92, поэтому ваше приложение может работать с другими базами данных.

(*: MySQL имеет параметр SQL_MODE ONLY_FULL_GROUP_BY, который должен решить эту проблему, но он идет слишком далеко и позволяет вы выбираете столбцы из GROUP BY, а не столбцы, которые имеют функциональную зависимость от GROUP BY. Таким образом, он также приведет к сбою действительных запросов, что делает его вообще бесполезным.)

Ответ 2

Coz, когда вы используете метод JOIN, следующий элемент NULL массива будет иметь значение предыдущего элемента NOT NULL. Но я не уверен. Это мой опыт, когда я использую его в PHP.

hm... есть еще одна проблема... "Страна" canot, потому что это имя таблицы. Так что измените что-нибудь еще. Тогда в последнем результате будет отображаться NULL. Вот мое предложение:

$result = mysql_query("SELECT c.printable_name AS 'countryp', count(*) AS '#'
FROM registrations r, country c WHERE r.country = c.country_id
GROUP BY countryp WITH ROLLUP");

while($row = @mysql_fetch_array($result)) {
  $r1 = $row["countryp"];
  $r2 = $row["#"];
  if ($r1 == NULL) $r1 = 'Total';
  echo "$r1 $r2<br />";
}

Ответ 3

SELECT ifnull(c.printable_name, "Total Registration = ") AS 'Country', count(*) AS '#' 
FROM registrations r 
INNER JOIN country c ON r.country = c.country_id 
GROUP BY country WITH ROLLUP;

Это будет печатать ' Total Registration = 39 и будет последней строкой/записью.