2017-06-06 40 views
1

因此,我必須創建一個存儲過程來在Oracle DataBase中搜索電影。在PL/SQL測試LOOP中替換DBMS_OUPUT以返回集合

搜索字符串遵循以下邏輯:

  1. 它看起來對電影的括號
    防爆年。 (1992)

  2. 它在括號內尋找一年的範圍
    Ex。 [1992,2000]

  3. 它尋找包含在標題,國家,realisator,流派,演員或演員的單詞。

  4. 以上任何一項都可以組合多次。例子:
    Ex。 :主環伊恩麥克萊恩克里斯托弗·李[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的問題指出,應該避免。

  • 使用管道行縫合的問題只能與函數一起工作(更改過程調用的函數的過程可能是一種有效的解決方法,我想知道在tho之前是否還有其他可行的方法)。

+0

這些條件應該是OR還是AND?因爲您應該能夠編寫一個查詢,以便爲您提供符合這些條件的所有內容。 – SandPiper

+0

他們是AND條件,我很樂意在單個查詢中做到這一點,你能告訴我一個例子嗎?例如:「電影標題」和「演員1」和「演員2」和「年份」(訂單可以更改) – user3220633

回答

0

使用該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; 

你的參數「電影」擁有所有從你的程序產生的積極成果,以及所有你需要做的就是讀取休眠光標。

請注意,一旦關閉了連接,臨時表就會丟失所有數據,並在另一個會話開始時再次使用(始終記得關閉遊標/連接)。

+0

這工作得很好,我改變了提交保存的提交刪除,所以我不必關閉每次會話。我正在使用session.createStoredProcedureQuery()。你知道如果hibernate處理關閉光標嗎? – user3220633

+0

Hello @ user3220633很高興聽到它對您有用,如果您更改了ON COMMIT行爲,請記住取下我在您的過程中植入的「提交」。 我很抱歉地說我不熟悉Hibernate,就我所見到的其他語言(即PHP,C#)而言,關閉連接會釋放服務器端資源(包括遊標),但不要採用我的話,如果我們在這個問題上進行一些探索,會發現不好的事情會發生。 –

0

這可能只需要查詢即可完成。您可以定義搜索詞是這樣的: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,標題和年份是因爲標題和年份將與film_id有不同的值。有幾種方法可以對這隻貓進行皮膚處理,但它都是圍繞使用LIKE語句和您定義的變量進行的。如果您不想明確定義腳本中的值,則可以使用ACCEPT命令。或者,您可以將它與其他將這些值應用於變量的其他接口相連接。

有了這種方法,在你如何確定你的數據的佈局方面,這在黑暗中是一種鏡頭。但要記住良好的關係數據庫管理實踐,你應該能夠把它變成一個可用的查詢。

其他方法:

您還可以使用一系列的子查詢發現上面用類似的語句相同的數據,然後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 

那麼你將有得到

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

然後,INNER JOIN會給你的ID號碼2和3,因爲這些都是所有的條件得到滿足的唯一部分。重複您的其他搜索條件的過程。我希望這有幫助。

+0

與此問題是,它不處理「遞歸」wheres,並且沒有辦法確定在哪裏拆分參數。對於每個參數,字符串可以包含從0到X的任何數字。例如:可能有2個演員1個國家和3個流派。然而,沒有辦法告訴我們:第一個是演員,第三個是國家,其餘的都是流派。可以將所有的字符串連接起來,並使用像我一樣的地方,然後測試每個單詞,但那是我不適合查詢的部分。你知道在每個子字符串的哪裏做一個foreach的方法嗎? – user3220633

+0

這很有趣,但這仍然不能解決主要問題。謝謝你的時間壽! – user3220633