在这种情况下我该如何处理唯一性?
How can I handle uniqueness in this situation?
我有一个 table 这样的:
create table my_table
(
type1 varchar2(10 char),
type2 varchar2(10 char)
);
我想要这样的独特性;
- 如果 type1 列具有 'GENERIC' 值,则 type2 列对于 table 必须是唯一的。例如;
- type1 列有 'GENERIC' 值,type2 列有 'value_x' 那么不能有任何 type2 列值等于 'value_x'.
- 但其他唯一性正在寻找这两列。我的意思是它应该在 type1 和 type2 列中是唯一的。(当然第一条规则是不变的)
我尝试用触发器实现;
CREATE OR REPLACE trigger my_trigger
BEFORE INSERT OR UPDATE
ON my_table
FOR EACH ROW
DECLARE
lvn_count NUMBER :=0;
lvn_count2 NUMBER :=0;
errormessage clob;
MUST_ACCUR_ONE EXCEPTION;
-- PRAGMA AUTONOMOUS_TRANSACTION; --without this it gives mutating error but I cant use this because it will conflict on simultaneous connections
BEGIN
IF :NEW.type1 = 'GENERIC' THEN
SELECT count(1) INTO lvn_count FROM my_table
WHERE type2= :NEW.type2;
ELSE
SELECT count(1) INTO lvn_count2 FROM my_table
WHERE type1= :NEW.type1 and type2= :NEW.type2;
END IF;
IF (lvn_count >= 1 or lvn_count2 >= 1) THEN
RAISE MUST_ACCUR_ONE;
END IF;
END;
但是它在没有 pragma 的情况下给出了 mutating 错误。由于同时连接发生冲突,我不想使用它。 (错误是因为我在触发器上使用了相同的 table)
我试着用唯一索引来做,但我做不到。
CREATE UNIQUE INDEX my_table_unique_ix
ON my_table (case when type1= 'GENERIC' then 'some_logic_here' else type1 end, type2); -- I know it does not make sense but maybe there is something different that I can use in here.
示例;
**Example 1**
insert into my_table (type1,type2) values ('a','b'); -- its ok no problem
insert into my_table (type1,type2) values ('a','c'); -- its ok no problem
insert into my_table (type1,type2) values ('c','b'); -- its ok no problem
insert into my_table (type1,type2) values ('GENERIC','b'); -- it should be error because b is exist before (i look just second column because first column value is 'GENERIC')
EXAMPLE 2:
insert into my_table (type1,type2) values ('GENERIC','b'); -- its ok no problem
insert into my_table (type1,type2) values ('a','c'); -- its ok no problem
insert into my_table (type1,type2) values ('d','c'); -- its ok no problem
insert into my_table (type1,type2) values ('d','b'); -- it should be error because second column can not be same as the second column value that first column value is 'GENERIC'
试试这样:
CREATE TABLE my_table (
type1 VARCHAR2(10 CHAR),
type2 VARCHAR2(10 CHAR),
type1_unique VARCHAR2(10 CHAR) GENERATED ALWAYS AS ( NULLIF(type1, 'GENERIC') ) VIRTUAL
);
ALTER TABLE MY_TABLE ADD (CONSTRAINT my_table_unique_ix UNIQUE (type1_unique, type2) USING INDEX)
或者像这样的索引也应该有效:
CREATE UNIQUE INDEX my_table_unique_ix ON MY_TABLE (NULLIF(type1, 'GENERIC'), type2);
或者按照你的风格去做(你只错过了END
):
CREATE UNIQUE INDEX my_table_unique_ix ON my_table (case when type1= 'GENERIC' then null else type1 end, type2);
您尝试做的事情在 Oracle 中并不是很简单。一种可能的(虽然有些麻烦)方法是结合使用
- 带有
refresh (on commit)
的附加物化视图
- 用于计算每组不同值数量的窗口函数
- 计算每组 GENERIC 行数的窗口函数
- 一个检查约束,以确保我们要么只有一个 DISTINCT 值,要么在同一组中没有 GENERIC
这应该有效:
create materialized view mv_my_table
refresh on commit
as
select
type1,
type2,
count(distinct type1) over (partition by type2) as distinct_type1_cnt,
count(case when type1 = 'GENERIC' then 1 else null end)
over (partition by type2) as generic_cnt
from my_table;
alter table mv_my_table add constraint chk_type1
CHECK (distinct_Type1_cnt = 1 or generic_cnt = 0);
现在,INSERT 复制不会立即失败,但随后的 COMMIT 会失败,因为它会触发物化视图刷新,这会导致检查约束触发。
缺点
- 重复的 INSERT 不会立即失败(使调试更加痛苦)
- 根据 table 的大小,MView 刷新可能会显着降低 COMMIT 速度
链接
有关此方法的更详细讨论,请参阅AskTom on cross-row constraints
除非我遗漏了一些明显的东西,否则@Frank Schmitt 的答案中的逻辑也可以使用语句级触发器来实现。它实施起来简单得多,并且没有 Frank 提到的缺点。
create or replace TRIGGER my_table_t
AFTER INSERT OR UPDATE OR DELETE
ON my_table
DECLARE
l_dummy NUMBER;
MUST_ACCUR_ONE EXCEPTION;
BEGIN
WITH constraint_violated AS
(
select
type1,
type2,
count(distinct type1) over (partition by type2) as distinct_type1_cnt,
count(case when type1 = 'GENERIC' then 1 else null end)
over (partition by type2) as generic_cnt
from my_table
)
SELECT 1 INTO l_dummy
FROM constraint_violated
WHERE NOT (distinct_type1_cnt = 1 or generic_cnt = 0) FETCH FIRST 1 ROWS ONLY;
RAISE MUST_ACCUR_ONE;
EXCEPTION WHEN NO_DATA_FOUND THEN
NULL;
END;
/
我有一个 table 这样的:
create table my_table
(
type1 varchar2(10 char),
type2 varchar2(10 char)
);
我想要这样的独特性;
- 如果 type1 列具有 'GENERIC' 值,则 type2 列对于 table 必须是唯一的。例如;
- type1 列有 'GENERIC' 值,type2 列有 'value_x' 那么不能有任何 type2 列值等于 'value_x'.
- 但其他唯一性正在寻找这两列。我的意思是它应该在 type1 和 type2 列中是唯一的。(当然第一条规则是不变的)
我尝试用触发器实现;
CREATE OR REPLACE trigger my_trigger
BEFORE INSERT OR UPDATE
ON my_table
FOR EACH ROW
DECLARE
lvn_count NUMBER :=0;
lvn_count2 NUMBER :=0;
errormessage clob;
MUST_ACCUR_ONE EXCEPTION;
-- PRAGMA AUTONOMOUS_TRANSACTION; --without this it gives mutating error but I cant use this because it will conflict on simultaneous connections
BEGIN
IF :NEW.type1 = 'GENERIC' THEN
SELECT count(1) INTO lvn_count FROM my_table
WHERE type2= :NEW.type2;
ELSE
SELECT count(1) INTO lvn_count2 FROM my_table
WHERE type1= :NEW.type1 and type2= :NEW.type2;
END IF;
IF (lvn_count >= 1 or lvn_count2 >= 1) THEN
RAISE MUST_ACCUR_ONE;
END IF;
END;
但是它在没有 pragma 的情况下给出了 mutating 错误。由于同时连接发生冲突,我不想使用它。 (错误是因为我在触发器上使用了相同的 table)
我试着用唯一索引来做,但我做不到。
CREATE UNIQUE INDEX my_table_unique_ix
ON my_table (case when type1= 'GENERIC' then 'some_logic_here' else type1 end, type2); -- I know it does not make sense but maybe there is something different that I can use in here.
示例;
**Example 1**
insert into my_table (type1,type2) values ('a','b'); -- its ok no problem
insert into my_table (type1,type2) values ('a','c'); -- its ok no problem
insert into my_table (type1,type2) values ('c','b'); -- its ok no problem
insert into my_table (type1,type2) values ('GENERIC','b'); -- it should be error because b is exist before (i look just second column because first column value is 'GENERIC')
EXAMPLE 2:
insert into my_table (type1,type2) values ('GENERIC','b'); -- its ok no problem
insert into my_table (type1,type2) values ('a','c'); -- its ok no problem
insert into my_table (type1,type2) values ('d','c'); -- its ok no problem
insert into my_table (type1,type2) values ('d','b'); -- it should be error because second column can not be same as the second column value that first column value is 'GENERIC'
试试这样:
CREATE TABLE my_table (
type1 VARCHAR2(10 CHAR),
type2 VARCHAR2(10 CHAR),
type1_unique VARCHAR2(10 CHAR) GENERATED ALWAYS AS ( NULLIF(type1, 'GENERIC') ) VIRTUAL
);
ALTER TABLE MY_TABLE ADD (CONSTRAINT my_table_unique_ix UNIQUE (type1_unique, type2) USING INDEX)
或者像这样的索引也应该有效:
CREATE UNIQUE INDEX my_table_unique_ix ON MY_TABLE (NULLIF(type1, 'GENERIC'), type2);
或者按照你的风格去做(你只错过了END
):
CREATE UNIQUE INDEX my_table_unique_ix ON my_table (case when type1= 'GENERIC' then null else type1 end, type2);
您尝试做的事情在 Oracle 中并不是很简单。一种可能的(虽然有些麻烦)方法是结合使用
- 带有
refresh (on commit)
的附加物化视图
- 用于计算每组不同值数量的窗口函数
- 计算每组 GENERIC 行数的窗口函数
- 一个检查约束,以确保我们要么只有一个 DISTINCT 值,要么在同一组中没有 GENERIC
这应该有效:
create materialized view mv_my_table
refresh on commit
as
select
type1,
type2,
count(distinct type1) over (partition by type2) as distinct_type1_cnt,
count(case when type1 = 'GENERIC' then 1 else null end)
over (partition by type2) as generic_cnt
from my_table;
alter table mv_my_table add constraint chk_type1
CHECK (distinct_Type1_cnt = 1 or generic_cnt = 0);
现在,INSERT 复制不会立即失败,但随后的 COMMIT 会失败,因为它会触发物化视图刷新,这会导致检查约束触发。
缺点
- 重复的 INSERT 不会立即失败(使调试更加痛苦)
- 根据 table 的大小,MView 刷新可能会显着降低 COMMIT 速度
链接
有关此方法的更详细讨论,请参阅AskTom on cross-row constraints
除非我遗漏了一些明显的东西,否则@Frank Schmitt 的答案中的逻辑也可以使用语句级触发器来实现。它实施起来简单得多,并且没有 Frank 提到的缺点。
create or replace TRIGGER my_table_t
AFTER INSERT OR UPDATE OR DELETE
ON my_table
DECLARE
l_dummy NUMBER;
MUST_ACCUR_ONE EXCEPTION;
BEGIN
WITH constraint_violated AS
(
select
type1,
type2,
count(distinct type1) over (partition by type2) as distinct_type1_cnt,
count(case when type1 = 'GENERIC' then 1 else null end)
over (partition by type2) as generic_cnt
from my_table
)
SELECT 1 INTO l_dummy
FROM constraint_violated
WHERE NOT (distinct_type1_cnt = 1 or generic_cnt = 0) FETCH FIRST 1 ROWS ONLY;
RAISE MUST_ACCUR_ONE;
EXCEPTION WHEN NO_DATA_FOUND THEN
NULL;
END;
/