将 PL/SQL 测试循环中的 DBMS_OUPUT 替换为 return collection

Replacing DBMS_OUPUT in a PL/SQL test LOOP to return a collection

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

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

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

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

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

  4. 以上任何一项都可以多次组合。
    前任。 : 指环王 Ian McKellen Christopher Lee [1992,2000]

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

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

所以这是我的问题:

  1. 我的逻辑有问题吗?有没有更简单的方法可以用一个 select 之类的东西来存档?

  2. 有没有办法将数据动态推送到游标中并return它?

  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 框架的预期通信。

如果您已经有一个存储过程来应用您的过滤器并确定您的阳性结果,解决方案可能是用这些阳性结果填充临时 table,然后return 一个打开的游标,它只会包含来自临时 table 的数据,例如:

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

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

   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 中读取游标。

请注意,一旦您关闭连接,临时 table 就会丢失所有数据,并且可以在另一个会话开始时再次使用(永远记得关闭您的 Cursors/Connections)。

第一部分可能只需要一个查询就可以完成。您可以这样定义搜索词: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%');
/

我只 select film_id、标题和年份的原因是因为标题和年份对于 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;
/

如果您在其中搜索 'star war',例如您会返回

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,因为只有它们满足所有条件。对其他搜索词重复该过程。希望对您有所帮助。