Как стать автором
Обновить

Комментарии 125

Если не наберет — адекватность аудитории хабра в очередной раз подтвердится.
Если не наберет, то мне не придется покупать билет на самолет из Мурманска
Сударь, не позорьте наш Кольский полуостров…
Вы бы еще у MySQL простым на вид запросом спросили, танцевать или нет на Красной площади :)
А если двести минусов?
Я думаю пойдет и будет дальше менять статусы вконтакте :)
Если честно, хотелось бы чуть более разжеванных примеров.
И так вопрос, какой знак имеет число 0. Не торопитесь с ответом, вы же уже поняли, что разработчики MySQL жуткие тролли.

Это не разработчики MySQL тролли, а разработчики стандарта IEEE 754: en.wikipedia.org/wiki/Signed_zero

Результат ROUND(-0.1) в MySQL — это фактически значение, которое возвращает glibc-шная функция rint(-0.1). Которая действительно возвращает ноль со знаком минус.
Спасибо за информацию, но -0=+0=0 значит лишних кортежей при группировке возникать не должно.
Нет, +0 и -0 это разное. По крайней мере на один бит точно должны отличаться, сейчас посмотрю IEEE 754…
Угу это и приходится объяснять бизнесу когда у них в отчетах 2 строки 0 с разным тоталом :) вы не про стандарт думайте а про конечного пользователя.
Лучше себе объясните зачем вы для денежных вычислений используете числа с плавающей точкой.
Можете составить такой же пример с целыми числами поделив их. Такой же результат должен быть, к сожалению не могу проверить уже. Float в примере просто для простоты восприятия.
деление целых чисел даёт плавающую точку же. используйте NUMERIC
Для дробной части суммы?
хранить в неделимых еденицах? копейки, центы и т.д.
Судя по количеству плюсов комментария выше я, вероятно, чего-то не знаю или не понимаю. Объясните кто-нибудь, пожалуйста, почему нельзя использовать естественное представление данных с дробной частью? Зачем разносить на две колонки целая-дробная часть?
Кто говорит про разнесение? Одна колонка. Целочисленная. Нужно сохранить $120.99, записываем — 12099.
Хорошо, пусть так. Но зачем?
en.wikipedia.org/wiki/Floating_point#Accuracy_problems

# Пример на php (прочие языки таковы же)
ustimenko@home:~$ php -r 'var_dump((1 - 0.7) == 0.3);'
bool(false)
# Ибо
ustimenko@home:~$ php -d precision=32 -r 'var_dump((1 - 0.7), 0.3);'
float(0.30000000000000004440892098500626)
float(0.29999999999999998889776975374843)
Стоит заметить, что почти во всех ЯП есть типы для точной работы с floating point числами, но это естественно медленнее чем работа с целыми числами.
Примеры скорости или типов данных?
А мне типов, я не знаю как можно работать с floating point точно.
В пхп не скажу. Но в ruby, java есть BigDecimal.

1.9.3p327 :007 > BigDecimal.new(1, 2) - BigDecimal.new(0.7, 2) == BigDecimal.new(0.3, 2)
 => true 
1.9.3p327 :008 > 1 - 0.7 == 0.3
 => false 

еще в руби есть тип Rational и библиотека mathn.

1.9.3p327 :021 > Rational(0.55)
 => (2476979795053773/4503599627370496) 


Работает странно, но точно :)
Оно работает из-за округления, и можно получить такие же погрешности:

BigDecimal.new(1, 2) - BigDecimal.new(0.7, 2) == BigDecimal.new(0.2999, 2) # true 
ну вы же должны расчитывать с какой точностью будете работать. если это деньги, то берем точность 6 и все.
Да и опять же есть Rational (который быстрее BigDecimal раза в 2)

