在这样一个场景,我 left join 了很多张表,用这些表的不同列来过滤,看起来非常合理
但是出现的问题是 left join 其中一张或多张表出现了笛卡尔积,且无法消除
sql
FUNCTION fun_get_xxx_helper(
v_param_1 VARCHAR2,
v_param_2 VARCHAR2,
v_param_3 VARCHAR2)
RETURN t_xxx_tab pipelined
AS
v_cur t_xxx_cur;
v_rec v_cur%ROWTYPE;
BEGIN
OPEN v_cur FOR
SELECT R.*
FROM (SELECT one.colum_1,
one.colum_2,
three.colum_1,
two.colum_1
FROM table_one one
LEFT JOIN table_two two ON one.colum_1= two.colum_1
LEFT JOIN table_three three ON one.colum_1= three.colum_1
-- left join table_four 是为了用它其中一列作为过滤条件,但是会产生笛卡尔积,且无法消除
LEFT JOIN table_four four ON one.colum_1= four.colum_1
WHERE (v_param_1 is not null AND one.colum_1= v_param_1 ) AND
(v_param_2 is null or two.colum_2 = v_param_2 ) AND
-- 在这里用到了 table_four.colum_3 作为过滤条件
(v_param_3 is null or four.colum_3 = v_param_3 )
)R
-- 输出部分
LOOP
FETCH v_cur INTO v_rec;
EXIT WHEN v_cur%notfound;
PIPE ROW (v_rec);
END LOOP;
END fun_get_xxx_helper;
这个时候可以把原本会产生笛卡尔积的那张表先舍弃掉,把它放在外层 select 的 where 子句中,以子查询的方式实现过滤
改良后:
sql
FUNCTION fun_get_xxx_helper(
v_param_1 VARCHAR2,
v_param_2 VARCHAR2,
v_param_3 VARCHAR2)
RETURN t_xxx_tab pipelined
AS
v_cur t_xxx_cur;
v_rec v_cur%ROWTYPE;
BEGIN
OPEN v_cur FOR
SELECT R.*
FROM (SELECT one.colum_1 id,
one.colum_2 name,
three.colum_1 age,
two.colum_1 gender
FROM table_one one
LEFT JOIN table_two two ON one.colum_1= two.colum_1
LEFT JOIN table_three three ON one.colum_1= three.colum_1
WHERE (v_param_1 is not null AND one.colum_1= v_param_1 ) AND
(v_param_2 is null or two.colum_2 = v_param_2 )
)R
WHERE v_param_3 is null OR
( EXISTS ( SELECT 1
FROM table_four four
WHERE four.colum_1= R.id AND
four.colum_3 = v_param_3
)
)
-- 输出部分
LOOP
FETCH v_cur INTO v_rec;
EXIT WHEN v_cur%notfound;
PIPE ROW (v_rec);
END LOOP;
END fun_get_xxx_helper;
在里层 select 中先把前面的过滤做了,然后在外层的 select 的 where 子句中过滤
v_param_3
为null
则不过滤,不为空则用EXISTS ()
函数配合select 1
子查询来做过滤
以R.id
和v_param_3
作为过滤条件查询table_four
中是否有此数据,若有则保留里层R.id
对应的那条数据,没有则将其过滤掉;
select 1
表示当含有满足条件的时候返回一个常量1
,可以看作一条 record
EXISTS
是SQL中的一个逻辑运算符,通常用于检查子查询中的行是否存在;
EXISTS (subquery)
它根据子查询是否返回任何行返回一个布尔值,如果子查询至少返回一行,则返回结果为True
,子查询没有返回任何行,则返回结果为False
;
通常与WHERE
或HAVING
子句配合使用,有条件地过滤或连接数据;例如,在执行删除或更新数据之前,可以使用
EXISTS
检查相关表中是否存在这条记录:
sqlDELETE FROM table_name WHERE EXISTS (SELECT 1 FROM related_table WHERE related_table.id = table_name.id);