Как стать автором
Обновить
166.48
Тензор
Разработчик системы Saby
Сначала показывать

Продолжаем улучшать наш инструмент анализа планов PostgreSQL explain.tensor.ru. Сегодня мы представляем новую подсказку-рекомендацию и расширенный вариант визуального представления хода выполнения (посмотреть пример):

Новая подсказка-рекомендация и распределение времени в порядке выполнения узлов
Новая подсказка-рекомендация и распределение времени в порядке выполнения узлов

Ловим кривые индексные условия

Периодически приходится разбирать ситуации, когда вроде бы давно уже отлаженный запрос к PostgreSQL начинает "съезжать с катушек" и дико "тупить".

Зачастую оказывается, что виной тому передача в качестве параметра-массива для ключа индекса какого-то заведомо-ложного условия типа пустого массива (= ANY('{}'::integer[])) или NULL (= ANY(NULL::integer[])).

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

В принципе, это ответственность самого разработчика передавать корректные значения. Но чтобы вам было удобнее находить такие кейсы, мы сделали новую подсказку в анализе плана.

Ход выполнения запроса

Выполнение запроса - это проход дерева плана "снизу - вверх", но сам план при этом представляет из себя дерево "сверху - вниз". Неудобненько...

Помимо узлов самого плана, в полное время выполнения запроса входит плюсом еще и время планирования (Planning Time) и время передачи данных (Execution Time), которые могут составлять весьма солидную долю.

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

Теги:
Всего голосов 4: ↑4 и ↓0+4
Комментарии0

В прошлом посте я показывал, один из вариантов бессмысленного усложнения запроса использованием CASE, сегодня - продолжение.

Еще пара бесполезных CASE
Еще пара бесполезных CASE

Тут представлена попытка заNULLить значение, если оно равно чему-то.

Но ведь в PostgreSQL есть функция nullif, которая делает ровно то же самое:

NULLIF(значение1, значение2)

Функция NULLIF выдаёт значение NULL, если значение1 равно значение2; в противном случае она возвращает значение1. Это может быть полезно для реализации обратной операции к COALESCE. В частности, для примера, показанного выше:

SELECT NULLIF(value, '(none)') ...

В данном примере если value равно (none), выдаётся null, а иначе возвращается значение value.

То есть в примере выше стоит переписать короче и понятнее:

, nullif(sdate, '1900-01-01') sdate
, nullif(mdate, '1900-01-01') mdate

Теги:
Всего голосов 7: ↑7 и ↓0+7
Комментарии0
Бесполезный CASE
Бесполезный CASE

В своей лекции про "сложные" SELECT я уже рассказывал про возможности оператора CASE, а еще раньше - про возможности оптимизации выполнения запросов с его помощью.

Но иногда он вовсе не нужен! Обратите внимание на картинку сверху...

Посмотрим на использованный тут синтаксис CASE:

CASE
  WHEN условие THEN результат
  [WHEN ...]
  [ELSE результат]
END

Или еще конкретнее:

CASE
  WHEN условие THEN TRUE -- [условие IS TRUE]
  ELSE FALSE             -- [условие IS FALSE, IS NULL]
END

Хм... То есть результат этого CASE эквивалентен значению условия с точностью до NULL!

При обращении условия в NULL такой CASE вернет FALSE, но этого же поведения можно добиться с помощью coalesce:

coalesce(условие, FALSE)

Но если мы говорим о конкретном примере с условием EXISTS, то уж оно-то точно никак не может принимать значение NULL! Значит, coalesce-обертка нам тут не требуется и эту часть запроса можно сократить до одного лишь условия, без всяких CASE:

EXISTS(
  SELECT
    NULL
  FROM
    _inforg20687 t15
  WHERE
    t15._fld1329 = 0::numeric AND
    t15._fld20688rref = t6._idrref AND
    t15._fld20689_type = '\\010'::bytea AND
    t15._fld20689_rtref = '\\000\\000\\001\\010'::bytea AND
    t15._fld20689_rrref = t4._fld6883rref
)

В общем, пишите меньше SQL-кода - и ваши запросы "будут мягкими и шелковистыми"!

Теги:
Всего голосов 8: ↑8 и ↓0+8
Комментарии0

