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

发布于 2024-10-03 00:05:33 字数 613 浏览 3 评论 0原文

好吧,假设我有一个疑问:

SELECT * FROM TABLE_AWESOME WHERE YEAR = :AMAZINGYEAR;

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

我决定使用以下格式使用 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 完全陌生,所以我认为我对它的期望太高了。我已经查看了文档,但没有找到任何原因为什么这不能按照我的方式工作。我实际使用的查询比这复杂得多,但我想保持简单,这样我就能很快得到答案。

提前致谢 :)

Ok, say I have a query:

SELECT * FROM TABLE_AWESOME WHERE YEAR = :AMAZINGYEAR;

Which works very nicely. But say I want to be able to return either just those results or all results based on a drop down. (e.g., the drop down would have 2008, 2009, ALL YEARS)

I decided to tackle said problem with PL/SQL with the following format:

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;

Unfortunately, this fails. I get errors like "an INTO clause is expected in this SELECT statement".

I'm completely new to PL/SQL so I think I'm just expecting too much of it. I have looked over the documentation but haven't found any reason why this wouldn't work the way I have it. The query I'm actually using is much much more complicated than this but I want to keep this simple so I'll get answer quickly.

Thanks in advance :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

毁梦 2024-10-10 00:05:33

吉姆和亚历克斯提出的问题确实存在危险。

假设,其中有 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 技能。

There is a real danger in the queries offered by Jim and Alex.

Assumption, you have 20 years of data in there, so a query on YEAR = return 5% of the blocks. I say blocks and not rows because I assume the data is being added on that date so the clustering factor is high.

If you want 1 year, you want the optimizer to use an index on year to find those 5% of rows.

If you want all years, you want the optimizer to use a full table scan to get every row.

Are we good so far?

Once you put this into production, the first time Oracle loads the query it peaks at the bind variable and formulates a plan based on that.

SO let's say the first load is 'All'.

Great, the plan is a Full table scan (FTS) and that plan is cached and you get all the rows back in 5 minutes. No big deal.

The next run you say 1999. But the plan is cached and so it uses a FTS to get just 5% of the rows and it takes 5 minutes. "Hmmm... the user says, that was many fewer rows and the same time." But that's fine... it's just a 5 minute report... life is a little slow when it doesn't have to be but no one is yelling.

That night the batch jobs blow that query out of the cache and in the morning the first user asks for 2001. Oracle checks the cache, not there, peeks at the variable, 2001. Ah, the best plan for that is an index scan. and THAT plan is cached. The results come back in 10 seconds and blows the user away. The next person, who is normally first, does the morning "ALL" report and the query never returns.

WHY?

Because it's getting every single row by looking through the index.... horrible nested loops. The 5 minute report is now at 30 and counting.

Your original post has the best answer. Two queries, that way both will ALWAYS get the best plan, bind variable peeking won't kill you.

The problem you're having is just a fundamental Oracle issue. You run a query from a tool and get the results back INTO the tool. If you put a select statement into a pl/sql block you have to do something with it. You have to load it into a cursor, or array, or variable. It's nothing to do with you being wrong and them being right... it's just a lack of pl/sql skills.

倾城°AllureLove 2024-10-10 00:05:33

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

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

并将参数传递给它两次。

You could do it with one query, something like:

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

and pass it the argument twice.

两人的回忆 2024-10-10 00:05:33

在 PL/SQL 中,您必须SELECT ... INTO某些内容,您需要能够将其返回给客户端;正如 taning 所示,这可能是一个参考光标。这可能会让客户变得复杂。

您可以在 SQL 中使用以下内容来执行此操作:

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

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

In PL/SQL you have to SELECT ... INTO something, which you need to be able to return to the client; that could be a ref cursor as tanging demonstrates. This can complicate the client.

You can do this in SQL instead with something like:

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

... although you may need to take care about indexes; I'd look at the execution plan with both argument types to check it isn't doing something unexpected.

惟欲睡 2024-10-10 00:05:33

不确定是否使用 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),并且几乎只需修改上面的代码即可。

我在这里回答了有关获取匿名块引用的类似问题
如何从 Oracle 函数返回 RefCursor?

Not sure about using a SqlDataSource, but you can definately do this via the system.data.oracle or the oracle clients.

You would do this via an anonymous block in 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 ;

whereas you would simply create an output param (of type refcursor) -- instead of the var sys# refcursors) and pretty much just amend the above code.

I answered a similar question about getting an anonymous block refcuror here
How to return a RefCursor from Oracle function?

画骨成沙 2024-10-10 00:05:33

此类参数应在您的代码中进行处理,以便您的 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.
        }
    }
}

This kind of parameter shall be processed from within your code so that your OracleCommand object only executes either queries.

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.
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文