PostgreSQL: создать индекс по длине всех полей таблицы

У меня есть таблица под названием profile, и я хочу заказать их, какие из них наиболее заполнены. Каждый из столбцов является столбцом JSONB или столбцом TEXT. Мне это не нужно в значительной степени, поэтому я обычно заказывал следующее:

SELECT * FROM profile ORDER BY LENGTH(CONCAT(profile.*)) DESC;

Однако это медленный процесс, поэтому я хочу создать индекс. Однако это не работает:

CREATE INDEX index_name ON profile (LENGTH(CONCAT(*))

Не делает

CREATE INDEX index_name ON profile (LENGTH(CONCAT(CAST(* AS TEXT))))

Не могу сказать, что я удивлен. Каков правильный способ объявления этого индекса?

Ответ 1

Вы можете объявить функцию, которая ложно помечена как "неизменяемая", и создать для нее индекс.

CREATE OR REPLACE FUNCTION len_immut(record)
 RETURNS int
 LANGUAGE plperl
 IMMUTABLE
AS $function$
  ## This function lies about its immutability.
  ## Use it with care.  It is useful for indexing
  ## entire table rows.
  return length(join ",", values %{$_[0]});
$function$

а затем

create index on profile (len_immut(profile));

SELECT * FROM profile ORDER BY len_immut(profile) DESC;

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

Ответ 2

Чтобы измерить размер строки в текстовом представлении, вы можете просто передать всю строку в текст, что намного быстрее, чем объединение отдельных столбцов:

SELECT length(profile::text) FROM profile;

Но есть 3 (или 4) вопроса с этим выражением в индексе:

  • Сокращение синтаксиса profile::text не принимается в CREATE INDEX, вам нужно добавить дополнительные скобки или по умолчанию стандартный синтаксис cast(profile AS text)

  • По-прежнему та же проблема, что @jjanes уже обсуждался: только функции IMMUTABLE допускаются в индексных выражениях и отличает тип строки до text. не пропустите это требование. Вы могли бы создать фальшивую <обложку IMMUTABLE, как показано на рисунке Джеффа.

  • Существует встроенная неоднозначность (применимая к ответу Джеффа!): если у вас есть имя столбца, такое же, как имя таблицы (это обычный случай), вы не может ссылаться на тип строки в CREATE INDEX, так как идентификатор всегда сначала разрешает имя столбца.

  • Незначительная разница с вашим оригиналом: добавляет разделители столбцов, декораторы строк и, возможно, escape-символы в представление text. Не имеет большого значения для вашего случая использования.

Однако, я бы предложил более радикальную альтернативу, как сырой индикатор для размера строки: pg_column_size(). Еще короче и быстрее и избегает проблем 1, 3 и 4:

SELECT pg_column_size(profile) FROM profile;

Проблема 2 остается, хотя: pg_column_size() также является только STABLE. Вы можете создать простую и дешевую функцию оболочки SQL:

CREATE OR REPLACE FUNCTION pg_column_size(profile)
  RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT pg_catalog.pg_column_size($1)';

а затем продолжите, как показано на рисунке @jjanes. Подробнее:

Обратите внимание, что я создал функцию с типом строки profile в качестве параметра. Postgres позволяет перегружать функции, поэтому мы можем использовать одно и то же имя функции. Теперь, когда мы подаем соответствующий тип строки на pg_column_size(), наша пользовательская функция более точно соответствует разрешению типа функции и выбирается вместо функции полиморфной системы. В качестве альтернативы, используйте отдельное имя и, возможно, сделайте также функцию полиморфной...

по теме: