Oracle PL/SQL 中基于参数的选择查询

Posted

技术标签:

【中文标题】Oracle PL/SQL 中基于参数的选择查询【英文标题】:Picking query based on parameter in Oracle PL/SQL 【发布时间】:2010-11-16 18:44:29 【问题描述】:

好的,假设我有一个问题:

SELECT * FROM TABLE_AWESOME WHERE YEAR = :AMAZINGYEAR;

效果很好。但是假设我希望能够仅返回这些结果或基于下拉列表返回所有结果。 (例如,下拉列表将包含 2008、2009、ALL YEARS)

我决定用 PL/SQL 解决上述问题,格式如下:

DECLARE
  the_year VARCHAR(20) := &AMAZINGYEAR;
BEGIN
  IF the_year = 'ALL' THEN
      SELECT * FROM TABLE_AWESOME;
  ELSE
      SELECT * FROM TABLE_AWESOME WHERE YEAR = the_year;
  END IF;
END;

不幸的是,这失败了。我收到诸如“此 SELECT 语句中应有 INTO 子句”之类的错误。

我对 PL/SQL 完全陌生,所以我认为我对它的期望太高了。我查看了文档,但没有找到任何理由说明这不能按照我的方式工作。我实际使用的查询比这复杂得多,但我想保持简单,以便我能很快得到答案。

提前致谢:)

【问题讨论】:

你必须对查询的结果做一些事情。接下来你想做什么? 我只希望它返回结果。我在 ASP.NET 中使用 SqlDataSource 绑定到结果。我不打算将其创建为一个过程,我只想执行该块并绑定到结果。这可能吗? 【参考方案1】:

Jim 和 Alex 提供的查询存在真正的危险。

假设你有 20 年的数据,所以查询 YEAR = 返回 5% 的块。我说的是块而不是行,因为我假设数据是在那个日期添加的,所以聚类因子很高。

如果您想要 1 年,您希望优化器使用年索引来查找这 ​​5% 的行。

如果您想要所有年份,您希望优化器使用全表扫描来获取每一行。

到目前为止我们还好吗?

一旦您将其投入生产,Oracle 第一次加载查询时,它会在绑定变量处达到峰值,并基于该变量制定计划。

假设第一次加载是“全部”。

太好了,该计划是全表扫描 (FTS),该计划已缓存,您可以在 5 分钟内取回所有行。没什么大不了的。

您说的下一次运行是 1999 年。但是该计划已缓存,因此它使用 FTS 仅获取 5% 的行,并且需要 5 分钟。 “嗯......用户说,那是更少的行和相同的时间。”不过没关系……这只是一个 5 分钟的报告……生活有点慢,不是必须的,但没有人大喊大叫。

那天晚上,批处理作业将该查询从缓存中删除,早上第一个用户请求 2001。Oracle 检查缓存,而不是那里,查看变量 2001。啊,最好的计划是索引扫描。并且该计划被缓存。结果会在 10 秒内返回,让用户大吃一惊。下一个人,通常是第一个,早上“ALL”报告并且查询永远不会返回。

为什么?

因为它通过查看索引来获取每一行......可怕的嵌套循环。 5 分钟报告现在是 30 分钟,而且还在增加。

您的原始帖子有最佳答案。两个查询,这样总是能得到最好的计划,绑定变量偷看不会杀了你。

您遇到的问题只是一个基本的 Oracle 问题。您从工具运行查询并将结果返回到工具中。如果将 select 语句放入 pl/sql 块中,则必须对其进行处理。您必须将其加载到游标、数组或变量中。这与你错而他们是对的无关......只是缺乏pl / sql技能。

【讨论】:

+1 好建议,但并不总是有问题。就我们所知,该表在年份上有一个 UNIQUE 约束。例如它可能是一个汇总表。 同意,我的警告措辞不够强烈 *8-) 这就是我喜欢这个网站的原因...我一直在学习。我认为这对我来说不会有问题。我正在使用的查询是返回学生在所有学期上的所有课程。因此,您正在查看最多 50 行。而且,平均大约 6 行。 @Jeffrey,我要补充的最后一件事是,当然你是对的,这并不总是一个问题......但是 1/some OR ALL 类型的 SQL 构造将始终 可能遭受这个问题。使用 IF 和 2 SQL 将永远受此影响。总的来说,我反对那些因为人们养成习惯而可能出错的事情,如果它在这里运作良好,那么它显然在任何地方都运作良好,然后它们就会从基因库中被淘汰。如果您养成的习惯没有问题,那么您最好传播这些习惯。 等等,表格中最多 50 行?那么学校很小吧?或者您是在谈论结果,如果您没有每个学生 1 个表,那么您在查询中也有一个 WHERE Student_ID = :1 ,这确实极大地改变了我们的答案。我认为你举了一个例子来简化它,但没有意识到你遗漏了关键细节。【参考方案2】:

