Почему COALESCE ведёт себя странно в моём SQL-запросе?

Доброго дня!
  • 15 мая 2026 г. 7:00
Ответы на вопрос 2
Доброго дня! Нужна чуть больше информации, чтобы точно сказать, почему у вас “странное” поведение (какая СУБД, сам запрос, пример данных и ожидаемый/фактический результат). Ниже — перечень типичных причин и как их проверять/устранять: возможно, одна из них и есть ваша проблема.

Что важно проверить (и что чаще всего вызывает сюрпризы)

1) Типы данных и приоритет типов
- COALESCE возвращает значение одного типа, и СУБД приводит аргументы к общему типу по правилам приведения/приоритетов. Из‑за этого возможны ошибки или неявные приведения.
- Пример (SQL Server): SELECT COALESCE('abc', 1) приведёт к ошибке «conversion failed», потому что СУБД попытается привести 'abc' к INT (int имеет более высокий приоритет).
- Решение: явно привести тип нужным CAST/CONVERT, например COALESCE(CAST(col AS varchar(100)), other_col).

2) NULL vs пустая строка / ноль
- В разных СУБД '' не равен NULL. Если ожидаете, что пустая строка считается «нет значения», используйте NULLIF или CHECK (например NULLIF(col,'') или CASE WHEN col = '' THEN NULL ELSE col END).
- Для чисел: 0 ≠ NULL. Не путайте COALESCE(col,0) (подставит 0 при NULL) и SUM(COALESCE(col,0)) (каждый NULL превратится в 0) vs COALESCE(SUM(col),0) (подставит 0 если результат агрегата NULL).

3) Короткое/ленивое вычисление (short‑circuit) и побочные эффекты
- В некоторых случаях выражения в COALESCE могут быть приведены/оценены даже если первый аргумент не NULL (например, из‑за приведения типов или реализации). Это может привести к ошибкам (деление на ноль, обращение к несуществующему столбцу в подзапросе и т.п.).
- Надежный способ избежать вычисления опасного выражения — использовать CASE WHEN, например:
  CASE WHEN denom = 0 THEN NULL ELSE numerator/denom END
  CASE гарантированно не вычисляет ветку, если условие её исключает (это стандартизированное поведение и безопаснее в подобных случаях).

4) COALESCE vs ISNULL/IFNULL (диалект)
- В SQL Server ISNULL(a,b) возвращает тип первого аргумента (и длину varchar первого аргумента), COALESCE использует правила совместимости типов/приоритета — поведение отличается.
- В MySQL аналог — IFNULL, в Postgres — COALESCE (но правила типов у Postgres строже: типы должны быть совместимы или явно указаны).

5) Колляция/кодировка строк
- Если аргументы строковых типов с различными коллациями — может быть ошибка или неожиданное приведение. Явно указывайте COLLATE или приводите в один тип/коллацию.

6) COALESCE с подзапросами/функциями
- Если аргументы — подзапросы, они могут выполняться/пытаться выполниться все (в зависимости от СУБД/плана), что даст неожиданный эффект. Лучше вынести подзапросы в JOIN/OUTER APPLY или использовать CASE.

Как отладить
- Покажите типы колонок: в Postgres — pg_typeof(col), в SQL Server — SQL_VARIANT_PROPERTY или INFORMATION_SCHEMA.COLUMNS.
- Попробуйте отдельно выполнить каждое выражение.
- Явно CAST/CONVERT каждый аргумент в ожидаемый тип и проверьте результат.
- Замените COALESCE на CASE WHEN для теста (чтобы проверить проблемы с вычислением аргументов).

Короткие примеры

- Проблема с типами (SQL Server):
  SELECT COALESCE('123', 456); -- вернёт 123 как INT (строка приведена к INT)
  SELECT COALESCE('abc', 1);   -- ошибка: conversion failed

  Исправление:
  SELECT COALESCE(CAST(col1 AS varchar(100)), CAST(col2 AS varchar(100)));

- Избежание деления на ноль:
  -- плохой вариант (может быть ошибка, если 1/0 вычислится):
  SELECT COALESCE(NULL, 1/0);
  -- безопасный:
  SELECT CASE WHEN denom = 0 THEN NULL ELSE numerator/denom END;

Что мне нужно от вас, чтобы помочь точнее
- СУБД (Postgres, SQL Server, MySQL, Oracle и т. п.)
- Сам SQL‑запрос (или упрощённый пример)
- Пример(ы) исходных данных и ожидаемый/фактический результат
- Любые ошибки, которые СУБД выводит

Пришлите эти данные — проанализирую и подскажу конкретное решение.
тут MySQL кривит на связке CTE + оконная функция + COALESCE — оптимизатор скорее всего объединяет CTE в основной запрос так, что rp.start_report_date оказывается NULL внутри COALESCE. <br/> <br/> Чище всего — используй третий аргумент LAG вместо обёртки в COALESCE: <br/> <pre><code>lag(t.rasp_time, 1, rp.start_report_date) over(partition by t.vg_id order by t.rasp_time) as prev_rasp_time</code></pre> <br/> <br/> Если не поможет — разнеси по уровням: вычисли LAG в raw_data как отдельный столбец lag_rasp_time, COALESCE уже в следующем SELECT: <br/> <pre><code>coalesce(lag_rasp_time, start_report_date) as prev_rasp_time</code></pre> <br/> <br/> Можно ещё попробовать хинт NO_MERGE для report_period — Akina в комментарии намекнул правильно.
Похожие вопросы