Rational(1) - Rational(0.7) == Rational(0.29999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
 => false 

В php нету из коробки, но можно заюзать java`вскую через extension кастомный.
Есть из коробки. BC math.
oops, точно!

Хотя выглядит по-уродски по сравнению с более православными руби и питоном:

ustimenko@home:~$ php -r 'var_dump(0 == bccomp(bcsub(1, "0.7", 2), "0.3", 2));'
bool(true)


Причем ещё надо явно precision указывать — пипец…
Ruby
1.9.3p327 :010 > Benchmark.measure { BigDecimal.new(1, 2) - BigDecimal.new(0.7, 2) == BigDecimal.new(0.3, 2) }
 =>   0.000000   0.000000   0.000000 (  0.000089)
 
1.9.3p327 :011 > Benchmark.measure { 1 - 0.7 == 0.3 }
 =>   0.000000   0.000000   0.000000 (  0.000015)


Как видите почти в 6 раз медленнее
Однострочники:

ustimenko@home:~$ ruby -e 'print(1 - 0.7 == 0.3); print("\n");'
false
ustimenko@home:~$ ruby -e 'require "bigdecimal"; print(BigDecimal.new("1") - BigDecimal.new("0.7") == BigDecimal.new("0.30000")); print("\n");'
true

ustimenko@home:~$ python -c 'print (1 - 0.7 == 0.3)'
False
python -c 'from decimal import *; print (1 - Decimal("0.7") == Decimal("0.3"))'
True
да первый бит всегда отвечат за минус — если первый бит равен 1 то минус есть или наоборот — 0 то +
-0 равен 0 судя из примера: группировка была по -0 и 0 т.к. в поле group_concat видим все 2 значения, значит они равны. Или я что то просмотрел?
Согласен, лишних кортежей быть не должно. Как выяснилось, это проблема HASH индексов в MEMORY storage engine: bugs.mysql.com/bug.php?id=67978
Спасибо за дельный комментарий, это действительно оно, теперь становится понятно где ещё это может всплыть. Автор бага вы, если не секрет?
К счастью, не я :) Баг был с незапамятных времён. Подозреваю, что с самой первой реализации HEAP и HASH индексов.
Я имел ввиду кто именно завел баг, а не кто написал код :) но ответ тоже хороший :D из области: «Казань брал, Астрахань брал, Шпака не брал!»
Я переделывал обработку чисел с плавающей точкой в MySQL 5.5. Поэтому и заинтересовался. Баг зарепортил я по мотивам нашего обсуждения.
А что документация говорит по этому поводу?


CREATE TABLE `medals` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `country` varchar(50) NOT NULL,
  `event_name` varchar(50) NOT NULL,
  `golds` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


INSERT into medals(event_name) VALUES('starting');

Select * from medals;
+----+---------+------------+-------+
| id | country | event_name | golds |
+----+---------+------------+-------+
|  3 |         | starting   |     0 |
+----+---------+------------+-------+
1 row in set (0.00 sec)




Или на такой пример:

SELECT 124124/0;
+----------+
| 124124/0 |
+----------+
|     NULL |
+----------+
1 row in set (0.00 sec)
И первый и второй случай это стандартное поведение если не указывать дополнительных режимов работы БД. Посмотрите SQL_MODE.
mysql> SET sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT 124124/0;
+----------+
| 124124/0 |
+----------+
|     NULL |
+----------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT @@SESSION.sql_mode;
+-----------------------------------------------------------------------------+
| @@SESSION.sql_mode                                                          |
+-----------------------------------------------------------------------------+
| STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO |
+-----------------------------------------------------------------------------+
1 row in set (0.00 sec)


Не поскажете какой параметр фиксит это?
Да вы правы ERROR_FOR_DIVISION_BY_ZERO на который я рассчитывал фиксит это только для DML операций согласно документации. Но опять же согласно ей же это поведение для мускуля нормально. Просто надо к нему привыкнуть и при необходимости ставить coalesce. Так же думаю надо проверить как это работает в процедурах. Там же ест обработчик деления на ноль. Он должен перехватить. Но согласитесь то о чем вы написали все знают как хорошо документированную фичу.
Помимо документации, есть еще стандарт ANSI SQL, и он должен быть главнее документации по БД. По поводу деления на ноль, стандарт прямо говорит, что должен происходить Exception.

«If the value of a divisor is zero, then an exception condition is raised: data exception-division by zero.»

Это написано в пункте 3.3.4.1 Exceptions
Вольный пересказ — команда считается неудачной, возможно надо прекратить выполнение кода и надо обязательно вывести отладочную информацию. Данные и структуры таблиц поменяться не должны.
В том то и дело, что «возможно», а значит MySQL следует стандарту.
Когда я забываю указать поля вставки в insert или допускаю в своем коде деление на ноль, мне некого ругать кроме самого себя. А вот когда выясняется, что null is not null это уже из другой области.
где то читал, что null это неопределенное значение и следовательно ни себе не равно и чему другому. И результат деления на 0 = неопределённое число… получается логично.
В математике, если уже и делится на 0, то получается определенная величина тоже — бесконечность.

1.0 / 0.0
 => Infinity


Так что логики пока не видно.
Да вы что… а я всегда думал что в стандартной арифметике «Результат деления на ноль не определен». А за «бесконечность — определённая величина» вам 2 и на повторный курс 8-го класса…
Операции деления ненулевого числа на ноль не соответствует никакое действительное число.
Результат этой операции считается бесконечно большим и равным бесконечности: bit.ly/gpa8nr

Значение операции 0:0 (ноль деленное на ноль) неопределенно (которое я и не рассматривал).

> 0.0 / 0.0
=> NaN
> 1.0 / 0.0
=> Infinity
ну уж 65535 десятичных цифр — нам точно хватит


Совсем не так. Знаковый bigint хранит значения от -9223372036854775808 до 9223372036854775807, т.е. 19 знаков. Что немного меньше, чем 65535,
Ну я как бы об этом и писал. Вы запрос ты выполнить не поленитесь. А потом посмотрите структуры созданной таблицы. И подумайте что будет при восстановлении БД из бэкапа или репликации.
Я не совсем понимаю, о чём вы говорите. Цифра в скобках (65535) вообще к диапазону хранимых значений никакого отношения не имеет. От того, что вы укажете там 1 или 42 — данные не изменятся абсолютно никак.
А то что cast( as signed integer) на выходе дает mediumtext тоже не о чем не говорит? Конечно вы не сможете записать туда такие значения и сделать вид что это bigint.
Я повторю ещё раз — вы как минимум вводите в заблуждение читателей, как максимум — заблуждаетесь сами, считая, что число 65535 имеет хоть какое-то влияние на хранимые данные.
Вы вообще о чем?
Я прошу БД сделать мне bigint из строки длиной 10!
substr(repeat(' ', 21848), 10)
А что получаю в итоге? А то что я пишу что null равно 0 или любой другой пример вас не насторожило что ли?
Я о ваших фразах:

> что ж выясним сколько знаков у нас есть
+
> ну уж 65535 десятичных цифр — нам точно хватит

Число знаков, которые позволяет сохранить тип определяется сугубо названием типа, а не числом в скобочках.

Про остальные примеры я не говорю, я с самого начала и до сих пор говорю о вашей единственной некорректной фразе.
А по моему все выводы в этой статье некорректны это же сарказм… Вся статья. Я не пояснил истинные причины ни по одному из примеров. Тут истина только то что mysql выдает кучу не документированного бреда в самый не подходящий момент. И именно это и есть зло.
С чего вы взяли?
После данного запроса ваша реплика вылетит а дамп придется чистить руками ибо:
create table unlimited_table (new_bigint_field bigint(65535));
ERROR 1439 (42000): Display width out of range for column 'new_bigint_field' (max = 255)

а в моей БД такие таблицы бывают ;)
show create table new_unlimited_table \G
*************************** 1. row ***************************
       Table: new_unlimited_table
Create Table: CREATE TABLE `new_unlimited_table` (
  `new_bigint_field` bigint(65535) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

О боже.

Я повторю в третий раз: я не спорю, что дамп получится кривой, я лишь говорю, что

> ну уж 65535 десятичных цифр — нам точно хватит

некорректно в принципе, даже в ироничной статье. В особенности после «Ведь все знают что bigint не ограничивается лишь 20 знаками. Как вы не знали? ну что ж это тоже легко доказать.»

Это не доказательство, а введение в заблуждение читателя (который не знает синтаксиса).
«Недокументированный бред» — это всегда баги. Не стесняйтесь их репортить «своему MySQL вендору».
В MySQL нет таких извращений как в Oracle (там NULL равен пустой строке)

А по-моему это классно. Когда первый раз, ещё в школьные или около того годы, прочитал — тоже подумал: «как это так? А как мне хранить пустые строки? Ну и извращение этот ваш Оракл...» А со временем я понял, что ни разу в жизни мне не попадалась ситуация, где хранение в БД пустой строки имело бы физический смысл. Если пользователь не заполнил необязательное поле формы, или ввёл что-то, а потом удалил — значит по данному полю нет данных, т.е. логически null. Если возникает ситуация, когда по этому полю «сказать нечего», а колонка в базе обозначена NOT NULL — значит имеет место ошибка проектирования. А микс означающих на деле одно и то же «нулей», пустых строк и серий пробелов в одной колонке — вот это извращение. В результате я пришёл к тому, что всегда (разумеется, если бы попался осмысленный повод, я сделал бы исключение) на уровне прослоечного кода делаю сначала trim строки по бокам и если получаю пустую строку, то в базу пишу NULL (а если не пустую — то её (обтримленную)).
А мне как-то не по себе. NULL не д.б. FALSE`ом.

Также NULL не д.б. пустотой — это просто ничто.

NULL — это когда мы не знаем чего-то, а пустая строка — это мы уже знаем, что чего-то нет.

Кароче это всё равно не хорошо так — как-то не по себе что ли.
В оракле null не является тождественным булеву false. Ну и как по мне вполне логично, что пустота — это ничто.
Все ваши ощущения исключительно от субъективного восприятия NULL. Для меня лично непонятно «NULL — это когда мы не знаем чего-то». Наоборот известно, что нет ничего.
Да не совсем. NULL — это когда неизвестно. Нет ничего — это как раз пустая строка или FALSE.

Пример:

Name: Саша
Sex: NULL

Какого пола у нас Саша?

Или другой вариант поля — IsMale

ps: Ежу ясно, что так не делают, но это конно-вакуумно абстракция.
Если Саша не указал/указала пол, то что там кроме нулла должно быть?
Если у вас поле предполагает булево значение, но допускает нулл, ну извиняйте, имхо, небольшая кривизна в моделировании.
И откуда такая уверенность, что False = Null? False — ложное утверждение, null — отсутствие утверждения (вполне коррелирует с пустой строкой).

Тов. Optik, я как раз об обратном и говорю!

Я за то, что FALSE не есть NULL

Просьба вчитаться внимательнее :)
Вы утверждаете, что в оракловой базе это так. Разве не об этом была ваша речь?
Да нет — я про оракл вообще ничего не говорил.

