【问题标题】:Replacing DBMS_OUPUT in a PL/SQL test LOOP to return a collection在 PL/SQL 测试循环中替换 DBMS_OUPUT 以返回集合
【发布时间】:2017-11-07 21:07:58
【问题描述】:

所以我有这个任务,我必须创建一个存储过程来搜索 Oracle 数据库中的电影。

搜索字符串遵循以下逻辑:

  1. 查找括号中的电影年份
    前任。 (1992)

  2. 查找括号中的年份范围
    前任。 [1992,2000]

  3. 它会查找包含在标题、国家/地区、现实者、流派、演员或编剧中的单词。

  4. 以上任何一项都可以多次组合。
    前任。 : 指环王伊恩·麦克莱恩·克里斯托弗·李 [1992,2000]

解决这个问题的逻辑是做一个巨大的查询来分组所有需要的数据,然后使用游标循环遍历结果集,用游标检查搜索字符串的每个单词是否有效。

我设法创建了一个按预期工作的过程,但我发现返回结果的唯一方法是使用 DBMS_OUTPUT。现在的问题是,当我插入 Hibernate 时,DBMS_OUTPUT 不会发送到客户端。我已经阅读了一些通过设置 DBMS_OUTPUT.enable 来强制输出的方法,但我觉得这不是正确的方法。

这是我的问题:

  1. 我的逻辑有问题吗?有没有更简单的方法可以通过一次选择或其他方式存档?

  2. 有没有办法在游标内动态推送数据并返回?

  3. 我真的应该欺骗 DBMS_OUTPUT 以便将其发送到休眠状态吗?

这是我的代码:

CREATE OR REPLACE PROCEDURE p_SearchFilm(searchString IN VARCHAR2) IS
    IsValid BOOLEAN;
    y1 INTEGER;
    y2 INTEGER;
    subStrArray apex_application_global.vc_arr2;
    term VARCHAR(100);

    CURSOR films IS 
            Select FilmId, Titre, real.Prenom||' '||real.nom as Realisateur, anneeSortie, ListPays, ListGenres,
                   ListScenaristes, ListActeurs, langueOrigine
                from Film 
                natural left join 
                    (select FilmId, listagg(p.Nom, ',') within group (Order By p.nom) ListPays from Film
                        natural join Film_vs_pays
                        natural join Pays p
                        Group by FilmId)
                natural left join 
                    (select FilmId, listagg(g.Nom, ',') within group (Order By g.nom) ListGenres from Film
                        natural join Film_vs_Genre
                        natural join Genre g
                        Group by FilmId)
                natural left join 
                    (select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListScenaristes from Film
                        natural join Scenariste s
                        join Personne p on s.personneId = p.personneId
                        Group by FilmId)
                natural left join 
                    (select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListActeurs from Film
                        natural join Personnage perso
                        join Personne p on perso.personneId = p.personneId
                        Group by FilmId) 
                left join Personne real on real.personneId = realisateurId;
BEGIN
    <<FILM_LOOP>>
    FOR film IN films LOOP
        subStrArray := apex_util.string_to_table(searchString, ' ');
        FOR i in 1..subStrArray.count LOOP
            IsValid:= FALSE;
            term:= subStrArray(i);
            IF REGEXP_LIKE(term, '\(\d{4}\)') THEN
                IF film.anneeSortie = TO_NUMBER(regexp_substr(term, '\d{4}')) THEN
                    IsValid:= TRUE;
                END IF;
            ELSIF REGEXP_LIKE(term, '\[\d{4},\d{4}\]') THEN
                y1:= regexp_substr(term, '\d{4}', 1, 1);
                y2:= regexp_substr(term, '\d{4}', 1, 2);

                IF film.anneeSortie BETWEEN y1 AND y2 THEN
                    IsValid:= TRUE;
                END IF;
            ELSE
                IF UPPER(film.Titre||film.Realisateur||film.ListActeurs||film.ListScenaristes||film.ListGenres||film.ListPays||film.langueOrigine)
                     LIKE '%'||UPPER(term)||'%' THEN
                    IsValid:= TRUE;
                END IF;
            END IF;

            IF NOT IsValid THEN
                CONTINUE FILM_LOOP;
            END IF;

        END LOOP;

        DBMS_OUTPUT.put_line(film.FilmId||'|'||film.Titre);
    END LOOP;
END;

