Почему count не возвращает 0 в пустой таблице

#sql #sybase #sap-ase

#sql #sybase #sap-ase

Вопрос:

Мне нужно посчитать строки таблицы, но я был предупрежден о необычном поведении count (*).

count (*) не возвращает результаты, когда я использую многоколоночную выборку в пустой таблице. Но возвращает ожидаемые результаты (0 строк), если я удаляю другие столбцы из инструкции select (выбор одного столбца).

В приведенном ниже коде вы найдете множество тестов, которые покажут вам, о чем я говорю.

Структура приведенного ниже кода такова:

1) Создание таблицы

2) Многоколоночный выбор в тестах пустой таблицы, который возвращает неожиданные результаты

3) Выбор одного столбца в тесте пустой таблицы, который возвращает ожидаемый результат

4) Многоколоночный выбор в тесте заполненной таблицы, который возвращает ожидаемый результат

Вопрос

Учитывая эти результаты, мой вопрос:

Почему многоколоночный выбор в пустой таблице не возвращает 0, а выбор в одном столбце возвращает его?

Определение ожидаемых результатов

Ожидаемые результаты для меня означают:

если таблица пуста, count(*) возвращает 0.

Если таблица не пуста, count возвращает количество строк

—СОЗДАТЬ ТЕСТОВУЮ ТАБЛИЦУ

 CREATE TABLE #EMPTY_TABLE(
    ID INT
)

DECLARE @ID INT
DECLARE @ROWS INT
  

—МНОГОКОЛОНОЧНЫЙ ВЫБОР С ПУСТОЙ ТАБЛИЦЕЙ

 --assignment attempt (Multi-column SELECT)
SELECT @ID = ID, @ROWS = COUNT(*) 
FROM #EMPTY_TABLE

--return Null instead of 0
SELECT @ROWS Test_01 , ISNULL(@ROWS, 1 )'IS NULL'

--Set variable with random value, just to show that not even the assignment is happening
SET @ROWS = 29

--assignment attempt (Multi-column SELECT)
SELECT @ID = ID, @ROWS = COUNT(*) 
FROM #EMPTY_TABLE

--return 29 instead of 0
SELECT @ROWS Test_02
  

—ВЫБОР ОДНОГО СТОЛБЦА С ПУСТОЙ ТАБЛИЦЕЙ

 --assignment attempt (Single-column SELECT)
SELECT @ROWS = COUNT(*)
FROM #EMPTY_TABLE

--returns 0 the expected result
SELECT @ROWS Test_03
  

—ВЫБОР ИЗ НЕСКОЛЬКИХ СТОЛБЦОВ С ЗАПОЛНЕННОЙ ТАБЛИЦЕЙ

 --insert a row
INSERT INTO #EMPTY_TABLE(ID)
SELECT 1

--assignment attempt
SELECT @ID = ID, @ROWS = COUNT(*) 
FROM #EMPTY_TABLE

--Returns 1
SELECT @ROWS Test_04
  

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

1. просто мысль: select ID from #empty_table вернет результирующую строку, только если в таблице есть хотя бы одна строка, однако, select count(*) from #empty_table (неявная группировка по) должна возвращать результирующую строку, даже если в таблице нет строк. исходя из предположения, что это допустимый запрос в sybase, я бы предположил, что результаты этих двух запросов объединены для ответа на ваш запрос, поскольку первый запрос не выдает строк, объединенный запрос также не даст результата, следовательно, никаких назначений. но … отказ от ответственности: у меня нет никакого опыта работы с sybase.

2. спасибо за ваш ввод, Jakumi. На самом деле я не знаю, происходит ли такое поведение также в других базах данных, таких как mysql или sqlserver. но, согласно моему пониманию, count (*) должен возвращать результаты, даже если строк нет. существуют условия, при которых это не применяется, например, при использовании group by (конкретный случай), но это не тот случай

3. Интересно. Таким образом, Sybase допускает, что неагрегированный столбец не включается в group by. Кстати, в T-Sql для MS Sql Server есть тот же эффект. Протестируйте здесь . За исключением того, что тогда требуется ГРУППИРОВАТЬ ПО.

4. явная GROUP BY указывает только группы, которые будут использоваться для агрегирования, по умолчанию все строки принадлежат одной группе, обычно требуется явная group by только для правильного определения столбцов, когда есть несколько значений для негруппированных столбцов (возможно).

5. Дополнительный тест: добавьте другой идентификатор в эту таблицу. Я ожидаю, что результатом будут две записи, каждая со счетом 1

Ответ №1:

Итак, я прочитал о механизмах группировки sybase и пришел к выводу, что в вашем запросе у вас есть «Расширенный столбец Transact-SQL» (см.: документы по group by в разделе Использование -> Расширения Transact-SQL для group by и having):

Список выбора, включающий агрегаты, может включать расширенные столбцы, которые не являются аргументами агрегатных функций и не включены в предложение group by. Расширенный столбец влияет на отображение конечных результатов, поскольку отображаются дополнительные строки. * (выделено мной)

