SAFE-режим: NULL вместо падения
Чему научишься
- превращать мусор в вместо падения:
SAFE_CASTпротивCAST - делить без страха на ноль:
SAFE_DIVIDE - писать короткие условия :
IFиIFNULLвместо длинныхCASEиCOALESCE - замечать, где SAFE-семейство прячет баги, и останавливать запрос намеренно:
ERROR()
Приборный отсек
Сегодня комендант открывает тебе новый раздел архива: приборные журналы колоний. Датчики жизнеобеспечения слали показания прямо в эфир, а приёмники складывали всё как есть — строками. Там, где связь рвалась, в строке остался мусор: «н/д», обрывки, пустота.
В Postgres ты вычистил бы такое заранее — ETL, регулярки, . Здесь чистить некому: архив мёртв, данные уже никто не перепишет. Запрос обязан выживать на грязных данных сам.
КВЕРИ: Я поискал в этом слово SAFE. Нашёл целое семейство. Кажется, архив знал, с какими данными ему предстоит жить.

CAST падает целиком, SAFE_CAST — нет
Обычный CAST здесь тот же, что в Postgres, — и так же смертелен: одно неприводимое значение убивает весь запрос:
SELECT CAST('н/д' AS INT64) AS reading;
-- Could not cast literal "н/д" to type INT64
На таблице в миллиард строк это значит: запрос работал, жёг байты — и умер где-то на 998-м миллионе строк. Счётчик, кстати, списал всё.
Диалектная пара — SAFE_CAST: тот же синтаксис, но неприводимое значение становится NULL, а запрос доезжает до конца. Посмотри на четыре показания одного датчика:
SAFE_DIVIDE: ноль в знаменателе
Вторая классика грязных данных — деление на ноль. Обычное / падает и здесь:
SELECT 1 / 0; -- division by zero
SAFE_DIVIDE(a, b) возвращает NULL, когда b — ноль или . В Postgres ты писал a / NULLIF(b, 0) — идиома работает и тут, но даёт готовую функцию.
Посчитаем терминалов: сколько просмотров товара у жителя закончилось покупкой. У некоторых жителей просмотров нет вообще — вот и ноль в знаменателе:
IF и IFNULL: условные по-диалектному
CASE из Postgres работает без изменений, но для двух самых частых случаев держит короткие формы:
IF(cond, a, b)— вместоCASE WHEN cond THEN a ELSE b END;IFNULL(x, default)— вместоCOALESCE(x, default)на два аргумента.
-- Postgres:
CASE WHEN total >= 1000 THEN 'крупный' ELSE 'обычный' END
-- BigQuery:
IF(total >= 1000, 'крупный', 'обычный')
Разметим заказы станции снабжения:
IFNULL дружит с SAFE-семейством: цепочка «посчитай опасное → подставь запасное» — фирменный приём . И он встраивается прямо в звёздочку из первого урока:
SELECT * EXCEPT(raw_payload) REPLACE(IFNULL(revenue, 0) AS revenue)
FROM report
— «все колонки, кроме мусорной, а дыры в revenue заткни нулём». Ровно этот приём ждёт тебя в задачах тренажёра ниже. Примерим цепочку на :
Когда SAFE прячет баг
Перечитай вывод: у жителя 6 0. А теперь его строка в предыдущей ячейке: 0 просмотров и 1 покупка. Это не «не конвертится» — это покупка мимо витрины: другой путь к покупке или дыра в логировании. SAFE_DIVIDE честно ответил («считать нечего»), а наш IFNULL(..., 0) уверенно соврал: «ноль».
С ещё коварнее: AVG игнорирует NULL, но учитывает 0. Средняя конверсия «по жителям с NULL» и «по жителям с нулями» — два разных числа, и оба выглядят правдоподобно.
Правило: SAFE_ — для мусора, который ты решил игнорировать осознанно.* Для невозможных состояний у есть противоположность — ERROR():
SELECT IF(views = 0 AND purchases > 0,
ERROR('покупка без просмотра: проверь логирование'),
SAFE_DIVIDE(purchases, views)) AS conversion
FROM per_user
ERROR() намеренно роняет запрос с твоим сообщением: лучше упасть громко, чем молча посчитать ерунду. Живой ячейки на ERROR() в главе нет намеренно: её успешный исход — красный отказ, а в тренажёре это неотличимо от сломанного примера. Синтаксис архив принимает: пока условие ложно, ветка с ERROR() просто не срабатывает — сработавшая роняет запрос целиком.
Префикс SAFE. для остальных функций
SAFE-семейство шире пары функций: почти любую скалярную функцию можно вызвать с префиксом SAFE. — и вместо ошибки она вернёт . Функции в примере взяты как первые попавшиеся: PARSE_DATE просто пытается прочитать дату вида ГГГГ-ММ-ДД, разбирать её маску сейчас не нужно. Без префикса обе строчки ниже роняют запрос:
SELECT SUBSTR('архив', 0, -2); -- SUBSTR: length must be positive number
SELECT PARSE_DATE('%F', 'не дата'); -- error parsing [не дата] with format [%F]
Префикс работает только со скалярными функциями: SAFE.SUM(...) архив отвергает прямо при разборе (SAFE is not supported for function sum), а к операторам вроде / префикс не приставить синтаксически — поэтому SAFE_DIVIDE и SAFE_CAST и оформлены отдельными функциями.
Важное: SAFE. глушит ошибку ВЫЧИСЛЕНИЯ, а не ошибку типов. SAFE.SUBSTR(order_id, 2, 2) из прошлого урока всё равно упадёт с No matching signature: неверные типы аргументов ловятся до запуска, глушить там нечего. Сравни три вызова — два опасных под префиксом и один заведомо исправный:
CAST('abc' AS INT64) и SAFE_CAST('abc' AS INT64)?AVG(IFNULL(SAFE_DIVIDE(purchases, views), 0)). У трёх жителей конверсия 1.0, ещё у двоих views = 0 (SAFE_DIVIDE дал NULL). Что выйдет — и что вышло бы без IFNULL?- Крупный заказ или обычныйEASY
- Числовой код без сбоевEASY
- Выручка без пустых значенийMEDIUM
- Выручка с шагом в сотнюMEDIUM
Главное из урока
SAFE_CAST(x AS T)— вместо падения; обычныйCASTроняет весь запросSAFE_DIVIDE(a, b)— NULL при нуле в знаменателе; замена постгресовомуa / NULLIF(b, 0)IF(cond, a, b)иIFNULL(x, d)— короткие формыCASEиCOALESCE- фирменная связка:
SELECT * EXCEPT(...) REPLACE(IFNULL(col, 0) AS col) - SAFE — осознанное игнорирование мусора; для невозможных состояний —
ERROR(); для прочих скалярных функций — префиксSAFE.
Документация: функции преобразования, условные выражения.
Ты научил запросы выживать. Но иногда архив всё же отвечает отказом — и его отказы надо уметь читать. Следующий урок — язык ошибок BigQuery.