这里有一个小小的免责声明:

  • 我看到了一些解决此问题的类似问题,但使用游标的问题返回的是完整的选择,而不是手动选择的行。

  • 关于 DBMS_OUTPUT 和 Hibernate 的问题说应该避免。

  • 使用管道行缝合的问题仅适用于函数(更改由过程调用的函数的过程可能是一种有效的解决方法,我想知道在此之前是否还有其他可能)。

【问题讨论】:

  • 这些条件应该是 OR 还是 AND?因为您应该能够编写一个查询,该查询将为您提供满足这些条件的所有内容。
  • 它们是 AND 条件,我很想在一个查询中执行此操作,你能给我举个例子吗?例如:“电影名称”和“演员 1”和“演员 2”和“年份范围”(顺序可以更改)

标签: oracle hibernate plsql dbms-output


【解决方案1】:

DBMS_OUTPUT 包的使用几乎仅限于匿名块的开发人员执行,因此不适合您与 Hibernate 框架的预期通信。

如果您已经有一个存储过程来应用您的过滤器并确定您的阳性结果,解决方案可以用这些阳性填充一个临时表,然后返回一个打开的游标,该游标只有该临时表中的数据,例如:

create global temporary table movie_results( movie varchar2(200) ) on commit preserve rows;

当然,您的临时表可以有更多列,但让我这样保留,只是为了说明我的解决方案。

   CREATE OR REPLACE PROCEDURE p_SearchFilm(searchString IN VARCHAR2, movies out SYS_REFCURSOR) IS
        IsValid BOOLEAN;
        y1 INTEGER;
        y2 INTEGER;
        subStrArray apex_application_global.vc_arr2;
        term VARCHAR(100);

        CURSOR films IS 
                Select FilmId, Titre, real.Prenom||' '||real.nom as Realisateur, anneeSortie, ListPays, ListGenres,
                       ListScenaristes, ListActeurs, langueOrigine
                    from Film 
                    natural left join 
                        (select FilmId, listagg(p.Nom, ',') within group (Order By p.nom) ListPays from Film
                            natural join Film_vs_pays
                            natural join Pays p
                            Group by FilmId)
                    natural left join 
                        (select FilmId, listagg(g.Nom, ',') within group (Order By g.nom) ListGenres from Film
                            natural join Film_vs_Genre
                            natural join Genre g
                            Group by FilmId)
                    natural left join 
                        (select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListScenaristes from Film
                            natural join Scenariste s
                            join Personne p on s.personneId = p.personneId
                            Group by FilmId)
                    natural left join 
                        (select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListActeurs from Film
                            natural join Personnage perso
                            join Personne p on perso.personneId = p.personneId
                            Group by FilmId) 
                    left join Personne real on real.personneId = realisateurId;
    BEGIN
        <<FILM_LOOP>>
        FOR film IN films LOOP
            subStrArray := apex_util.string_to_table(searchString, ' ');
            FOR i in 1..subStrArray.count LOOP
                IsValid:= FALSE;
                term:= subStrArray(i);
                IF REGEXP_LIKE(term, '\(\d{4}\)') THEN
                    IF film.anneeSortie = TO_NUMBER(regexp_substr(term, '\d{4}')) THEN
                        IsValid:= TRUE;
                    END IF;
                ELSIF REGEXP_LIKE(term, '\[\d{4},\d{4}\]') THEN
                    y1:= regexp_substr(term, '\d{4}', 1, 1);
                    y2:= regexp_substr(term, '\d{4}', 1, 2);

                    IF film.anneeSortie BETWEEN y1 AND y2 THEN
                        IsValid:= TRUE;
                    END IF;
                ELSE
                    IF UPPER(film.Titre||film.Realisateur||film.ListActeurs||film.ListScenaristes||film.ListGenres||film.ListPays||film.langueOrigine)
                         LIKE '%'||UPPER(term)||'%' THEN
                        IsValid:= TRUE;
                    END IF;
                END IF;

                IF NOT IsValid THEN
                    CONTINUE FILM_LOOP;
                END IF;

            END LOOP;

            --DBMS_OUTPUT.put_line(film.FilmId||'|'||film.Titre);
            insert into movie_results( movie )
            values film.FilmId||'|'||film.Titre;

            commit;
        END LOOP;

        open movies for
           select *
           from movie_results;
    END;

现在您的参数“movies”具有从您的过程得出的所有积极结果,您所要做的就是在 Hibernate 中读取光标。