(что касается *: это последнее утверждение на самом деле неверно в вашем конкретном случае, поскольку одна строка превращается в нулевые строки)

также в документах по группированию по в разделе Использование -> Как группировать по и выполнять запросы с агрегатами, вы найдете:

Предложение group by собирает оставшиеся строки в одну группу для каждого уникального значения в выражении group by. При пропуске group by создается одна группа для всей таблицы. (выделено мной)

Итак, по сути:

  1. наличие a COUNT(*) приведет к тому, что весь запрос будет агрегированным, поскольку это агрегированная функция (вызывающая неявное GROUP BY NULL )
  2. добавление ID в SELECT предложение затем расширит первую группу (не состоящую из строк) на содержащиеся в ней строки (none) и объединит ее со столбцами совокупного результата.

в вашем случае: счетчик равен 0, поскольку вы также запрашиваете идентификатор, для каждого идентификатора будет сгенерирована строка, к которой добавляется счетчик. однако, поскольку в вашей таблице нет строк, в ней вообще нет строк результатов, следовательно, нет назначений. (Некоторые примеры приведены в связанных документах, и поскольку id отсутствует, а существующий id должен быть в столбце id вашего результата, …)

чтобы всегда получать count, вам, вероятно, следует только SELECT @ROWS = COUNT(*) и выбирать идентификаторы отдельно.

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

1. Отличный поиск @Jakumi, к сожалению, этот конкретный пример не отображается в ссылках, но они дают довольно хорошее представление о том, что может произойти в этом конкретном случае. После прочтения я прихожу к выводу, что группа создана без строк, и поскольку она расширена, она будет объединена с таблицей, в которой нет строк, и результатом (внутреннего) объединения пустой таблицы с другой таблицей является пустая таблица. Спасибо за ссылки.

2. @Nelssen — Правильный вывод из неправильных аргументов ;-). Ваша проблема не имеет ничего общего с JOIN (так сказать), но все с GROUP BY . Как писал Якуми: не на что группировать для COUNT() , поэтому по определению ничего не может быть отображено. Вот как это работает. Вы правы, что это может быть вызвано присоединением к пустым таблицам, но даже если ничего нет, COUNT() возвращает что-то, если только в миксе нет GROUP BY .

Ответ №2:

Если вы считаете строки и пытаетесь получить идентификатор, когда строк нет — вам нужно проверить, существуют ли они. Что-то вроде этого:

 SELECT COUNT(*), 
    (CASE WHEN EXISTS(SELECT ID FROM EMPTY_TABLE) THEN (SELECT ID FROM EMPTY_TABLE) ELSE 0 END) AS n_id 
FROM EMPTY_TABLE
  

В случае, если в строке больше 1 строки, вы получите ошибку подзапроса.

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

1. Привет @ M.N., я не верю, что это отвечает на вопрос

2. @Nelssen, если вам нечего возвращать — запрос вернет … ничего (не NULL). COUNT и EXISTS возвращают не исходные данные из таблицы, но со СЧЕТОМ 0, если данных нет, и с EXISTS все, что вам нужно. Просто попробуйте что-нибудь вроде этого: ВЫБЕРИТЕ ID / 100 КАК ИЗ EMPTY_TABLE, ГДЕ ID = 999 [ничего]. И ВЫБЕРИТЕ 100 / (ВЫБЕРИТЕ g1 ИЗ EMPTY_TABLE, ГДЕ ID = 999) [NULL]. Итак, в вашем случае запрос должен что-то возвращать, если вы хотите использовать это с count.

3. Привет, М.Н., Спасибо за ваши усилия, это не ответ на вопрос «Почему нам нужно сделать это обходное решение». Это решение работает только для очень специфических случаев, которые имеют 0 и 1 строки. Я бы проголосовал за это, если бы это работало во всех сценариях (0, 1 или много строк), но не могу сделать это так, как сейчас. Убедитесь, что в следующий раз вы предоставляете более устойчивые решения 🙂

Ответ №3:

Этот запрос:

 SELECT @ID = ID, @ROWS = COUNT(*) 
FROM #EMPTY_TABLE
  

Проблема в том, что COUNT(*) это делает запрос агрегирования, но вы также хотите вернуть ID . Нет GROUP BY .

Я подозреваю, что ваша основная проблема заключается в том, что вы игнорируете такие ошибки.

Этот SQL Fiddle использует SQL Server (который похож на Sybase). Однако сбой является довольно общим и вызван запросом, который не будет работать практически в любой базе данных.

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

1. Даже при использовании group by результат будет тем же. И вы правы — он игнорирует ошибку.

2. Привет @Gordon Linoff, я подозревал, что это обычное поведение, но не тестировал его. Вот почему я действительно хотел понять это поведение. Какие ошибки я игнорирую? если не отображаются ошибки? Я не могу игнорировать ошибку, если она не существует. Что я мог бы игнорировать, так это некоторое поведение, а не ошибки, поскольку в представленных мной случаях их нет.