PL/SQL 结果与直接查询不同
PL/SQL Result different than direct query
我创建了 PL/SQL 程序来更新学校注册排名。
看起来像这样:
DECLARE
studentsNumber NUMBER :=0;
vSREDNIA RANK.SREDNIA%TYPE;
vFIZYKA RANK.SREDNIA_FIZ%TYPE;
vMATEMATYKA RANK.SREDNIA_MAT%TYPE;
vPO RANK.SREDNIA_PO%TYPE;
vWF RANK.SREDNIA_WF%TYPE;
vREL RANK.SREDNIA_REL%TYPE;
vMUZYKA RANK.SREDNIA_MUZ%TYPE;
BEGIN
SELECT COUNT(*) INTO studentsNumber FROM uczniowie;
FOR id IN 1..studentsNumber LOOP
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vSREDNIA FROM OCENY
WHERE WLASCICIEL = id;
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vFIZYKA FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Fizyka';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vMATEMATYKA FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Matematyka';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vPO FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Przysposobienie Obronne';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vWF FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'WF';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vREL FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Religioznastwo';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vMUZYKA FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Muzyka';
DBMS_OUTPUT.PUT_LINE('ID:' || id || ' Srednia: '|| vSREDNIA);
UPDATE RANK
SET SREDNIA = vSREDNIA, SREDNIA_MAT = vMATEMATYKA, SREDNIA_FIZ = vFIZYKA,
SREDNIA_REL = vREL, SREDNIA_MUZ = vMUZYKA, SREDNIA_PO = vPO, SREDNIA_WF = vWF
WHERE ID_UCZNIA = id;
END LOOP;
COMMIT;
END;
但是 SREDNIA 和 SREDNIA_WF 列始终为 4,而其他列为空。例如,当我在控制台中输入时:
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) FROM OCENY
WHERE WLASCICIEL = 3;
我得到了正确的结果:3,73913043。
这种差异从何而来?如果这有意义的话,那就是 Oracle 数据库。 DBMS_OUTPUT 生成 1,2,3... ID,所以没问题。
见,例如
SQL> create table tmp$i (i number(38, 0), j number(38, 2), k number(38, 4));
Table created
SQL> insert into tmp$i values (2.55, 2.55, 2.55);
1 row inserted
SQL> select * from tmp$i;
I J K
---------- ---------- ----------
3 2,55 2,5500
检查您的 table 字段是否以适当的比例描述。
除了检查排名 table 列的数字精度是否正确之外,您的代码也不可靠。
您不仅要进行逐行(也就是慢慢地)处理,而且您可能无法获得所有正确的学生 ID。您执行循环的 "count the number of students and then assume all the ids start from 1 and are consecutive" 步骤实际上可能不正确。如果最低的学生ID是2呢?如果学生编号有差距怎么办?你最终会为一些 id 做不必要的工作而完全错过其他的。至少,您应该将其转换为循环游标,以确保您 select 来自 uczniowie table.
的所有 ID
此外,所有这些单独的 select 都可以在一个 select 中完成。当您使用它时,您可以将它合并到一个 update/merge 语句中。因此首先否定了所有循环的需要,比如(未经测试,因为你没有提供任何样本数据):
merge into rank tgt
using (select wlasciciel student_id,
sum(waga * wynik)/sum(waga) vsrednia,
sum(case when przedmiot = 'Fizyka' then waga * wynik end)/sum(case when przedmiot = 'Fizyka' then waga end) vfizyka,
sum(case when przedmiot = 'Matematyka' then waga * wynik end)/sum(case when przedmiot = 'Matematyka' then waga end) vmatematyka,
sum(case when przedmiot = 'Przysposobienie Obronne' then waga * wynik end)/sum(case when przedmiot = 'Przysposobienie Obronne' then waga end) vpo,
sum(case when przedmiot = 'WF' then waga * wynik end)/sum(case when przedmiot = 'WF' then waga end) vwf,
sum(case when przedmiot = 'Religioznastwo' then waga * wynik end)/sum(case when przedmiot = 'Religioznastwo' then waga end) vrel,
sum(case when przedmiot = 'Muzyka' then waga * wynik end)/sum(case when przedmiot = 'Muzyka' then waga end) vmuzyka
from oceny
group by wlasciciel) src
on (tgt.id_ucznia = src.student_id)
when matched then
update set tgt.srednia = src.vsrednia,
tgt.srednia_mat = src.vmatematyka,
tgt.srednia_fiz = src.vfizyka,
tgt.srednia_rel = src.vrel,
tgt.srednia_muz = src.vmuzyka,
tgt.srednia_po = src.vpo,
tgt.srednia_wf = src.vwf
when not matched then -- optional insert clause, just in case the id isn't already present in the rank table
insert (tgt.id_ucznia,
tgt.srednia,
tgt.srednia_mat,
tgt.srednia_fiz,
tgt.srednia_rel,
tgt.srednia_muz,
tgt.srednia_po,
tgt.srednia_wf)
values (src.student_id,
src.vsrednia,
src.vmatematyka,
src.vfizyka,
src.vrel,
src.vmuzyka,
src.vpo,
src.vwf);
您会发现单个语句的性能优于您的过程(1. 您不必为每个单个 ID 查询相同的 table 7 次,并且 2. 您不必切换上下文在 pl/sql 和 sql 之间每次移动到另一个 id 时)。它还应该更容易调试,因为现在您可以 运行 单独的 select 语句来查看输出并检查它是否与您期望的相匹配。
我创建了 PL/SQL 程序来更新学校注册排名。 看起来像这样:
DECLARE
studentsNumber NUMBER :=0;
vSREDNIA RANK.SREDNIA%TYPE;
vFIZYKA RANK.SREDNIA_FIZ%TYPE;
vMATEMATYKA RANK.SREDNIA_MAT%TYPE;
vPO RANK.SREDNIA_PO%TYPE;
vWF RANK.SREDNIA_WF%TYPE;
vREL RANK.SREDNIA_REL%TYPE;
vMUZYKA RANK.SREDNIA_MUZ%TYPE;
BEGIN
SELECT COUNT(*) INTO studentsNumber FROM uczniowie;
FOR id IN 1..studentsNumber LOOP
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vSREDNIA FROM OCENY
WHERE WLASCICIEL = id;
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vFIZYKA FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Fizyka';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vMATEMATYKA FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Matematyka';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vPO FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Przysposobienie Obronne';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vWF FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'WF';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vREL FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Religioznastwo';
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) INTO vMUZYKA FROM OCENY
WHERE WLASCICIEL = id AND PRZEDMIOT = 'Muzyka';
DBMS_OUTPUT.PUT_LINE('ID:' || id || ' Srednia: '|| vSREDNIA);
UPDATE RANK
SET SREDNIA = vSREDNIA, SREDNIA_MAT = vMATEMATYKA, SREDNIA_FIZ = vFIZYKA,
SREDNIA_REL = vREL, SREDNIA_MUZ = vMUZYKA, SREDNIA_PO = vPO, SREDNIA_WF = vWF
WHERE ID_UCZNIA = id;
END LOOP;
COMMIT;
END;
但是 SREDNIA 和 SREDNIA_WF 列始终为 4,而其他列为空。例如,当我在控制台中输入时:
SELECT SUM(WAGA * WYNIK)/SUM(WAGA) FROM OCENY
WHERE WLASCICIEL = 3;
我得到了正确的结果:3,73913043。 这种差异从何而来?如果这有意义的话,那就是 Oracle 数据库。 DBMS_OUTPUT 生成 1,2,3... ID,所以没问题。
见,例如
SQL> create table tmp$i (i number(38, 0), j number(38, 2), k number(38, 4));
Table created
SQL> insert into tmp$i values (2.55, 2.55, 2.55);
1 row inserted
SQL> select * from tmp$i;
I J K
---------- ---------- ----------
3 2,55 2,5500
检查您的 table 字段是否以适当的比例描述。
除了检查排名 table 列的数字精度是否正确之外,您的代码也不可靠。
您不仅要进行逐行(也就是慢慢地)处理,而且您可能无法获得所有正确的学生 ID。您执行循环的 "count the number of students and then assume all the ids start from 1 and are consecutive" 步骤实际上可能不正确。如果最低的学生ID是2呢?如果学生编号有差距怎么办?你最终会为一些 id 做不必要的工作而完全错过其他的。至少,您应该将其转换为循环游标,以确保您 select 来自 uczniowie table.
的所有 ID此外,所有这些单独的 select 都可以在一个 select 中完成。当您使用它时,您可以将它合并到一个 update/merge 语句中。因此首先否定了所有循环的需要,比如(未经测试,因为你没有提供任何样本数据):
merge into rank tgt
using (select wlasciciel student_id,
sum(waga * wynik)/sum(waga) vsrednia,
sum(case when przedmiot = 'Fizyka' then waga * wynik end)/sum(case when przedmiot = 'Fizyka' then waga end) vfizyka,
sum(case when przedmiot = 'Matematyka' then waga * wynik end)/sum(case when przedmiot = 'Matematyka' then waga end) vmatematyka,
sum(case when przedmiot = 'Przysposobienie Obronne' then waga * wynik end)/sum(case when przedmiot = 'Przysposobienie Obronne' then waga end) vpo,
sum(case when przedmiot = 'WF' then waga * wynik end)/sum(case when przedmiot = 'WF' then waga end) vwf,
sum(case when przedmiot = 'Religioznastwo' then waga * wynik end)/sum(case when przedmiot = 'Religioznastwo' then waga end) vrel,
sum(case when przedmiot = 'Muzyka' then waga * wynik end)/sum(case when przedmiot = 'Muzyka' then waga end) vmuzyka
from oceny
group by wlasciciel) src
on (tgt.id_ucznia = src.student_id)
when matched then
update set tgt.srednia = src.vsrednia,
tgt.srednia_mat = src.vmatematyka,
tgt.srednia_fiz = src.vfizyka,
tgt.srednia_rel = src.vrel,
tgt.srednia_muz = src.vmuzyka,
tgt.srednia_po = src.vpo,
tgt.srednia_wf = src.vwf
when not matched then -- optional insert clause, just in case the id isn't already present in the rank table
insert (tgt.id_ucznia,
tgt.srednia,
tgt.srednia_mat,
tgt.srednia_fiz,
tgt.srednia_rel,
tgt.srednia_muz,
tgt.srednia_po,
tgt.srednia_wf)
values (src.student_id,
src.vsrednia,
src.vmatematyka,
src.vfizyka,
src.vrel,
src.vmuzyka,
src.vpo,
src.vwf);
您会发现单个语句的性能优于您的过程(1. 您不必为每个单个 ID 查询相同的 table 7 次,并且 2. 您不必切换上下文在 pl/sql 和 sql 之间每次移动到另一个 id 时)。它还应该更容易调试,因为现在您可以 运行 单独的 select 语句来查看输出并检查它是否与您期望的相匹配。