使用 PLSQL 创建过程
Posted
技术标签:
【中文标题】使用 PLSQL 创建过程【英文标题】:Creating a procedure with PLSQL 【发布时间】:2012-11-27 21:14:00 【问题描述】:我正在尝试编写一个 PLSQL 函数来实现一个约束,即员工不能同时是司机和机械师,换句话说,来自 TRKEMPLOYEE 的相同 E# 不能同时在 TRKDRIVER 和 TRKMECHANIC 中.如果数据库的内容违反了该约束,请列出所有既是机械师又是司机的员工的 E# 和 NAME。上述表格如下:
TRKEMPLOYEE(E# NUMBER(12) NOT NULL
NAME VARCHAR(50) NOT NULL,
DOB DATE ,
ADDRESS VARCHAR(300) NOT NULL,
HIREDATE DATE NOT NULL,
CONSTRAINT TRKEMPLOYEE_PKEY PRIMARY KEY(E#))
TRKDRIVER(E# NUMBER(12) NOT NULL
L# NUMBER(8) NOT NULL,
STATUS VARCHAR(10) NOT NULL,
CONSTRAINT TRKDRIVER_PKEY PRIMARY KEY(E#),
CONSTRAINT TRKDRIVER_FKEY FOREIGN KEY(E#) REFERENCES TRKEMPLOYEE(E#))
TRKMECHANIC(E# NUMBER(12) NOT NULL
L# NUMBER(8) NOT NULL,
STATUS VARCHAR(10) NOT NULL,
EXPERIENCE VARCHAR(10) NOT NULL,
CONSTRAINT TRKMECHANIC_PKEY PRIMARY KEY(E#),
CONSTRAINT TRKMECHANIC_FKEY FOREIGN KEY(E#) REFERENCES TRKEMPLOYEE(E#))
我试图编写一个函数,但在第 1 行第 7 列中不断出现编译错误。谁能告诉我为什么我的代码不起作用?我的代码如下
CREATE OR REPLACE FUNCTION Verify()
IS DECLARE
E# TRKEMPLOYEE.E#%TYPE;
CURSOR C1 IS SELECT E# FROM TRKEMPLOYEE;
BEGIN
OPEN C1;
LOOP
FETCH C1 INTO EMPNUM;
IF(EMPNUM IN(SELECT E# FROM TRKMECHANIC )AND EMPNUM IN(SELECT E# FROM TRKDRIVER))
SELECT E#, NAME FROM TRKEMPLOYEE WHERE E#=EMPNUM;
ELSE
dbms_output.put_line(“OK”);
ENDIF
EXIT WHEN C1%NOTFOUND;
END LOOP;
CLOSE C1;
END;
/
任何帮助将不胜感激。谢谢。
【问题讨论】:
我不确定我是否了解您的数据模型。 TRKDRIVER 和 TRKMECHANIC 表存储什么?他们是否具有 TRKEMPLOYEE 所没有的其他属性?为什么不在 TRKEMPLOYEE 中设置一个标志来标记员工是司机还是机械师? 我省略了我的表格的一些细节,因为我认为它们与当时的问题无关。我已将这些字段连同我的问题的全部详细信息一起添加回 顺便说一句:您可以通过使用物化视图将约束破解到此模型中。该解决方案是否可行取决于这些表的更新频率以及它们的大小。 【参考方案1】:您正在创建一个函数,但缺少 RETURN
声明。如果您不想返回结果,请使用create or replace procedure
。
另外,如果你没有任何参数,去掉括号()
,DECLARE
关键字也不正确。
您检查表格的方式并不是很有效。您可以通过单个查询获得所有违反业务限制的员工的数量:
select emp.e#,
emp.name,
count(drv.e#) + count(mec.e#) as cnt
from trkemployee emp
left join trkdriver drv on drv.e# = emp.e#
left join trkmechanic mec on mec.e# = emp.e#
group by emp.e#, emp.name
having count(drv.e#) + count(mec.e#) > 1;
此外,EXIT WHEN C1%NOTFOUND;
应该在FETCH
语句之后。否则,即使游标没有获取任何内容,您也会停留在循环中。
如果您采用我的简化语句,您可以遍历所有员工并打印 OK,也不是 Not OK,具体取决于计数:
CREATE OR REPLACE procedure Verify
IS
empnum number(12);
cnt integer;
empname varchar(50);
CURSOR C1 IS
select emp.e#,
emp.name,
count(drv.e#) + count(mec.e#) as cnt
from trkemployee emp
left join trkdriver drv on drv.e# = emp.e#
left join trkmechanic mec on mec.e# = emp.e#
group by emp.e#, emp.name;
BEGIN
OPEN C1;
LOOP
FETCH C1 INTO empnum, empname, cnt;
EXIT WHEN C1%NOTFOUND;
if cnt > 1 then
dbms_output.put_line(to_char(empnum)||' NOK');
else
dbms_output.put_line(to_char(empnum)||' OK');
end if;
END LOOP;
CLOSE C1;
END;
/
不用存储过程解决问题
我正在考虑一种方法,如何仅通过数据库中的约束来强制执行此业务规则:
create table trkemployee
(
E# NUMBER(12) NOT NULL,
NAME VARCHAR(50) NOT NULL,
DOB DATE ,
ADDRESS VARCHAR(300) NOT NULL,
HIREDATE DATE NOT NULL,
emp_type char(1) NOT NULL,
constraint trkemployee_pkey primary key(e#, emp_type),
constraint chk_emp_type check (emp_type in ('d','m'))
);
create table TRKDRIVER
(
e# number(12) not null,
emp_type char(1) default 'd' not null,
l# number(8) not null,
status varchar(10) not null,
constraint trkdriver_pkey primary key(e#),
constraint trkdriver_fkey foreign key(e#,emp_type) references trkemployee(e#, emp_type),
constraint check_drv_type check (emp_type = 'd')
);
create table trkmechanic
(
e# number(12) not null,
emp_type char(1) default 'm' not null,
l# number(8) not null,
status varchar(10) not null,
experience varchar(10) not null,
constraint trkmechanic_pkey primary key(e#),
constraint trkmechanic_fkey foreign key(e#,emp_type) references trkemployee(e#, emp_type),
constraint check_mec_type check (emp_type = 'm')
);
这个想法是在其外键中包含员工类型,并确保从属表不能使用错误的类型。插入新的 trkemployee 时,必须指定类型。明细表上的检查约束将拒绝任何类型错误的行。
要将员工从一种类型“移动”到另一种类型(如果可能的话),必须首先删除旧的明细行。只有这样员工的类型才能更改为其他类型。最后可以插入新的细节。
【讨论】:
谢谢,但有一个我没有提到的伪约束:它必须在 PLSQL 中完成,而不是简单地修改表的架构,因为这是我的任务的一部分学校工作。这个问题要求我使用 PLSQL。您进行预编辑的代码非常接近我想要的(除了我仍然得到的错误,尽管基本上是复制粘贴),但是我不想打印“NOK”,而是想打印有问题的 E# 和名称。这行得通吗? : 如果 cnt > 1 然后 SELECT E# FROM TRKEMPLOYEE WHERE E# IN(SELECT E# FROM TRKDRIVER) @user1857460 如果要打印名称和e#
,只需将它们添加到基本光标即可。无需运行新的 select 语句。这是一个可编译的示例:sqlfiddle.com/#!4/39db9
我试过这个例子。这条 SQL 语句有问题: select emp.e#,emp.name,count(drv.e#) + count(mec.e#) as cnt from trkemployee emp left join trkdriver drv on drv.e# = emp.e# left join trkmechanic mec on mec.e# = emp.e# group by emp.e#;它说“不是按表达式分组”。我自己也无法完全理解那句话有什么问题。
@user1857460:抱歉,emp.name
列在 group by
子句中丢失。【参考方案2】:
好吧,这个函数有很多问题。
正如 a_horse_with_no_name 所指出的,如果您没有参数,则删除 ()
并且根据定义,函数需要 RETURN 语句。
然而,这还不是全部:
-
您不能在 IF 语句中使用 SELECT。
您必须将结果放入变量中或将其作为
一个光标。
函数或过程中不需要 DECLARE 块。
您的 IF 语句需要 THEN...但是,我认为您不需要这个,因为您只是在 ELSE 中做某事。
您正在将游标的结果提取到一个不存在的变量中。
我会冒险猜测您想要一个程序,因为我看不出您可以返回什么。我还猜想,如果您不想对第一个 IF 做任何事情,那么您可以将其他选择放入光标中。
最后,我假设您将要检查的员工的e#
传递给它。
由于e#
上的所有 3 个表都是唯一的,您可以使用 LEFT OUTER JOINS 将所有 SQL 放在一个游标中
create or replace function verify ( Pemployee trkemployee.e#%type
) return varchar2 is
l_mechanic trkemployee.e#%type;
l_driver trkemployee.e#%type;
begin
-- Doing things in SQL is more efficient.
select m.e#, d.e#
into l_mechanic, l_driver
from trkemployee e
left outer join trkmechanic m
on e.e# = m.e#
left outer join trkdriver d
on e.e# = d.e#
;
if l_driver is not null and l_mechanic is not null then
return 'BAD';
else
return 'OK';
end if;
end;
/
【讨论】:
【参考方案3】:混合a_horse_with_no_name
和Ben
给出的答案,然后“简化”一点给出-
CREATE OR REPLACE FUNCTION Verify
return varchar2 IS
BEGIN
FOR c_rec in (select emp.e#,
count(drv.e#) + count(mec.e#) as cnt
from trkemployee emp
left join trkdriver drv on drv.e# = emp.e#
left join trkmechanic mec on mec.e# = emp.e#
group by emp.e#)
LOOP
if c_rec.cnt > 1 then
return ('BAD '||c_rec.e#);
else
return ('OK '||c_rec.e#);
end if;
END LOOP;
EXCEPTION
WHEN ....THEN
--as the wise man once said "Do some meaningful exception handling here"...
END;
/
DECLARE
、OPEN
、FETCH
和 CLOSE
光标看起来有点过头了。
或PROCEDURE
喜欢
CREATE OR REPLACE PROCEDURE Verify
IS
BEGIN
FOR c_rec in (select emp.e#,
count(drv.e#) + count(mec.e#) as cnt
from trkemployee emp
left join trkdriver drv on drv.e# = emp.e#
left join trkmechanic mec on mec.e# = emp.e#
group by emp.e#)
LOOP
if c_rec.cnt > 1 then
dbms_output.put_line('BAD '||c_rec.e#);
else
dbms_output.put_line('OK '||c_rec.e#);
end if;
END LOOP;
EXCEPTION
WHEN ....THEN
--as the wise man once said "Do some meaningful exception handling here"...
END;
/
【讨论】:
以上是关于使用 PLSQL 创建过程的主要内容,如果未能解决你的问题,请参考以下文章
Plsql developer 工具创建存储过程,必须要在package 文件下创建存储声明 pa