Я сказал: «NULL не д.б. FALSE`ом.»

Это не конкретно про оракл/мусклуь/ё-нейм-хиа.
Вообще говоря многозначность значения NULL является достаточно старой проблемой, упоминавшейся ещё Коддом!
Codd indicated in his 1990 book The Relational Model for Database Management, Version 2 that the single Null mandated by the SQL standard was inadequate, and should be replaced by two separate Null-type markers to indicate the reason why data is missing. In Codd's book, these two Null-type markers are referred to as 'A-Values' and 'I-Values', representing 'Missing But Applicable' and 'Missing But Inapplicable', respectively.[5] Codd's recommendation would have required SQL's logic system be expanded to accommodate a four-valued logic system. Because of this additional complexity, the idea of multiple Null-type values has not gained widespread acceptance in the database practitioners' domain. It remains an active field of research though, with numerous papers still being published.
Sorry, but comrade Codd confused storage with validation. If we have situation, where we should have the data but not have it — ti should be managed on app level or in case of complex DB-side solutions — in just not-null or triggers side.

Inapplicable NULL is something wrong — it's same as have in one DB column for two purposes. For example we have some GOD-mode shitti app and some table with some positive number values and instead of adding new column like something_type, we start to add in this column another values but negative. It's a bad practice. I think industry did it right to leave only one-purpose NULL value.
I think we missed that point raised by Codd.
Sometime NULL means that this particular row (tuple) does not have this value by nature,
sometimes — value is not known (yet), sometimes — any other value is not applicable at all.
It is not a problem of validation or restriction.
We can't solve that problem fundamentally by ignoring it.

PS.
И снова о вечной проблеме отсутствующей информации
Сергей Кузнецов
Hmm… Do you mean the case, when we use single table for two/more classes?

Like this --> martinfowler.com/eaaCatalog/singleTableInheritance.html

?

I just cant' understand those case like «no value by nature». Why then we have column for it??? Can you provide some eral-life example?
Это логика такая двоичная. Была бы троичная, было бы как вам хочется.
А разве введение NULL в стандарте уже не предусматривает «троичной логики»?
А я думал мне одному кажется что это не фичи а баги
Начали за здравие, кончили за упокой. Как раз ваша статья подтверждение, что MySQL это плохо:) Это круто, что на 22533 странице документации есть описание того, что константа TRUE = 0, а FALSE =1, но есть еще такая штука, как ожидаемое поведение. Во многих случаях MySQL ведет себя непредсказуемо, притом не очевидно непредсказуемо(хоть с тем же делением на ноль), и требуется долго локализовывать причину и искать в доках причину.
Не скажу, что MySQL плохая, но лично я ее жутко не люблю, т.к. проблем с ней у меня возникало предостаточно, притом неочевидных. Сам предпочитаю postgresql.
Я таки не постесняюсь и скажу, что после PostgreSQL MySQL вспоминается как страшный сон.
С неудобоваримой документацией, непредсказуемым поведением и жуткой консолью.
Вот расскажите мне, какой профит использовать её, когда есть текущая версия postgres?
Утверждение «в Oracle NULL равен пустой строке» не совсем верно. Пустая строка равна NULL, и то по определению, а не по операции сравнения. Любое же сравнение с NULL имеет результатом NULL. То есть не true или false. И это хорошо.
Опять не соглашусь — даже 0 не равен NULL.

NULL — это неизвестность, а ноль — это конкретное количество.

— Сколько вешать в граммах?
— Да х.з. vs. — Ноль грамм.
Я упоминал ноль? Надо же.
Да нееет :)) Пустая строка и NULL тоже самое.

Пример:

1. Мы пришли и спросили у продавца: «Сколько стоят вот эти конфеты?» Он нам ответил: «0 руб. — они списаны».
VS
2. Мы пришли и спросили у продавца: «Сколько стоят вот эти конфеты?» Он нам ответил: «Ой, я не знаю — я тут первый день!»

Поэтому «НЕ ЗНАЮ» у нас в итоге не есть «НОЛЬ».

ps: простите за кэпс
pps: упс., опять пример про ноль — в первом варианте было «Как тебя зовут?»
ppppps: Короче это очень тонкое различие, которое не сразу осознаётся. Да и больше оно академично, нежели практично.
Молодой человек, вы приписываете мне то, что я не говорил, и наоборот, лишаете того, что я сказал.
На самом деле даже я допустил неточность в первом комментарии, а именно — все верно лишь для строк типа varchar2 или sql-машины. В pl\sql машине есть нюанс:

declare
  a char := '';
begin
  if a is null then
    dbms_output.put_line( 'null' );
  else    
    dbms_output.put_line( 'not null' );
  end if;
end;
/

Эхх… Уважаемый товарищ,

Мне до конкретики в конкретной реализации мало интересно доходить.

Я пытаюсь объяснить что такое NULL вообще, а не только в оракле. Если мы друг-друга не поняли, то и хрен с ним тогда.

Вообще в любой системе хранения данных NULL не должен быть равен пустой строке — это моё личное мнение и не утверждение про DBMS IKS.
Строго говоря, пример неудачный, т.к. в oracle char не может быть «пустой строкой». Просто посмотрите его длину. Отличие действительно есть в pl/sql, но единственное и категорически не связано с char'aми:
create or replace function f_is_null(s varchar2) return varchar2 is
  type tt is table of number index by varchar2(10);
  vt tt;
  e exception;
  pragma exception_init(e,-6502);
begin
  vt(s):=1;
  return case 
           when s is null then 'empty' 
           else 'not empty' 
         end;
exception 
  when e then return 'null';
end;
/
declare
  pkey1 varchar2(10):='';
  pkey2 varchar2(10):=null;
  pkey3 varchar2(10):='filled';
begin
  dbms_output.put_line('pkey1-'||f_is_null(pkey1));
  dbms_output.put_line('pkey2-'||f_is_null(pkey2));
  dbms_output.put_line('pkey3-'||f_is_null(pkey3));
end;
/
Уважаю MySQL, только он умеет троллить в ответ на попытки его затроллить.
Я кстати совсем недавно ввел вопрос, где мне нужно было явно узнать количество дней прошеших с начало нового времени и TO_DAYS как ни странно — вернул неправильный результат.
habrahabr.ru/qa/31239/
select   distinct int_value
    from null_equals_zero
group by group_value;


Как вы понимаете данный запрос вернет нам уникальные значения первой колонки которых как мы знаем два: ноль и NULL
Я такого запроса категорически не понимаю! Что за группировка по второму полю, а дистинкт без агрегата по первому?
Вывести все уникальные значения для каждой группы.
А если подумать?
Если и подумать не помогло:
«Доказательство», что 1=2=3=4:
mysql> create table null_equals_zero(int_value     int,
    ->                               group_value   int
    ->                              )
    -> engine = innodb;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into null_equals_zero
    ->      values (1, 1), (2, 1), (3, 1), (4, 1);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select   distinct int_value
    ->     from null_equals_zero
    -> group by group_value;
+-----------+
| int_value |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)



Думаю этот ответ стоит откомментировать, ибо в вашей логиге заблуждение как раз из первой группы, так как вы думаете, что группировка в MySQL работает так же как и в остальных СУБД.
Рассмотрите этот пример
drop table if exists null_equals_zero;

create table null_equals_zero(int_value     int,
                              group_value   int
                             )
engine = innodb;

insert into null_equals_zero
     values (null, 1), (0, 2), (1, 3);

select   distinct int_value
    from null_equals_zero
group by group_value;
+-----------+
| int_value |
+-----------+
|      NULL |
|         1 |
+-----------+

delete from null_equals_zero;

insert into null_equals_zero
     values (0, 1), (null, 2), (1, 3);

select   distinct int_value
    from null_equals_zero
group by group_value;
+-----------+
| int_value |
+-----------+
|         0 |
|         1 |
+-----------+


и обязательно посмотрите, что будет если группировка делается по одному полю, а выбирается другое.
Еще раз хорошенько подумайте… Нет у вас тут никакой группировки! У вас нет аггрегата! Вы используете исключительно кривой запрос с багом.
Вас не смутило даже то что при изменении порядка вставки элементов поменялся результат запроса?
А вас не смущает писать идиотские невалидные запросы? причем внимательно посмотрите на то, что сами написали:
Как вы понимаете данный запрос вернет нам уникальные значения первой колонки которых как мы знаем два: ноль и NULL
Очень даль что вы считаете меня идотом, а мой пост "имбанутым гавном", о чем вы почему-то не постеснялись написать в твиттере, но постеснялись сказать мне лично в комментариях в этом топике, но я считаю кокретно с вашим вопросом надо разобраться. Новый год все таки. Надо быть позитивнее :)
Я такого запроса категорически не понимаю! Что за группировка по второму полю, а дистинкт без агрегата по первому?

В отличии от Oracle, в котором вы видимо привыкли работать, MySQL допускает ряд фривольностей в написании group by запросов. К примеру если выбрать поля, по которым не произведена группировка, то MySQL возьмет для значения этого поля — первое попавшееся ему значение в таблице. Т.е. тот результат примера, который вы привели далее немного подумав — вполне ожидаем. Вы произвели группировку по полю с единственным значением, а выбрали поле, по которому группировка не производилась. Собственно MySQL взял первое что ему попалось и вывел в результируещем resultset'е. Я же делаю немного другое. Я провожу группировку по полу в котором 4 значения. Так что у меня получается 4 кортежа. Далее я просто выбираю уникальные поля из всего того что у меня получилось, опуская собственно поля группировки. Воспроизведем эти шаги на значения отличных от NULL.
получим 0 и 1
drop table if exists null_equals_zero;
create table null_equals_zero(int_value     int,
                              group_value   int
                             )
engine = innodb;

insert into null_equals_zero
     values (1, 1), (0, 2), (1, 3), (0, 4);

select   int_value
    from null_equals_zero
group by group_value;
+-----------+
| int_value |
+-----------+
|         1 |
|         0 |
|         1 |
|         0 |
+-----------+
4 rows in set (0.00 sec)

select   distinct int_value
    from null_equals_zero
group by group_value;
+-----------+
| int_value |
+-----------+
|         1 |
|         0 |
+-----------+
2 rows in set (0.00 sec)


теперь же попробуем пример из статьи
получим только одно значение
drop table if exists null_equals_zero;
create table null_equals_zero(int_value     int,
                              group_value   int
                             )
engine = innodb;

insert into null_equals_zero
     values (null, 1), (0, 2), (null, 3), (0, 4);

select   int_value
    from null_equals_zero
group by group_value;
+-----------+
| int_value |
+-----------+
|      NULL |
|         0 |
|      NULL |
|         0 |
+-----------+
4 rows in set (0.00 sec)

select   distinct int_value
    from null_equals_zero
group by group_value;
+-----------+
| int_value |
+-----------+
|      NULL |
+-----------+
1 row in set (0.00 sec)

то, что лежит в БД первее 0 или NULL, но не оба значения сразу, что доказывает предыдущий мой пример из комментария.

Я рискну предположить, что тут дело в оптимизаторе, который умеет оптимизировать конструкции типа group by если так же в условии встречается distinct или же limit (с ним тоже есть немало интересных багов, но они сложнее в понимании по этому я их тут не стал приводить, а привел только те, что можно быстро понять).
Если вам непонятет синтаксис, не стоит говорить что это идиотский запрос, ибо парсер отлично этот запрос отработал и даже вернул результат, что уже говорит о том, что запрос написан верно. Стоит все таки действительно немного подумать.

З.Ы. а по поводу неверной оценки статей я с вами частично согласен, ибо на хабре действительно есть множество интересных, по моему мнению технических статей, которые были недостаточно оценены сообществом, но только в этом…
Как меня поражает все-таки наглая попытка увильнут и как-то откреститься от своих же слов и действий. Внимательно смотрите что я процитировал в исходном комменте.
Пройдемся спокойно по списочку:
1. Вы пишете кривой недокументированный запрос
2. Снабжаете его неверной интерпретацией
3. Получаете кривые данные кривого запроса
4. Пытаетесь сделать из этих данных какой-то совершено не взаимосвязанный вывод.
5. В выводе косвенно заявляете о том, что все написанное выше это ошибки разработчиков mysql, но не уточняете, что как минимум этот ваш запрос — это именно ваша ошибка.

Я несколько раз пытался намекнуть, а после уже прямым текстом сказал, что запрос вы пишете невалидный. Багом является то, что не выдает ошибки при запросе об отстутствии агрегата. Не пытайтесь анализировать на невалидных запросах, что вы постоянно пытаетесь делать как и в случаете с enum.
1. запрос не кривой, он успешно работает, и даже всегда за исключением одного случая, возвращает корректные данные.
2. интерпретация запросов неверна в каждом из моих комментариев, но SQL не врет ибо в данном конкретном случае NULL действительно неотличим от 0
3. см 2 пункт выше и пример с 0 и 1, чем они кривой?
4. во всей стаье выводы совершенно бредовые, неужели вы этого не заметили?
5. все это является багами MySQL если вы захотите вы даже сможете найти их номера. Когда я получал данные ошибки я сознательно не заводил баги, ибо пробежавшись по bugs.mysql.com находил очень похожие. Все эти баги не решены до сих пор. Если хотите можете поискать, вон выше сам разработчик MySQL как раз завел новый баг на основании нашего обсуждения этих фич. Вот он bugs.mysql.com/bug.php?id=67978. И поверьте каждый описанный случай основан именно на баге. Конечно сами запросы в которых они возникли гораздо сложнее, но для статьи я переделал их, для того чтобы максимально упростить.
Чтобы не продолжать бессмысленный диспут, приведите пруф на документацию, где описано поведение при указании поля вне агрегатов в группировках
А внимательнее? Где там детерминировано поведение дистинкта с этим расширенным поведением? Вот сортировка описана, а дистинкт? То есть вы успешно нашли способ стрельнуть себе в ногу, поздравляю!
Э… Там как бы и про лимит не написано, и про остальные сотню комманд которые там можно впихнуть тоже не слова.
Подумайте: почему при любых значениях кроме NULL и 0 этот запрос работает верно? Вот вы упрямы. Вы свои ошибки в сужениях никогда не признаете?
Заметьте, чуть выше один комментатор написал про bigint, и после длительных баталий в личке, я наконец-то понял что он имеет ввиду, и что это действительно может запутять неопытного читателя, и убрал указанный им пунк. Вы же упрямо не хоите верить ни одному запросу, который я привожу вам в качестве аргументации.
Вы даже не знали про cинтаксис комманды group by! И упорно настаиваете на том, что вы правы, делая это самым хамским образом у всех за спиной… Не надо путать и оскорблять ни меня ни читателя, пожалуйста.
Во-первых, раз используете расширение с нестандартным поведением да еще и прямо противоречащим ansi-стандарту, то должны отталкиваться от документации. Во-вторых, один из известных и применяемых в разных субд алгоритмов дистинкта — это сортировка либо значений/хэшей, что должно было вселить сомнения в валидности.
За спиной? Имхо вполне себе публично, только вот перевирать мои слова не надо.
Вы используете исключительно кривой запрос с багом.

Странно я вроде так и писал в своей статье, очень жаль что вы приняли все за чистую монету. Конечно баги на то и баги, что не везде встречаются. Я просто сделал подборку из тех багов с которыми пришлось столкнуться на протяжении последних трех лет работы…
Странно я вроде так и писал в своей статье
Вы нигде не указали, что ваш запрос невалиден! Неудивительно, что вы получаете недокументированный результат на запрос невалидный с точки зрения той же документации.
уберите в запросе дистинкт-может быть это вас натолкнет на верный путь. хотя вам проще почитать про стандарт сиквела
Ваше нежелание почитать как конкретно реализован group by в mysql не позволяет мне продолжать с вами дискуссию. По сему во избежание холиваров предлагаю каждому из нас остаться при своем мнении.
Ваше нежелание думать при написании запросов не позволяет мне продолжать с вами дискуссию.
Немного помогу в споре. Запрос:

select   distinct int_value
    from null_equals_zero
group by group_value;

изначально некорректный, но отрабатывается из-за специфических особенностей MySQL. Вот так можно проверить валидность запроса:

SET sql_mode = 'ONLY_FULL_GROUP_BY';

SELECT distinct int_value
FROM null_equals_zero
group by group_value;

Error Code: 1055. 'test.null_equals_zero.int_value' isn't in GROUP BY

И ещё, то что один равно двум доказывается несколько иным способом.
Как-то так
drop table if exists two_numbers;

create table two_numbers (
  number_value enum('1','0')
);

insert into two_numbers(number_value)
     values ('0'), ('1');

select concat(max(number_value), ' = ', max(number_value + 0)) one_equals_two from two_numbers;
+----------------+
| one_equals_two |
+----------------+
| 1 = 2          |
+----------------+
1 row in set (0.00 sec)

и именно из-за этого я не люблю ENUM, но вроде это тоже документировано
Некорректно сравнивать максимум енума(де факто другой формат) с числом.
Странно но официальная документация с вами не согласна
Sorting ENUM values are sorted based on their index numbers, which depend on the order in which the enumeration members were listed in the column specification. For example, 'b' sorts before 'a' for ENUM('b', 'a'). The empty string sorts before nonempty strings, and NULL values sort before all other enumeration values. To prevent unexpected results when using the ORDER BY clause on an ENUM column, use one of these techniques: Specify the ENUM list in alphabetic order. Make sure that the column is sorted lexically rather than by index number by coding ORDER BY CAST(col AS CHAR) or ORDER BY CONCAT(col).
Т. е. Я так понимаю вас не смущает что максимальный элемент имеет индекс 2 хотя я четко вижу что согласно декларации индекс 2 имеет элемент '0' а никак не '1'? Вы глубже смотрите а не на то как называются поля и таблицы.
А еще раз подумать:
Sorting ENUM values are sorted based on their index numbers,
Давайте я на всякий случай переведу, чтобы исключить разночтения.
Сортировка. ENUM значения сортируются на основании их номера индекса

Соотвественно было бы разумно ожидать что в перечислении данного типа:
enum('1','0')

элемент с именем '1' имеет порядковый номер 1, а элемент с именем '0' порядковый номер 2.
Тут как бы MySQL с нами полностью согласен:
select   number_value, number_value   0
    from two_numbers
order by number_value desc;
 ------ ------ 
| val  | ord  |
 ------ ------ 
| 0    |    2 |
| 1    |    1 |
 ------ ------ 

Но вот те кто писал функцию MAX(MIN) об этом видимо не знали, так как
select min(number_value) min_val from two_numbers;
 --------- 
| min_val |
 --------- 
| 0       |
 --------- 
1 row in set (0.00 sec)

т.е. сравнение производится не согласно индексам, как они заданы при создании, а просто в лексикографическом порядке. При чем первая же арифметическая операция заставляет работать MySQL согласно документации.
А еще раз подумать:

Я почитал ваши статьи, ряд из них я нашел даже интересными, ибо сам раньше программировал под Oracle, так что действительно — подумайте, вроде статья старая, никто никуда не спешит.
Если вы программировали под оракл, да и вообще с приведением типов то вы должны понимать, что только приведение типа детерминирует операцию. Именно +0 и делает неявный автокаст.
При чем тут автокаст? Автокаст — это workaround для этого бага. сортировка все сортирует по индексам и верно, почему максимальных по индексу элемент '1' а не '0'?
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории