Включение нескольких столбцов в один индекс в Postgres

У меня есть таблица пользователей с двумя столбцами: «электронная почта» и «новая_почта». Я нуждаюсь:

  • Ограничение уникальности без учета регистра, охватывающее оба столбца - то есть, если «[email protected]» появляется в столбце «email» одной строки, то вставка «[email protected]» в «new_email» другой строки (или даже той же строки) 'столбец должен выйти из строя.

  • Быстрый поиск без учета регистра для заданного адреса электронной почты в полях 'email' или 'new_email', т. Е. Найдите строку, в которой new_email ИЛИ адрес электронной почты является "[email protected]", без учета регистра.

Я знаю, что мне было бы проще сделать это, создав связанную таблицу «электронных писем», но я ожидаю, что буду искать пользователей в этой таблице (по первичному ключу) из нескольких приложений, и я бы хотел избежать дублирования соединения. логика в разных местах, чтобы также получать свои электронные письма. Поэтому я думаю, что какой-то индекс выражения был бы лучше, если это возможно.

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

Я использую Postgres 8.4. Спасибо!


person PreciousBodilyFluids    schedule 02.07.2011    source источник


Ответы (2)


Я думаю, вам придется использовать триггер, чтобы обеспечить соблюдение ограничения уникальности между столбцами. Если вы добавите уникальные индексы в каждый столбец, а затем активируете что-то вроде этого (непроверенный в верхней части моего головного кода):

CREATE FUNCTION no_dups_allowed() RETURNS trigger AS $$
DECLARE
    r ROW;
BEGIN
    SELECT 1 INTO r
    FROM users
    WHERE LOWER(email)     = LOWER(NEW.email_new)
       OR LOWER(email_new) = LOWER(NEW.email);
    IF FOUND THEN
        -- Found a duplicate so it is time for a hissy fit!
        RAISE 'Duplicate email address found' USING ERRCODE = 'unique_violation';
    END;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Вам нужно что-то подобное в качестве триггера BEFORE INSERT и BEFORE UPDATE. Этот триггер позаботится об обнаружении дубликатов между столбцами, а уникальные индексы позаботятся о дубликатах в столбцах.

Некоторые полезные ссылки:

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

Для запрашивающей половины вы можете создать представление, в котором используется UNION, чтобы объединить два столбца. Вы также можете создать функцию для объединения адресов электронной почты пользователя в один список. Трудно сказать, что было бы лучше, не зная более подробной информации об этих других запросах, но я подозреваю, что исправление всех других запросов, чтобы знать о email и email_new, было бы лучшим подходом; вам все равно придется обновить все остальные запросы, чтобы использовать представление или функцию, так зачем вообще создавать представление или функцию?

person mu is too short    schedule 02.07.2011
comment
Лучше всего использовать две строчные UNIQUE INDEX функции с проверкой таблицы. Для решения этой проблемы нет необходимости вызывать триггер. Используйте вывод из того, что @Scott предлагает ниже. - person Sean; 03.07.2011
comment
@Sean: Решение Скотта хорошее, но COALESCE оставляет дыру. - person mu is too short; 04.07.2011
comment
Согласен, я бы не стал использовать COALESCE или два столбца для хранения старых и текущих значений в одной таблице. - person Sean; 04.07.2011

Нет необходимости в триггерах. Попробуй это:

create  table et (email text, email2 text);
create unique index et_u on et (coalesce(lower(email),lower(email2)));
insert into et (email,email2) values ('[email protected]',NULL);
insert into et (email,email2) values ('[email protected]',NULL);
ERROR:  duplicate key value violates unique constraint "et_u"
insert into et (email,email2) values (NULL,'[email protected]');
ERROR:  duplicate key value violates unique constraint "et_u"
insert into et (email,email2) values (NULL,'[email protected]');
ERROR:  duplicate key value violates unique constraint "et_u"
person Scott Marlowe    schedule 03.07.2011
comment
Что ж, это не идеально, я могу ввести один и тот же адрес электронной почты в оба поля одновременно. Таким образом, вам также понадобится контрольное ограничение, которое отправлено по электронной почте ‹› email2 здесь. изменить таблицу и добавить ограничение et_nn check (email ‹› email2); - person Scott Marlowe; 03.07.2011
comment
Это интересно. Будет ли Postgres достаточно умен, чтобы использовать этот индекс при запросах адреса электронной почты? Нравится WHERE lower(email) = '[email protected]'? Или есть другой способ сформулировать запрос, чтобы он это сделал? - person PreciousBodilyFluids; 03.07.2011
comment
Это не сработает, если вы вставите (NULL,'[email protected]'), а затем ('[email protected]','[email protected]'), когда еще нет адреса [email protected], COALESCE эффективно скрывает email2 от проверок уникальности, когда email не равно NULL. - person mu is too short; 04.07.2011