2016-02-29 1 views
2

Considérez ce tableau (simplifié) qui pourrait avoir beaucoup de lignes:Existe-t-il une fonction d'analyse Oracle qui peut aider à transformer ce code SQL en vue?

CREATE TABLE v 
(
    m VARCHAR2(50), 
    ts date, 
    v NUMBER 
) 
/

ensuite les éléments suivants fonctionne très bien comme une requête:

SELECT 
    m, 
    MIN(ts) min_ts, 
    MAX(ts) max_ts 
FROM 
    v 
WHERE 
    TO_DATE('2016-01-10','YYYY-MM-DD') <= ts AND 
    ts < TO_DATE('2016-01-20','YYYY-MM-DD') AND 
    m = '123' 
GROUP BY 
    m 
/

Lorsque le TO_DATES et « 123 » représentent des critères de filtrage fournis par l'utilisateur . Maintenant, lorsque je tente de convertir cette SQL à une vue et de mettre les critères, je rencontre des problèmes:

CREATE OR REPLACE VIEW vv AS 
SELECT 
    m, 
    MIN(ts) min_ts, 
    MAX(ts) max_ts 
FROM 
    v 
GROUP BY 
    m 
/

Je ne peux pas fournir le filtre de date de ts sur la vue comme Oracle déjà ai regroupé le résultat , par exemple, ce qui suit ne fonctionnera pas (ORA-00904: « TS »: identificateur non valide):

SELECT 
    * 
FROM 
    vv 
WHERE 
    TO_DATE('2016-01-10','YYYY-MM-DD') <= ts AND 
    ts < TO_DATE('2016-01-20','YYYY-MM-DD') AND 
    m='123' 
/

Ainsi sont-il des fonctions analytiques Oracle etc. qui peuvent aider dans ce scénario pour transformer le SQL en vue?

+0

Faut-il être appelable comme ça, dans une clause 'from'? Voulez-vous le joindre à d'autres choses? (Vous vous demandez si une fonction renvoyant un curseur ref pourrait convenir, sinon une vue paramétrée utilisant un contexte est possible mais peut prêter à confusion, et le contexte doit être construit, ou une fonction renvoyant une collection - dépend de l'utilisation requise) –

+0

Vous devez le faire avec une fonction de table définie par l'utilisateur. Oracle (et SQL en général) n'a pas le concept de vues paramétrées. –

Répondre

0

J'utilisais un fonction de table pipelinée dans un paquet en utilisant une vue factice et% ROWTYPE sur ce point de vue, quelque chose comme:

-- Dummy view 
CREATE OR REPLACE VIEW vv AS 
SELECT 
    m, 
    MIN(ts) min_ts, 
    MAX(ts) max_ts 
FROM 
    v 
WHERE 
    1=0 
GROUP BY 
    m 
/

CREATE OR REPLACE PACKAGE vv_utl 
AS 
    TYPE tt_vv IS TABLE OF vv%ROWTYPE; 

    FUNCTION load 
    (
     ifromts IN date, 
     itots IN date, 
     im IN varchar2 default null 
    ) RETURN tt_vv PIPELINED; 
END; 
/

CREATE OR REPLACE PACKAGE BODY vv_utl 
AS 
    FUNCTION load 
    (
     ifromts IN date, 
     itots IN date, 
     im IN varchar2 default null 
    ) RETURN tt_vv PIPELINED 
    AS 
     CURSOR vv_cur IS 
     SELECT 
     m, 
     MIN(ts) min_ts, 
     MAX(ts) max_ts 
     FROM 
     v 
     WHERE 
     ifromts <= ts AND 
     ts < itots AND 
     m = nvl(im,m) 
     GROUP BY 
     m; 
    BEGIN 
     FOR rec IN vv_cur LOOP 
     pipe row(rec); 
     END LOOP; 
    END; 
END; 
/

-- Test 
SELECT 
    * 
FROM 
    table(
     vv_utl.load(
     TO_DATE('2016-01-10','YYYY-MM-DD'), 
     TO_DATE('2016-01-20','YYYY-MM-DD') 
     ) 
    ) 
/