Если вдруг вам понадобилось базу IP2Location перевести из DECIMAL-представления IP-адресов в "родной" для PostgreSQL тип inet, то для IPv4-адресов все будет тривиально:

'0.0.0.0'::inet + ipnum::bigint

А вот для преобразования числа к формату IPv6-адреса придется проявить немного изобретательности:

  • "математически" разбиваем число на 8 двухбайтовых сегментов по (2 ^ 16) ^ i

  • каждое значение преобразуем в шестнадцатиричную систему счисления и добиваем лидирующими нулями

  • склеиваем сегменты через двоеточие и кастуем к inet

array_to_string(ARRAY(
  SELECT
    lpad(to_hex(trunc(
      ipnum % (2::numeric(39,0) ^ ((i + 1) * 16)) / (2::numeric(39,0) ^ (i * 16))
    )::integer), 4, '0')
  FROM
    generate_series(7, 0, -1) i
), ':')::inet

В принципе, после этого мы можем "свернуть" ip_from и ip_to в подсеть, не обращая внимания на исходный формат:

inet_merge(ip_from, ip_to) subnet

А если проиндексируем эти подсети с помощью gist...

CREATE INDEX ON country_inet USING gist(subnet inet_ops);

... то сможем по индексу быстро определять принадлежность произвольного IPv4/IPv6-адреса подсетям с помощью соответствующих операторов примерно таким запросом:

SELECT
  *
FROM
  country_inet
WHERE
  subnet >> '8.8.8.8' AND
  country <> '-'
ORDER BY
  masklen(subnet) DESC
LIMIT 1;

Теги:
Всего голосов 7: ↑7 и ↓0+7
Комментарии0

IS NOT NULL + OR

Иногда внутри SQL-запроса возникает необходимость проверить наличие/отсутствие NULL-значения в некотором наборе полей:

a IS NOT NULL OR b IS NOT NULL OR c IS NOT NULL

Но то же самое по смыслу условие можно записать гораздо короче с помощью функции coalesce:

coalesce(a, b, c) IS NOT NULL

Подробнее об особенностях работы со сложными выражениями можно прочитать в статье "PostgreSQL Antipatterns: вычисление условий в SQL".

IS NOT NULL + AND

Немного изменим условие - заменим OR на AND:

a IS NOT NULL AND b IS NOT NULL AND c IS NOT NULL

Тут нам поможет ROW-конструктор:

(a, b, c) IS NOT NULL

IS NULL + AND

Теперь заменим IS NOT NULL на IS NULL:

a IS NULL AND b IS NULL AND c IS NULL

Тут достаточно вспомнить из логики, что (A and B) эквивалентно not(not A or not B), а (A or B) - not(not A and not B), поэтому легко применяем not к варианту IS NOT NULL + OR:

coalesce(a, b, c) IS NULL

Или с помощью ROW-конструктора:

(a, b, c) IS NULL

Разница будет заключаться в том, что coalesce вычисляет выражения "лениво" (см. "«Ленивый сахар» PostgreSQL").

IS NULL + OR

Остался последний вариант:

a IS NULL OR b IS NULL OR c IS NULL

Тут мы можем "обратить" вариант IS NOT NULL + AND:

NOT (a, b, c) IS NOT NULL

Заметьте, что пара NOT тут "не сокращается", иначе получился бы предыдущий вариант.

Теги:
Всего голосов 9: ↑9 и ↓0+9
Комментарии2

Периодически в коде запросов и "заточенных" под них индексов наблюдаю примерно подобные куски:

coalesce("Фамилия", '') || ' ' || coalesce("Имя", '') || ' ' || coalesce("Отчество", '')

Понятно, что тут хотели обезопасить себя от заполненности любого из полей NULL-значением, чтобы случайно вся строка не заNULL'илась.

Правда, тут возникают некоторые артефакты в виде "висящих пробелов" типа ' Иван Иванович' или 'Иванов Иван '.

Но ведь есть решение изящнее и проще - функция concat_ws:

concat_ws(' ', "Фамилия", "Имя", "Отчество")

RTFM!

Теги:
Всего голосов 7: ↑7 и ↓0+7
Комментарии8

Информация

Сайт
saby.ru
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия