Tenho a seguinte tabela:
CREATE TABLE users (
first_name VARCHAR(256) DEFAULT NULL NULL,
last_name VARCHAR(256) DEFAULT NULL NULL,
full_name VARCHAR(1024) GENERATED ALWAYS AS
(CASE
WHEN first_name IS NULL AND
last_name IS NULL THEN NULL
ELSE
(TRIM(COALESCE(first_name, '') || ' ' || COALESCE(last_name, ''))) END) STORED;
);
E tenho a seguinte função para "normalizar":
CREATE OR REPLACE FUNCTION NORMALIZE_FUNC(IN source VARCHAR)
RETURNS VARCHAR
LANGUAGE PLPGSQL
AS
$$
BEGIN
source := (SELECT COALESCE(source, ''));
source := (SELECT REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
LOWER(source),
'ş', 's'),
'ç', 'c'),
'ı', 'i'),
'ü', 'u'),
'ö', 'o'),
'ğ', 'g'))::VARCHAR;
RETURN source;
END
$$
IMMUTABLE;
e o seguinte índice e consulta, soundex()
de onde é a fuzzystrmatch
extensão:
CREATE INDEX users_full_name_soundex_idx ON users (SOUNDEX(NORMALIZE_FUNC(full_name))) WHERE full_name IS NOT NULL;
SELECT *
FROM users
WHERE SOUNDEX(NORMALIZE_FUNC(full_name)) = SOUNDEX(NORMALIZE_FUNC('name surname'))
e eu obtenho uma varredura sequencial na tabela users
.
Quando tento sem o definido pelo usuário NORMALIZE_FUNC
:
CREATE INDEX users_full_name_soundex_idx ON users (SOUNDEX(full_name)) WHERE full_name IS NOT NULL;
SELECT *
FROM users
WHERE SOUNDEX(full_name) = SOUNDEX('name surname')
Vejo que o users_full_name_soundex_idx
índice é usado e a consulta é rápida.
O que estou perdendo?
O problema é a
WHERE
cláusula na definição do índice, que você não definiunormalize_func()
comRETURNS NULL ON NULL INPUT
.Normalmente, um índice parcial (um índice com uma
WHERE
condição) só pode ser usado se aWHERE
condição do índice também for usada na consulta. Então por que o índice é usado se você não usanormalize_func()
na definição do índice e na consulta? A resposta é que o otimizador do PostgreSQL é inteligente o suficiente para fazer a seguinte dedução:soundex('name surname')
, que é avaliado'N526'
durante o planejamento da consulta, não é NULLsoundex()
é uma função que foi definida comoRETURNS NULL ON NULL INPUT
(que é o mesmo queSTRICT
), então para todas as linhas ondefull_name
é NULL,soundex(full_name)
também será NULL, e a comparação não retornaráTRUE
full_name
é NULL não atenderão à condição, apenas as linhas ondefull_name
não é NULL podem ter sucesso, e podemos usar o índice parcial sem perder nenhuma linhaComo você esqueceu de definir
normalize_func()
comoRETURNS NULL ON NULL INPUT
, essa dedução não pode ser feita quando essa função está envolvida.Você tem várias opções:
defina a função como
RETURNS NULL ON NULL INPUT
:acabar com a
WHERE
condição na definição do índiceadicionar
AND full_name IS NOT NULL
à consultaA primeira solução é a mais inteligente e também pode trazer benefícios adicionais de desempenho, já que o PostgreSQL pode pular a execução do corpo da função para argumentos NULL.