您可以通过一个查询来完成,例如:

SELECT * FROM TABLE_AWESOME WHERE (? = 'ALL' OR YEAR = ?)

并将参数传递给它两次。

【讨论】:

除非您的总行数接近 1 年的行数,否则不要这样做。如果你在那里有 20 年或更长时间......不要使用这个。你会遇到性能问题。你仍然需要解释你想要做什么。【参考方案3】:

在PL/SQL中你必须SELECT ... INTO一些东西,你需要能够返回给客户端;这可能是一个参考光标,如 tanging 所示。这会使客户复杂化。

您可以在 SQL 中执行此操作,例如:

SELECT * FROM TABLE_AWESOME WHERE :AMAZING_YEAR = 'ALL' OR YEAR = :AMAZINGYEAR;

...尽管您可能需要注意索引;我会查看两种参数类型的执行计划,以检查它没有做意外的事情。

【讨论】:

谢谢。我对此投了赞成票,但由于吉姆在你的答案之前得到了他的答案,所以我把这个问题交给了他。 :)【参考方案4】:

不确定是否使用 SqlDataSource,但您绝对可以通过 system.data.oracle 或 oracle 客户端执行此操作。

您可以通过 asp.net 中的匿名块来执行此操作

VAR SYS1 REFCURSOR;
VAR SYS2 REFCURSOR;

DECLARE
  FUNCTION CURSORCHOICE(ITEM IN VARCHAR2) RETURN SYS_REFCURSOR IS
      L_REFCUR SYS_REFCURSOR;
    returnNum VARCHAR2(50);
    BEGIN
        IF upper(item) = 'ALL' THEN
            OPEN L_REFCUR FOR
            SELECT  level  FROM DUAL 
            CONNECT BY LEVEL < 15 ;
        ELSE
            OPEN L_REFCUR FOR
            SELECT   'NONE'  FROM DUAL ;  
        END IF;
        RETURN L_REFCUR;
    END ;
BEGIN
:SYS1 := CURSORCHOICE('ALL');
:SYS2 := CURSORCHOICE('NOT ALL');
end ;
/
PRINT :SYS1 ;
PRINT :SYS2 ;

而您只需创建一个输出参数(refcursor 类型)——而不是 var sys# refcursors)并且几乎只是修改上面的代码。

我在这里回答了一个关于获取匿名块引用的类似问题 How to return a RefCursor from Oracle function?

【讨论】:

【参考方案5】:

应在您的代码中处理此类参数,以便您的 OracleCommand 对象仅执行任一查询。

using (var connection = new OracleConnection(connString)) 
    connection.Open();

    string sql = "select * from table_awesome";
    sql = string.Concat(sql, theYear.Equals(@"ALL") ? string.Empty : " where year = :pYear")

    using (var command = connection.CreateCommand()) 
        command.CommancText = sql;
        command.CommandType = CommandType.Text;
        var parameter = command.CreateParameter();
        parameter.Name = @":yearParam";
        parameter.Direction = ParameterDirection.Input;
        parameter.Value = theYear;

        var reader = command.ExecuteQuery();

        if (!reader.HasRows) return;

        while (reader.Read()) 
            // Extract your data from the OracleDataReader instance here.
        
    

【讨论】:

以上是关于Oracle PL/SQL 中基于参数的选择查询的主要内容,如果未能解决你的问题,请参考以下文章

带有参数的 PL/SQL 过程/函数从选择查询返回表

ORACLE PL/SQL:函数和可选参数,如何?

从基于 PL/SQL 函数体返回 SQL 查询的 Oracle APEX 5 经典报告下载到 Excel 选项,

ORACLE PL/SQL:使用多个参数调用存储过程函数(DML 查询)

构建一些动态查询选择并立即在 Oracle PL/SQL 中显示其输出

Oracle SQL Developer - 使用 Job Wizard PL/SQL 的基本选择查询