J'étais inquiet de la performance de la fonction de table pipelinée (je lu quelque part qu'ils ne cache pas leurs résultats) et comme je ne pouvais pas faire ma fonction déterministe (une façon d'obtenir Oracle pour mettre en cache les résultats), je suis allé pour une solution de vue directement en utilisant un package pour stocker mes paramètres:

CREATE OR REPLACE PACKAGE vv_param 
AS 
    PROCEDURE set 
    (
     ifromts IN date, 
     itots IN date, 
     im IN varchar2 default null 
    ); 

    FUNCTION get_fromts RETURN DATE; 
    FUNCTION get_tots RETURN DATE; 
    FUNCTION get_m RETURN VARCHAR2; 

END; 
/

CREATE OR REPLACE PACKAGE BODY vv_param 
AS 
    lfromts date; 
    ltots date; 
    lm varchar2(50); 

    PROCEDURE set 
    (
     ifromts IN date, 
     itots IN date, 
     im IN varchar2 default null 
    ) 
    AS 
    BEGIN 
     lfromts := ifromts; 
     ltots := itots; 
     lm := im; 
    END; 

    FUNCTION get_fromts RETURN DATE AS BEGIN RETURN lfromts; END; 
    FUNCTION get_tots RETURN DATE AS BEGIN RETURN ltots; END; 
    FUNCTION get_m RETURN VARCHAR2 AS BEGIN RETURN lm; END; 

END; 
/

CREATE OR REPLACE VIEW vv AS 
SELECT 
    m, 
    MIN(ts) min_ts, 
    MAX(ts) max_ts 
FROM 
    v 
WHERE 
    vv_param.get_fromts <= ts AND 
    ts < vv_param.get_tots AND 
    m = nvl(vv_param.get_m,m) 
GROUP BY 
    m 
/

BEGIN 
    vv_param.set(
     TO_DATE('2016-01-10','YYYY-MM-DD'), 
     TO_DATE('2016-01-20','YYYY-MM-DD') 
    ); 
END; 
/

-- Test 
SELECT 
    * 
FROM 
    vv 
/

également lors de l'utilisation d'une fonction de table pipelinée Je ne pouvais pas voir les détails du PLAN EXPLAIN car le SQL était caché dans la fonction du paquet. Maintenant, un PLAN EXPLAIN sur la vue fonctionne comme d'habitude.

Cet exemple est évidemment très simple mais dans mon implémentation actuelle il y a beaucoup de ces vues, beaucoup en fonction d'autres vues comme ça. L'utilisation de la solution de variable de package m'a permis de les partager à travers toutes ces vues. Tout oui, ce n'est pas sans ses pièges, par ex. on pourrait oublier de définir les variables et si vous réutilisez la session comme dans ODP.NET, vous pourriez utiliser d'anciennes valeurs et ainsi obtenir le mauvais résultat. Donc, seul le temps nous dira si c'était la bonne façon d'y aller!

1

Il est possible de le faire, mais il nécessite l'utilisation d'un contexte, avec le paquet nécessaire pour faire le réglage/effacement du contexte. Vous aurez besoin de tester avec vos données pour voir si cela est assez performant.

Créer un nouveau contexte

create or replace context test_context using pkg_context_utils; 

Créer package pour définir le nouveau contexte

create or replace package pkg_context_utils 
as 
    procedure set_date (p_date in date); 
    procedure clear_date; 
end pkg_context_utils; 
/

create or replace package body pkg_context_utils 
as 
    procedure set_date (p_date in date) 
    is 
    begin 
    dbms_session.set_context (namespace => 'test_context', 
           attribute => 'test_date', 
           value  => p_date); 
    end set_date; 

    procedure clear_date 
    is 
    begin 
    dbms_session.clear_context(namespace => 'test_context', 
           client_id => null, 
           attribute => 'test_date'); 
    end clear_date; 

end pkg_context_utils; 
/

Créer une vue en utilisant le contexte dans lequel la clause

create or replace view test_view 
as 
select * from dual where sys_context('test_context', 'test_date') > sysdate; 

Exécutez la vue sans contexte défini

select * from test_view; 

no rows selected. 

Définir le contexte d'être après la date

begin 
    pkg_context_utils.set_date(trunc(sysdate + 1)); 
end; 
/

select * from test_view; 

DUMMY 
----- 
X 

Réglez le contexte avant la date actuelle

begin 
    pkg_context_utils.set_date(trunc(sysdate)); 
end; 
/

select * from test_view; 

no rows selected. 
+0

Cette façon très intéressante de le faire. Je suppose que l'on pourrait utiliser une variable de package définie par l'utilisateur au lieu de sys_context? Je suis allé pour une approche de fonction pipelined mais suis inquiet au sujet de la performance donc je ne sais pas si cela va fonctionner pour moi. – VinceJS