具有聚合函数的 Oracle Dynamic SQL 过程通过 ORDS 调用需要很长时间,但在 SQL Developer 中运行速度很快
Oracle Dynamic SQL Procedure with Aggregate Function takes long time to call via ORDS, but runs quickly in SQL Developer
我一直在努力改进我的程序,所以我开始将它们从使用 ||
字符串连接更改为使用绑定变量。当我将绑定变量添加到以下过程时,我发现我需要将聚合函数添加到 where 子句,以便搜索数字在逗号分隔的数字列表中的位置。
添加聚合函数 string_to_table_num
和 string_to_table_varchar2
并更新过程的其他部分以使用绑定变量后,我尝试向我的 ORDS 端点发出请求。该请求用了 10 多分钟才完成。我认为这是由于聚合函数和 APEX 的组合造成的,因为原始查询 运行s 不到 500 毫秒。为了提高速度,我尝试了几种不同的功能,但我没有任何运气。
如果您有建议,我也会采纳有关如何改进整体查询的建议。我对 Oracle 比较陌生。
ORDS 端点
BEGIN
get_categoryprods2(:commasepproductids, :commasepproductskus, :pcategoryid, :sortby);
END;
参数
commasepproductids IN STRING from URI
commasepproductskus IN STRING from URI
pcategoryid IN STRING from URI
sortby IN STRING from URI
ptopcount in INT
原始查询
create or replace PROCEDURE get_categoryprods (
commasepproductids IN VARCHAR2,
commasepproductskus in varchar2,
pcategoryid IN NUMBER,
sortby IN VARCHAR2
) AS
l_cursor SYS_REFCURSOR;
v_stmt_str STRING(5000);
s_counter NUMBER;
v_productid productnew.productid%TYPE;
v_manufacturerid productmanufacturer.manufacturerid%TYPE;
v_sename productnew.sename%TYPE;
v_name productnew.name%TYPE;
v_summary varchar(3999);
v_freeground productnew.freeground%TYPE;
v_quantitydiscountid productnew.quantitydiscountid%TYPE;
v_sku productnew.sku%TYPE;
v_price productnew.price%TYPE;
v_msrp productnew.msrp%TYPE;
v_cost productnew.cost%TYPE;
v_saleprice productnew.saleprice%TYPE;
v_weight productnew.weight%TYPE;
v_percase productnew.percase%TYPE;
v_relatedproducts varchar(3999);
v_hidepriceuntilcart productnew.hidepriceuntilcart%TYPE;
v_discontinued productnew.discontinued%TYPE;
v_discounttype productnew.discounttype%TYPE;
v_unitofmeasure productnew.unitofmeasure%TYPE;
v_replacement productnew.replacement%TYPE;
v_displayorder productcategory.displayorder%TYPE;
v_friendlyurl urlmapper.friendlyurl%TYPE;
v_sortby varchar2(300);
BEGIN
IF sortby IS NULL OR sortby = 'null' OR sortby = '' THEN
v_sortby := '"p".Discontinued, "pc".DisplayOrder ';
ELSIF sortby = 'PriceAscending' THEN
v_sortby := '"p".discontinued, "p".price ';
ELSIF sortby = 'PriceDescending' THEN
v_sortby := '"p".discontinued, "p".price DESC ';
ELSIF sortby = 'Name' THEN
v_sortby := '"p".discontinued, "p".name ';
ELSE
v_sortby := '"p".discontinued, "pc".displayorder ';
END IF;
v_stmt_str := 'SELECT
"p".productid,
"pm".manufacturerid,
"p".sename,
"p".name,
to_char(substr("p".summary, 0, 3999)),
TO_NUMBER("p".freeground),
"p".quantitydiscountid,
"p".sku,
"p".price,
"p".msrp,
"p".cost,
"p".saleprice,
"p".weight,
"p".percase,
to_char(substr("p".relatedproducts, 0, 3999)),
"p".hidepriceuntilcart,
"p".discontinued,
"p".discounttype,
"p".unitofmeasure,
"p".replacement,
"pc".displayorder,
"um".friendlyurl
FROM
productnew "p"
LEFT OUTER JOIN productcategory "pc" ON "p".productid = "pc".productid
LEFT OUTER JOIN productmanufacturer "pm" ON "p".productid = "pm".productid
LEFT OUTER JOIN urlmapper "um" ON "p".productid = "um".productid
WHERE
("pc".categoryid = ' || pcategoryid || ')
AND "p".productid in ('||commasepproductids||')
--and ("p".productid in ('||commasepproductids||') -- removed because I broke the actual original proc
AND ( "p".deleted = 0 )
AND "p".published = 1 ORDER BY '||v_sortby;
s_counter := 0;
apex_json.open_array;
OPEN l_cursor FOR v_stmt_str;
FETCH l_cursor into
v_productid,
v_manufacturerid,
v_sename,
v_name,
v_summary,
v_freeground,
v_quantitydiscountid,
v_sku,
v_price,
v_msrp,
v_cost,
v_saleprice,
v_weight,
v_percase,
v_relatedproducts,
v_hidepriceuntilcart,
v_discontinued,
v_discounttype,
v_unitofmeasure,
v_replacement,
v_displayorder,
v_friendlyurl;
loop
EXIT WHEN l_cursor%notfound;
s_counter := 1;
apex_json.open_object;
apex_json.write('ProductID', v_productid);
apex_json.write('ManufacturerID', v_manufacturerid);
apex_json.write('SEName', v_sename);
apex_json.write('Name', v_name);
apex_json.write('Summary', v_summary);
apex_json.write('FreeGround', v_freeground);
apex_json.write('QuantityDiscountID', v_quantitydiscountid);
apex_json.write('SKU', v_sku);
apex_json.write('Price', v_price);
apex_json.write('MSRP', v_msrp);
apex_json.write('Cost', v_cost);
apex_json.write('SalePrice', v_saleprice);
apex_json.write('Weight', v_weight);
apex_json.write('PerCase', v_percase);
apex_json.write('RelatedProducts', v_relatedproducts);
apex_json.write('HidePriceUntilCart', v_hidepriceuntilcart);
apex_json.write('Discontinued', v_discontinued);
apex_json.write('QuantityDiscountType', v_discounttype);
apex_json.write('UnitOfMeasure', v_unitofmeasure);
apex_json.write('Replacement', v_replacement);
apex_json.write('DisplayOrder', v_displayorder);
apex_json.write('FriendlyUrl', v_friendlyurl);
apex_json.close_object;
end loop;
IF s_counter = 0 THEN
apex_json.open_object;
apex_json.write('ProductID', 0);
apex_json.write('SEName', 'NOT FOUND');
apex_json.write('Name', 'NOT FOUND');
apex_json.write('Summary', 'NOT FOUND');
apex_json.write('FreeGround', 'NOT FOUND');
apex_json.write('QuantityDiscountID', 0);
apex_json.write('SKU', 'NOT FOUND');
apex_json.write('Price', 0);
apex_json.write('MSRP', 0);
apex_json.write('Cost', 0);
apex_json.write('SalePrice', 0);
apex_json.write('Weight', 0);
apex_json.write('PerCase', 'NOT FOUND');
apex_json.write('RelatedProducts', 'NOT FOUND');
apex_json.write('HidePriceUntilCart', 'NOT FOUND');
apex_json.write('Discontinued', 'NOT FOUND');
apex_json.write('QuantityDiscountType', 'NOT FOUND');
apex_json.write('UnitOfMeasure', 'NOT FOUND');
apex_json.write('Replacement', 'NOT FOUND');
apex_json.write('FriendlyUrl', 'NOT FOUND');
apex_json.close_object;
END IF;
apex_json.close_all;
END get_categoryprods;
**新查询 1 **
create or replace PROCEDURE GET_CATEGORYPRODS2
(
COMMASEPPRODUCTIDS IN VARCHAR2
, COMMASEPPRODUCTSKUS IN VARCHAR2
, PCATEGORYID IN NUMBER
, SORTBY IN VARCHAR2
, PTOPCOUNT IN VARCHAR2
) AS
l_cursor SYS_REFCURSOR;
v_stmt_str STRING(5000);
s_counter NUMBER;
v_productid productnew.productid%TYPE;
v_manufacturerid productmanufacturer.manufacturerid%TYPE;
v_sename productnew.sename%TYPE;
v_name productnew.name%TYPE;
v_summary varchar(3999);
v_freeground productnew.freeground%TYPE;
v_quantitydiscountid productnew.quantitydiscountid%TYPE;
v_sku productnew.sku%TYPE;
v_price productnew.price%TYPE;
v_msrp productnew.msrp%TYPE;
v_cost productnew.cost%TYPE;
v_saleprice productnew.saleprice%TYPE;
v_weight productnew.weight%TYPE;
v_percase productnew.percase%TYPE;
v_relatedproducts varchar(3999);
v_hidepriceuntilcart productnew.hidepriceuntilcart%TYPE;
v_discontinued productnew.discontinued%TYPE;
v_discounttype productnew.discounttype%TYPE;
v_unitofmeasure productnew.unitofmeasure%TYPE;
v_replacement productnew.replacement%TYPE;
v_displayorder productcategory.displayorder%TYPE;
v_friendlyurl urlmapper.friendlyurl%TYPE;
v_sortby varchar2(300);
type t_categoryprods is table of categoryprod_typ;
l_catprodrow categoryprod_typ;
l_catprods t_categoryprods;
BEGIN
IF sortby IS NULL OR sortby = 'null' OR sortby = '' THEN
v_sortby := '"p".Discontinued, "pc".DisplayOrder ';
ELSIF sortby = 'PriceAscending' THEN
v_sortby := '"p".discontinued, "p".price ';
ELSIF sortby = 'PriceDescending' THEN
v_sortby := '"p".discontinued, "p".price DESC ';
ELSIF sortby = 'Name' THEN
v_sortby := '"p".discontinued, "p".name ';
ELSE
v_sortby := '"p".discontinued, "pc".displayorder ';
END IF;
v_stmt_str := 'SELECT
"p".productid,
"pm".manufacturerid,
"p".sename,
"p".name,
to_char(substr("p".summary, 0, 3999)),
to_number("p".freeground),
"p".quantitydiscountid,
"p".sku,
"p".price,
"p".msrp,
"p".cost,
"p".saleprice,
"p".weight,
"p".percase,
to_char(substr("p".relatedproducts, 0, 3999)),
"p".hidepriceuntilcart,
"p".discontinued,
"p".discounttype,
"p".unitofmeasure,
"p".replacement,
"pc".displayorder,
"um".friendlyurl
FROM
productnew "p"
LEFT OUTER JOIN productcategory "pc" ON "p".productid = "pc".productid
LEFT OUTER JOIN productmanufacturer "pm" ON "p".productid = "pm"
.productid
LEFT OUTER JOIN urlmapper "um" ON "p".productid = "um".productid
WHERE
"pc".categoryid = :pcategoryid
AND ( ( "p".productid IN (SELECT * FROM TABLE (string_to_table_num(:commasepproductids))) OR :commasepproductids IS NULL)
AND (lower("p".sku) IN (SELECT * FROM TABLE(string_to_table_varchar2(:commasepproductskus))) OR :commasepproductskus IS NULL ) )
AND "p".deleted = 0
AND "p".published = 1
ORDER BY :sortby';
s_counter := 0;
apex_json.open_array;
OPEN l_cursor FOR v_stmt_str USING pcategoryid, commasepproductids, commasepproductids, commasepproductskus, commasepproductskus, v_sortby;
FETCH l_cursor into
v_productid,
v_manufacturerid,
v_sename,
v_name,
v_summary,
v_freeground,
v_quantitydiscountid,
v_sku,
v_price,
v_msrp,
v_cost,
v_saleprice,
v_weight,
v_percase,
v_relatedproducts,
v_hidepriceuntilcart,
v_discontinued,
v_discounttype,
v_unitofmeasure,
v_replacement,
v_displayorder,
v_friendlyurl;
loop
EXIT WHEN l_cursor%notfound;
s_counter := 1;
apex_json.open_object;
apex_json.write('ProductID', v_productid);
apex_json.write('ManufacturerID', v_manufacturerid);
apex_json.write('SEName', v_sename);
apex_json.write('Name', v_name);
apex_json.write('Summary', v_summary);
apex_json.write('FreeGround', v_freeground);
apex_json.write('QuantityDiscountID', v_quantitydiscountid);
apex_json.write('SKU', v_sku);
apex_json.write('Price', v_price);
apex_json.write('MSRP', v_msrp);
apex_json.write('Cost', v_cost);
apex_json.write('SalePrice', v_saleprice);
apex_json.write('Weight', v_weight);
apex_json.write('PerCase', v_percase);
apex_json.write('RelatedProducts', v_relatedproducts);
apex_json.write('HidePriceUntilCart', v_hidepriceuntilcart);
apex_json.write('Discontinued', v_discontinued);
apex_json.write('QuantityDiscountType', v_discounttype);
apex_json.write('UnitOfMeasure', v_unitofmeasure);
apex_json.write('Replacement', v_replacement);
apex_json.write('DisplayOrder', v_displayorder);
apex_json.write('FriendlyUrl', v_friendlyurl);
apex_json.close_object;
end loop;
IF s_counter = 0 THEN
apex_json.open_object;
apex_json.write('ProductID', 0);
apex_json.write('SEName', 'NOT FOUND');
apex_json.write('Name', 'NOT FOUND');
apex_json.write('Summary', 'NOT FOUND');
apex_json.write('FreeGround', 'NOT FOUND');
apex_json.write('QuantityDiscountID', 0);
apex_json.write('SKU', 'NOT FOUND');
apex_json.write('Price', 0);
apex_json.write('MSRP', 0);
apex_json.write('Cost', 0);
apex_json.write('SalePrice', 0);
apex_json.write('Weight', 0);
apex_json.write('PerCase', 'NOT FOUND');
apex_json.write('RelatedProducts', 'NOT FOUND');
apex_json.write('HidePriceUntilCart', 'NOT FOUND');
apex_json.write('Discontinued', 'NOT FOUND');
apex_json.write('QuantityDiscountType', 'NOT FOUND');
apex_json.write('UnitOfMeasure', 'NOT FOUND');
apex_json.write('Replacement', 'NOT FOUND');
apex_json.write('FriendlyUrl', 'NOT FOUND');
apex_json.close_object;
END IF;
apex_json.close_all;
END GET_CATEGORYPRODS2;
string_to_table_num
create or replace FUNCTION string_to_table_num (
p VARCHAR2
)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(lower(cc.res));
END LOOP;
END;
string_to_table_varchar
create or replace FUNCTION string_to_table_varchar2(p VARCHAR2)
RETURN tab_varchar2
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(lower(cc.res));
END LOOP;
END;
然后我尝试用以下函数替换 string_to_table 函数
f_convert2
create or replace FUNCTION f_convert2(p_list IN STRING)
RETURN tab_number
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN ;
END f_convert2;
f_convert
create or replace FUNCTION f_convert(p_list IN STRING)
RETURN tab_varchar2
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN;
END f_convert;
如果我从 Postman 或我的 Node 应用程序调用该过程,它会超时或给出 Please check the SQL statement is correctly formed and executes without error. SQL Error Code: ORA-04036: PGA memory used by the instance exceeds PGA_AGGREGATE_LIMIT
。如果我通过 SQL 开发人员将 apex_json.write 替换为 dbms_output.write_line 和 运行,它 运行 很快就可以了。
考虑在使用 table 函数时使用 DYNAMIC_SAMPLING
提示。 Oracle 不知道函数 STRING_TO_TABLE_NUM
返回了多少行,并且可能会使用 8168 行的猜测。动态采样提示指示 Oracle 花费一些额外的解析时间来计算行数,这有望比更好的执行计划节省的时间少得多。
例如,使用这个类型和函数:
create or replace type tab_number as table of number;
create or replace FUNCTION string_to_table_num (
p VARCHAR2
)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(lower(cc.res));
END LOOP;
END;
/
下面的简单示例显示 Oracle 将行数高估为 8168,而不是实际值 9:
explain plan for select * from table(string_to_table_num('1,2,3,4,5,6,7,8,9'));
select * from table(dbms_xplan.display);
Plan hash value: 127161297
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| STRING_TO_TABLE_NUM | 8168 | 16336 | 29 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------
当我们添加动态采样提示时,Oracle 得到完美的基数:
explain plan for select /*+ dynamic_sampling(2) */ * from table(string_to_table_num('1,2,3,4,5,6,7,8,9'));
select * from table(dbms_xplan.display);
Plan hash value: 127161297
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 18 | 11 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| STRING_TO_TABLE_NUM | 9 | 18 | 11 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
这个解决方案没有直接解决为什么 SQL 在不同的上下文中表现不同的问题。但如果你能解决两个巨大的基数问题,其他问题可能就不再重要了。
我一直在努力改进我的程序,所以我开始将它们从使用 ||
字符串连接更改为使用绑定变量。当我将绑定变量添加到以下过程时,我发现我需要将聚合函数添加到 where 子句,以便搜索数字在逗号分隔的数字列表中的位置。
添加聚合函数 string_to_table_num
和 string_to_table_varchar2
并更新过程的其他部分以使用绑定变量后,我尝试向我的 ORDS 端点发出请求。该请求用了 10 多分钟才完成。我认为这是由于聚合函数和 APEX 的组合造成的,因为原始查询 运行s 不到 500 毫秒。为了提高速度,我尝试了几种不同的功能,但我没有任何运气。
如果您有建议,我也会采纳有关如何改进整体查询的建议。我对 Oracle 比较陌生。
ORDS 端点
BEGIN
get_categoryprods2(:commasepproductids, :commasepproductskus, :pcategoryid, :sortby);
END;
参数
commasepproductids IN STRING from URI
commasepproductskus IN STRING from URI
pcategoryid IN STRING from URI
sortby IN STRING from URI
ptopcount in INT
原始查询
create or replace PROCEDURE get_categoryprods (
commasepproductids IN VARCHAR2,
commasepproductskus in varchar2,
pcategoryid IN NUMBER,
sortby IN VARCHAR2
) AS
l_cursor SYS_REFCURSOR;
v_stmt_str STRING(5000);
s_counter NUMBER;
v_productid productnew.productid%TYPE;
v_manufacturerid productmanufacturer.manufacturerid%TYPE;
v_sename productnew.sename%TYPE;
v_name productnew.name%TYPE;
v_summary varchar(3999);
v_freeground productnew.freeground%TYPE;
v_quantitydiscountid productnew.quantitydiscountid%TYPE;
v_sku productnew.sku%TYPE;
v_price productnew.price%TYPE;
v_msrp productnew.msrp%TYPE;
v_cost productnew.cost%TYPE;
v_saleprice productnew.saleprice%TYPE;
v_weight productnew.weight%TYPE;
v_percase productnew.percase%TYPE;
v_relatedproducts varchar(3999);
v_hidepriceuntilcart productnew.hidepriceuntilcart%TYPE;
v_discontinued productnew.discontinued%TYPE;
v_discounttype productnew.discounttype%TYPE;
v_unitofmeasure productnew.unitofmeasure%TYPE;
v_replacement productnew.replacement%TYPE;
v_displayorder productcategory.displayorder%TYPE;
v_friendlyurl urlmapper.friendlyurl%TYPE;
v_sortby varchar2(300);
BEGIN
IF sortby IS NULL OR sortby = 'null' OR sortby = '' THEN
v_sortby := '"p".Discontinued, "pc".DisplayOrder ';
ELSIF sortby = 'PriceAscending' THEN
v_sortby := '"p".discontinued, "p".price ';
ELSIF sortby = 'PriceDescending' THEN
v_sortby := '"p".discontinued, "p".price DESC ';
ELSIF sortby = 'Name' THEN
v_sortby := '"p".discontinued, "p".name ';
ELSE
v_sortby := '"p".discontinued, "pc".displayorder ';
END IF;
v_stmt_str := 'SELECT
"p".productid,
"pm".manufacturerid,
"p".sename,
"p".name,
to_char(substr("p".summary, 0, 3999)),
TO_NUMBER("p".freeground),
"p".quantitydiscountid,
"p".sku,
"p".price,
"p".msrp,
"p".cost,
"p".saleprice,
"p".weight,
"p".percase,
to_char(substr("p".relatedproducts, 0, 3999)),
"p".hidepriceuntilcart,
"p".discontinued,
"p".discounttype,
"p".unitofmeasure,
"p".replacement,
"pc".displayorder,
"um".friendlyurl
FROM
productnew "p"
LEFT OUTER JOIN productcategory "pc" ON "p".productid = "pc".productid
LEFT OUTER JOIN productmanufacturer "pm" ON "p".productid = "pm".productid
LEFT OUTER JOIN urlmapper "um" ON "p".productid = "um".productid
WHERE
("pc".categoryid = ' || pcategoryid || ')
AND "p".productid in ('||commasepproductids||')
--and ("p".productid in ('||commasepproductids||') -- removed because I broke the actual original proc
AND ( "p".deleted = 0 )
AND "p".published = 1 ORDER BY '||v_sortby;
s_counter := 0;
apex_json.open_array;
OPEN l_cursor FOR v_stmt_str;
FETCH l_cursor into
v_productid,
v_manufacturerid,
v_sename,
v_name,
v_summary,
v_freeground,
v_quantitydiscountid,
v_sku,
v_price,
v_msrp,
v_cost,
v_saleprice,
v_weight,
v_percase,
v_relatedproducts,
v_hidepriceuntilcart,
v_discontinued,
v_discounttype,
v_unitofmeasure,
v_replacement,
v_displayorder,
v_friendlyurl;
loop
EXIT WHEN l_cursor%notfound;
s_counter := 1;
apex_json.open_object;
apex_json.write('ProductID', v_productid);
apex_json.write('ManufacturerID', v_manufacturerid);
apex_json.write('SEName', v_sename);
apex_json.write('Name', v_name);
apex_json.write('Summary', v_summary);
apex_json.write('FreeGround', v_freeground);
apex_json.write('QuantityDiscountID', v_quantitydiscountid);
apex_json.write('SKU', v_sku);
apex_json.write('Price', v_price);
apex_json.write('MSRP', v_msrp);
apex_json.write('Cost', v_cost);
apex_json.write('SalePrice', v_saleprice);
apex_json.write('Weight', v_weight);
apex_json.write('PerCase', v_percase);
apex_json.write('RelatedProducts', v_relatedproducts);
apex_json.write('HidePriceUntilCart', v_hidepriceuntilcart);
apex_json.write('Discontinued', v_discontinued);
apex_json.write('QuantityDiscountType', v_discounttype);
apex_json.write('UnitOfMeasure', v_unitofmeasure);
apex_json.write('Replacement', v_replacement);
apex_json.write('DisplayOrder', v_displayorder);
apex_json.write('FriendlyUrl', v_friendlyurl);
apex_json.close_object;
end loop;
IF s_counter = 0 THEN
apex_json.open_object;
apex_json.write('ProductID', 0);
apex_json.write('SEName', 'NOT FOUND');
apex_json.write('Name', 'NOT FOUND');
apex_json.write('Summary', 'NOT FOUND');
apex_json.write('FreeGround', 'NOT FOUND');
apex_json.write('QuantityDiscountID', 0);
apex_json.write('SKU', 'NOT FOUND');
apex_json.write('Price', 0);
apex_json.write('MSRP', 0);
apex_json.write('Cost', 0);
apex_json.write('SalePrice', 0);
apex_json.write('Weight', 0);
apex_json.write('PerCase', 'NOT FOUND');
apex_json.write('RelatedProducts', 'NOT FOUND');
apex_json.write('HidePriceUntilCart', 'NOT FOUND');
apex_json.write('Discontinued', 'NOT FOUND');
apex_json.write('QuantityDiscountType', 'NOT FOUND');
apex_json.write('UnitOfMeasure', 'NOT FOUND');
apex_json.write('Replacement', 'NOT FOUND');
apex_json.write('FriendlyUrl', 'NOT FOUND');
apex_json.close_object;
END IF;
apex_json.close_all;
END get_categoryprods;
**新查询 1 **
create or replace PROCEDURE GET_CATEGORYPRODS2
(
COMMASEPPRODUCTIDS IN VARCHAR2
, COMMASEPPRODUCTSKUS IN VARCHAR2
, PCATEGORYID IN NUMBER
, SORTBY IN VARCHAR2
, PTOPCOUNT IN VARCHAR2
) AS
l_cursor SYS_REFCURSOR;
v_stmt_str STRING(5000);
s_counter NUMBER;
v_productid productnew.productid%TYPE;
v_manufacturerid productmanufacturer.manufacturerid%TYPE;
v_sename productnew.sename%TYPE;
v_name productnew.name%TYPE;
v_summary varchar(3999);
v_freeground productnew.freeground%TYPE;
v_quantitydiscountid productnew.quantitydiscountid%TYPE;
v_sku productnew.sku%TYPE;
v_price productnew.price%TYPE;
v_msrp productnew.msrp%TYPE;
v_cost productnew.cost%TYPE;
v_saleprice productnew.saleprice%TYPE;
v_weight productnew.weight%TYPE;
v_percase productnew.percase%TYPE;
v_relatedproducts varchar(3999);
v_hidepriceuntilcart productnew.hidepriceuntilcart%TYPE;
v_discontinued productnew.discontinued%TYPE;
v_discounttype productnew.discounttype%TYPE;
v_unitofmeasure productnew.unitofmeasure%TYPE;
v_replacement productnew.replacement%TYPE;
v_displayorder productcategory.displayorder%TYPE;
v_friendlyurl urlmapper.friendlyurl%TYPE;
v_sortby varchar2(300);
type t_categoryprods is table of categoryprod_typ;
l_catprodrow categoryprod_typ;
l_catprods t_categoryprods;
BEGIN
IF sortby IS NULL OR sortby = 'null' OR sortby = '' THEN
v_sortby := '"p".Discontinued, "pc".DisplayOrder ';
ELSIF sortby = 'PriceAscending' THEN
v_sortby := '"p".discontinued, "p".price ';
ELSIF sortby = 'PriceDescending' THEN
v_sortby := '"p".discontinued, "p".price DESC ';
ELSIF sortby = 'Name' THEN
v_sortby := '"p".discontinued, "p".name ';
ELSE
v_sortby := '"p".discontinued, "pc".displayorder ';
END IF;
v_stmt_str := 'SELECT
"p".productid,
"pm".manufacturerid,
"p".sename,
"p".name,
to_char(substr("p".summary, 0, 3999)),
to_number("p".freeground),
"p".quantitydiscountid,
"p".sku,
"p".price,
"p".msrp,
"p".cost,
"p".saleprice,
"p".weight,
"p".percase,
to_char(substr("p".relatedproducts, 0, 3999)),
"p".hidepriceuntilcart,
"p".discontinued,
"p".discounttype,
"p".unitofmeasure,
"p".replacement,
"pc".displayorder,
"um".friendlyurl
FROM
productnew "p"
LEFT OUTER JOIN productcategory "pc" ON "p".productid = "pc".productid
LEFT OUTER JOIN productmanufacturer "pm" ON "p".productid = "pm"
.productid
LEFT OUTER JOIN urlmapper "um" ON "p".productid = "um".productid
WHERE
"pc".categoryid = :pcategoryid
AND ( ( "p".productid IN (SELECT * FROM TABLE (string_to_table_num(:commasepproductids))) OR :commasepproductids IS NULL)
AND (lower("p".sku) IN (SELECT * FROM TABLE(string_to_table_varchar2(:commasepproductskus))) OR :commasepproductskus IS NULL ) )
AND "p".deleted = 0
AND "p".published = 1
ORDER BY :sortby';
s_counter := 0;
apex_json.open_array;
OPEN l_cursor FOR v_stmt_str USING pcategoryid, commasepproductids, commasepproductids, commasepproductskus, commasepproductskus, v_sortby;
FETCH l_cursor into
v_productid,
v_manufacturerid,
v_sename,
v_name,
v_summary,
v_freeground,
v_quantitydiscountid,
v_sku,
v_price,
v_msrp,
v_cost,
v_saleprice,
v_weight,
v_percase,
v_relatedproducts,
v_hidepriceuntilcart,
v_discontinued,
v_discounttype,
v_unitofmeasure,
v_replacement,
v_displayorder,
v_friendlyurl;
loop
EXIT WHEN l_cursor%notfound;
s_counter := 1;
apex_json.open_object;
apex_json.write('ProductID', v_productid);
apex_json.write('ManufacturerID', v_manufacturerid);
apex_json.write('SEName', v_sename);
apex_json.write('Name', v_name);
apex_json.write('Summary', v_summary);
apex_json.write('FreeGround', v_freeground);
apex_json.write('QuantityDiscountID', v_quantitydiscountid);
apex_json.write('SKU', v_sku);
apex_json.write('Price', v_price);
apex_json.write('MSRP', v_msrp);
apex_json.write('Cost', v_cost);
apex_json.write('SalePrice', v_saleprice);
apex_json.write('Weight', v_weight);
apex_json.write('PerCase', v_percase);
apex_json.write('RelatedProducts', v_relatedproducts);
apex_json.write('HidePriceUntilCart', v_hidepriceuntilcart);
apex_json.write('Discontinued', v_discontinued);
apex_json.write('QuantityDiscountType', v_discounttype);
apex_json.write('UnitOfMeasure', v_unitofmeasure);
apex_json.write('Replacement', v_replacement);
apex_json.write('DisplayOrder', v_displayorder);
apex_json.write('FriendlyUrl', v_friendlyurl);
apex_json.close_object;
end loop;
IF s_counter = 0 THEN
apex_json.open_object;
apex_json.write('ProductID', 0);
apex_json.write('SEName', 'NOT FOUND');
apex_json.write('Name', 'NOT FOUND');
apex_json.write('Summary', 'NOT FOUND');
apex_json.write('FreeGround', 'NOT FOUND');
apex_json.write('QuantityDiscountID', 0);
apex_json.write('SKU', 'NOT FOUND');
apex_json.write('Price', 0);
apex_json.write('MSRP', 0);
apex_json.write('Cost', 0);
apex_json.write('SalePrice', 0);
apex_json.write('Weight', 0);
apex_json.write('PerCase', 'NOT FOUND');
apex_json.write('RelatedProducts', 'NOT FOUND');
apex_json.write('HidePriceUntilCart', 'NOT FOUND');
apex_json.write('Discontinued', 'NOT FOUND');
apex_json.write('QuantityDiscountType', 'NOT FOUND');
apex_json.write('UnitOfMeasure', 'NOT FOUND');
apex_json.write('Replacement', 'NOT FOUND');
apex_json.write('FriendlyUrl', 'NOT FOUND');
apex_json.close_object;
END IF;
apex_json.close_all;
END GET_CATEGORYPRODS2;
string_to_table_num
create or replace FUNCTION string_to_table_num (
p VARCHAR2
)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(lower(cc.res));
END LOOP;
END;
string_to_table_varchar
create or replace FUNCTION string_to_table_varchar2(p VARCHAR2)
RETURN tab_varchar2
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(lower(cc.res));
END LOOP;
END;
然后我尝试用以下函数替换 string_to_table 函数
f_convert2
create or replace FUNCTION f_convert2(p_list IN STRING)
RETURN tab_number
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN ;
END f_convert2;
f_convert
create or replace FUNCTION f_convert(p_list IN STRING)
RETURN tab_varchar2
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN;
END f_convert;
如果我从 Postman 或我的 Node 应用程序调用该过程,它会超时或给出 Please check the SQL statement is correctly formed and executes without error. SQL Error Code: ORA-04036: PGA memory used by the instance exceeds PGA_AGGREGATE_LIMIT
。如果我通过 SQL 开发人员将 apex_json.write 替换为 dbms_output.write_line 和 运行,它 运行 很快就可以了。
考虑在使用 table 函数时使用 DYNAMIC_SAMPLING
提示。 Oracle 不知道函数 STRING_TO_TABLE_NUM
返回了多少行,并且可能会使用 8168 行的猜测。动态采样提示指示 Oracle 花费一些额外的解析时间来计算行数,这有望比更好的执行计划节省的时间少得多。
例如,使用这个类型和函数:
create or replace type tab_number as table of number;
create or replace FUNCTION string_to_table_num (
p VARCHAR2
)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(lower(cc.res));
END LOOP;
END;
/
下面的简单示例显示 Oracle 将行数高估为 8168,而不是实际值 9:
explain plan for select * from table(string_to_table_num('1,2,3,4,5,6,7,8,9'));
select * from table(dbms_xplan.display);
Plan hash value: 127161297
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| STRING_TO_TABLE_NUM | 8168 | 16336 | 29 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------
当我们添加动态采样提示时,Oracle 得到完美的基数:
explain plan for select /*+ dynamic_sampling(2) */ * from table(string_to_table_num('1,2,3,4,5,6,7,8,9'));
select * from table(dbms_xplan.display);
Plan hash value: 127161297
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 18 | 11 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| STRING_TO_TABLE_NUM | 9 | 18 | 11 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
这个解决方案没有直接解决为什么 SQL 在不同的上下文中表现不同的问题。但如果你能解决两个巨大的基数问题,其他问题可能就不再重要了。