Как создать уникальный индекс с условным и подзапросом в PostgreSQL?
Я использую PGSQL и пытаюсь добавить индекс ниже.
CREATE UNIQUE INDEX fk_client ON user_client (fk_client) WHERE fk_client NOT IN(SELECT fk_client FROM legal_entity);
Но... Это невозможно, поскольку при создании индекса разрешено выполнение подзапроса.
Я получаю следующую ошибку:
ERROR: cannot use subquery in index predicate
Есть ли способ решить эту проблему?
Выше модель представляет ситуацию дела.
- Клиент может быть либо обычным человеком, либо компанией
- Если обычный человек, у нее не будет FK в таблице "legal_entity".
- Если обычный человек, она должна только одну запись в таблице "user_client".
С индексом нет, но есть ли способ решить эту проблему?...
Скрипт генерации таблиц:
-- user is a special word, then renamed to users
CREATE TABLE users (
id_user INT,
name VARCHAR(50) NOT NULL,
CONSTRAINT user_pkey PRIMARY KEY (id_user)
);
CREATE TABLE client (
id_client INT,
CONSTRAINT client_pkey PRIMARY KEY (id_client)
);
CREATE TABLE legal_entity (
fk_client INT,
federal_id VARCHAR(14) NOT NULL,
CONSTRAINT legal_entity_pkey PRIMARY KEY (fk_client),
CONSTRAINT legal_entity_fkey FOREIGN KEY (fk_client) REFERENCES client (id_client)
);
CREATE TABLE user_client (
fk_client INT,
fk_user INT,
CONSTRAINT user_client_pkey PRIMARY KEY (fk_client, fk_user),
CONSTRAINT user_client_fkey_1 FOREIGN KEY (fk_client) REFERENCES client (id_client),
CONSTRAINT user_client_fkey_2 FOREIGN KEY (fk_user) REFERENCES users (id_user)
);
3 ответа
Недостатком использования правил является то, что правила просто переписывают запрос после его анализа, поэтому, если данные добавляются через триггер, он не будет запущен. Безопаснее добавить ограничение CHECK, которое вызывает функцию с вашей логикой. Если я правильно следую вашей логике, это должно быть что-то вроде:
CREATE OR REPLACE FUNCTION check_user_client(fkc int)
RETURNS boolean AS
$$
DECLARE
i int;
BEGIN
SELECT count(*) INTO i FROM legal_entity WHERE fk_client = fkc;
IF (i > 0) THEN
RETURN true;
END IF;
SELECT count(*) INTO i FROM user_client WHERE fk_client = fkc;
IF (i = 0) THEN
RETURN true;
END IF;
RETURN false;
END
$$ LANGUAGE plpgsql;
ALTER TABLE user_client ADD CONSTRAINT unique_user CHECK (check_user_client(fk_client));
Я решил свою проблему, добавив таблицу правил:
CREATE OR REPLACE RULE rule_test AS ON INSERT
TO user_client WHERE (
(SELECT fk_client FROM legal_entity WHERE fk_client = new.fk_client) IS NULL) AND (
(SELECT fk_client FROM user_client WHERE fk_client = new.fk_client) IS NOT NULL)
DO INSTEAD NOTHING;
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE foo
( id INTEGER NOT NULL PRIMARY KEY
);
CREATE TABLE tbl_relation
( id INTEGER NOT NULL PRIMARY KEY
, foo_id INTEGER REFERENCES foo(id)
, fk_1 INTEGER
);
CREATE UNIQUE INDEX fk_1 ON tbl_relation (fk_1)
WHERE foo_id IS NULL;
ОБНОВЛЕНИЕ: это новая ситуация после модификации модели данных. Дело в том, что legal_entity и клиент, похоже, совместно используют ключевой домен (что не считается правильным). Предоставление legal_entity своего собственного ключевого домена кажется более подходящим.
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
-- user is a special word, then renamed to users
CREATE TABLE users
( id INTEGER PRIMARY KEY
-- name is a special word
, zname VARCHAR(50) NOT NULL
);
CREATE TABLE client
(id INTEGER PRIMARY KEY
);
CREATE TABLE legal_entity
( id INTEGER PRIMARY KEY
, client_id INTEGER REFERENCES client(id)
, federal_id VARCHAR(14) NOT NULL
);
CREATE TABLE user_client
( client_id INTEGER REFERENCES client (id)
, user_id INTEGER REFERENCES users (id)
, legal_id INTEGER REFERENCES legal_entity(id)
, CONSTRAINT user_client_pkey PRIMARY KEY (client_id, user_id)
);
CREATE INDEX tres_stupide ON user_client (client_id) WHERE legal_id IS NULL;