请注意,一旦您关闭连接,临时表就会丢失所有数据,并准备好在另一个会话开始时再次使用(请记住关闭您的游标/连接)。

【讨论】:

  • 这很好用,我更改了提交时保留的提交删除,因此我不必每次都关闭会话。我正在使用 session.createStoredProcedureQuery()。你知道hibernate是否处理关闭光标吗?
  • 您好@user3220633 很高兴听到它对您有用,如果您更改了 ON COMMIT 行为,请记住取消我在您的程序中植入的“提交”。很抱歉,我对Hibernate不熟悉,据我在其他语言(即PHP,C#)中看到的,关闭连接会释放包括游标在内的服务器端资源,但是不要拿我的换句话说,如果我们稍微搜索一下这个主题,就会发现不好的情况。
【解决方案2】:

这可能只用一个查询就可以完成。您可以像这样定义搜索词:How to declare variable and use it in the same SQL script? (Oracle SQL)

我将把基本查询留给你写(例如加入适当的数据),但你的脚本实际上看起来像这样:

DEFINE var_year1 = 1992;
DEFINE var_year2 = 1994;
DEFINE var_country = null;
DEFINE var_title = 'Lord Of The Rings';
DEFINE var_realisator = null;
DEFINE var_genre = null;
DEFINE var_actors = 'Ian';
DEFINE var_scenarists = 'Lee';

SELECT film_id, title, year
FROM ...
WHERE year BETWEEN &var_year1 AND &var_year2
 AND UPPER(title) LIKE UPPER('%&var_title%')
 AND UPPER(country) LIKE UPPER('%&var_country%')
 AND UPPER(realisator) LIKE UPPER('%&var_realisator%')
 AND UPPER(genre) LIKE UPPER('%&var_genre%')
 AND UPPER(actors) LIKE UPPER('%&var_actors%')
 AND UPPER(scenarists) LIKE UPPER('%&var_scenarists%');
/

我只选择film_id、title 和year 的原因是因为title 和year 将是film_id 的不同值。有几种方法可以给这只猫剥皮,但它们都围绕着使用带有您定义的变量的 LIKE 语句。如果不想在脚本中显式定义值,可以使用ACCEPT 命令。或者,您可以将它与将这些值应用到变量中的其他东西接口。

使用这种方法,对于您如何真正布置数据有点摸不着头脑。但请记住良好的关系数据库管理实践,您应该能够将其转化为可用的查询。

其他方法:

您还可以使用一系列子查询使用like 语句找到上面相同的数据,然后将它们INNER JOIN 一起找出哪些是共同的。示例:

WITH sub_title AS (SELECT film_id FROM Film WHERE UPPER(title) LIKE UPPER('%&var_title%')),
...
sub_actor AS (SELECT film_id FROM (...) WHERE UPPER(actor) LIKE UPPER('%&var_actor%'))
SELECT film_id
FROM sub_title t
     INNER JOIN sub_actor a ON a.film_id = t.film_id;
/

如果您在其中搜索过“星球大战”,例如,您会返回

1   Star Wars A New Hope
2   Star Wars The Empire Strikes Back
3   Star Wars Return of the Jedi

那么在第二个子查询中,如果你搜索过“Donald Glover”,你会得到

2   Star Wars The Empire Strikes Back
3   Star Wars Return of the Jedi

然后,INNER JOIN 会为您提供 ID 号 2 和 3,因为它们是唯一满足所有条件的 ID。对其他搜索词重复此过程。我希望这会有所帮助。

【讨论】:

  • 这个问题是它不处理“递归”的wheres,并且没有办法确定在哪里分割参数。对于每个参数,字符串可以包含从 0 到 X 的任何数字。例如:可能有 2 个演员 1 个国家和 3 个流派。却说不清:前两个是演员,第三个是国家,剩下的都是类型。可以像我一样将所有字符串连接起来并使用 where ,然后测试每个单词,但这就是我无法适应查询的部分。你知道在每个子字符串的 where 中做一个 foreach 的方法吗?
  • 这很有趣,但这仍然不能解决主要问题。感谢您的宝贵时间!
猜你喜欢
  • 1970-01-01
  • 2012-01-31
  • 2020-08-20
  • 1970-01-01
  • 2023-03-06
  • 2021-07-23
  • 2023-04-07
  • 1970-01-01
  • 2017-03-30
相关资源
最近更新 更多