2012-04-20 11 views
6

Ho bisogno di fare qualcosa di veramente strano, ovvero creare record falsi in una vista per colmare il divario tra le date pubblicate dei prezzi dei prodotti.Duplicare i record per riempire il divario tra le date

In realtà, il mio scenario è un po 'più complicato di così, ma ho semplificato i prodotti/date/prezzi.

Diciamo che abbiamo questa tabella:

create table PRICES_TEST 
(
    PRICE_DATE date   not null, 
    PRODUCT  varchar2(13) not null, 
    PRICE   number 
); 

alter table PRICES_TEST 
    add constraint PRICES_TEST_PK 
    primary key (PRICE_DATE, PRODUCT); 

Con questi record:

record
insert into PRICES_TEST values (date'2012-04-15', 'Screw Driver', 13); 
insert into PRICES_TEST values (date'2012-04-18', 'Screw Driver', 15); 

insert into PRICES_TEST values (date'2012-04-13', 'Hammer', 10); 
insert into PRICES_TEST values (date'2012-04-16', 'Hammer', 15); 
insert into PRICES_TEST values (date'2012-04-19', 'Hammer', 17); 

selezionando mi torneranno questo:

PRICE_DATE    PRODUCT  PRICE     
------------------------- ------------- ---------------------- 
13-Apr-2012 00:00:00  Hammer  10      
16-Apr-2012 00:00:00  Hammer  15      
19-Apr-2012 00:00:00  Hammer  17      
15-Apr-2012 00:00:00  Screw Driver 13      
18-Apr-2012 00:00:00  Screw Driver 15      

Supponendo che oggi è 21 Apr 2012, Ho bisogno di una vista che deve ripetere ogni prezzo ogni giorno fino a quando viene pubblicato un nuovo prezzo. Così:

PRICE_DATE    PRODUCT  PRICE     
------------------------- ------------- ---------------------- 
13-Apr-2012 00:00:00  Hammer  10      
14-Apr-2012 00:00:00  Hammer  10      
15-Apr-2012 00:00:00  Hammer  10      
16-Apr-2012 00:00:00  Hammer  15      
17-Apr-2012 00:00:00  Hammer  15      
18-Apr-2012 00:00:00  Hammer  15      
19-Apr-2012 00:00:00  Hammer  17      
20-Apr-2012 00:00:00  Hammer  17      
21-Apr-2012 00:00:00  Hammer  17      
15-Apr-2012 00:00:00  Screw Driver 13      
16-Apr-2012 00:00:00  Screw Driver 13      
17-Apr-2012 00:00:00  Screw Driver 13      
18-Apr-2012 00:00:00  Screw Driver 15      
19-Apr-2012 00:00:00  Screw Driver 15      
20-Apr-2012 00:00:00  Screw Driver 15      
21-Apr-2012 00:00:00  Screw Driver 15      

Qualche idea su come farlo? I non può usare davvero altre tabelle ausiliarie, trigger o programmazione PL/SQL, ho davvero bisogno di fare questo usando una vista.

Penso che questo possa essere fatto usando l'analisi Oracle, ma non mi è familiare. Ho provato a leggere questo http://www.club-oracle.com/articles/analytic-functions-i-introduction-164/ ma non l'ho capito affatto.

+0

NVM, lo capisco ora :) Sarà possibile generare dati con l'uso creativo della tabella 'Dual'. – mellamokb

+0

Potrei presumere che Oracle Analytics abbia la propria tabella delle dimensioni delle date per eseguire tale funzione. Potresti semplicemente creare la tua tabella delle dimensioni della data? –

+0

ecco un interessante articolo sulla creazione di una vista calandar dinamica in tsql (sì, vuoi oracle ma forse può essere modificato): http://sqlserverpedia.com/blog/sql-server-bloggers/tsql-tuesday-18- using-a-recursive-cte-to-create-a-calendar-table/ –

risposta

4

Penso di avere una soluzione che utilizza un approccio incrementale verso il risultato finale con il CTE di:

with mindate as 
(
    select min(price_date) as mindate from PRICES_TEST 
) 
,dates as 
(
    select mindate.mindate + row_number() over (order by 1) - 1 as thedate from mindate, 
    dual d connect by level <= floor(SYSDATE - mindate.mindate) + 1 
) 
,productdates as 
(
    select p.product, d.thedate 
    from (select distinct product from PRICES_TEST) p, dates d 
) 
,ranges as 
(
    select 
    pd.product, 
    pd.thedate, 
    (select max(PRICE_DATE) from PRICES_TEST p2 
    where p2.product = pd.product and p2.PRICE_DATE <= pd.thedate) as mindate 
    from productdates pd 
) 
select 
    r.thedate, 
    r.product, 
    p.price 
from ranges r 
inner join PRICES_TEST p on r.mindate = p.price_date and r.product = p.product 
order by r.product, r.thedate 
  • mindate recupera la prima data possibile nel set di dati
  • dates genera un calendario di date da prima data possibile fino ad oggi.
  • productdates croce unisce tutti i prodotti possibili con tutte le possibili date
  • ranges determina quale data prezzo applicato ad ogni data di
  • i link query finale che risalgono prezzo riservato il prezzo effettivo e filtra le date per le quali non vi sono rilevanti date dei prezzi attraverso la condizione inner join

Demo: http://www.sqlfiddle.com/#!4/e528f/126

+0

Ho appena rimosso il riferimento alla doppia tabella e l'arrotondamento del pavimento nella seconda selezione 'con'. Non sono veramente necessari. Oltre a questo, sembra fantastico! Il mio scenario reale contiene altre 2 colonne nel PK (qualcosa come prodotto, marca, modello) e diverse altre colonne di informazioni (qualcosa come prezzo, peso netto, note) e funziona ancora perfettamente con le modifiche appropriate. Grazie mille. –

6

È possibile creare una dichiarazione generatore riga utilizzando la CONNECT BY LEVEL sintassi, croce unita ai prodotti distinti nella tabella e quindi outer join alla tabella dei prezzi. Il tocco finale è quello di utilizzare la funzione LAST_VALUE e IGNORE NULLS ripetere il prezzo fino a quando si incontra un nuovo valore, e dal momento che si voleva una vista, con un CREATE VIEW dichiarazione:

create view dense_prices_test as 
select 
    dp.price_date 
    , dp.product 
    , last_value(pt.price ignore nulls) over (order by dp.product, dp.price_date) price 
from (
     -- Cross join with the distinct product set in prices_test 
     select d.price_date, p.product 
     from (
      -- Row generator to list all dates from first date in prices_test to today 
      with dates as (select min(price_date) beg_date, sysdate end_date from prices_test) 
      select dates.beg_date + level - 1 price_date 
      from dual 
      cross join dates 
      connect by level <= dates.end_date - dates.beg_date + 1 
      ) d 
     cross join (select distinct product from prices_test) p 
    ) dp 
left outer join prices_test pt on pt.price_date = dp.price_date and pt.product = dp.product; 
+0

Uomo, sei un maestro! Non ho mai sentito parlare di "con", "connetti da" né "ignora i null" in una funzione analitica. Grazie mille. Ha funzionato perfettamente con il mio scenario più complicato. –

+0

@LeoHolanda Eccellente! In bocca al lupo. – Wolf

+0

Ciao @Wolf, anche se la tua risposta è stata quella che mi ha fatto imparare più degli altri, la risposta di mellamokb è quella che effettivamente produce i risultati desiderati, quindi, merita la bandiera "risposta accettata". Grazie mille a tutti voi ragazzi che mi hanno aiutato con questo. –

4

ho fatto alcuni cambiamenti alla risposta eccellente del Lupo .

Ho sostituito il factoring di subquery (WITH) con una sottoquery regolare nel connect by. Questo rende il codice un po 'più semplice.(Anche se questo tipo di codice sembra strano all'inizio, in entrambi i casi, quindi potrebbe non esserci un enorme guadagno.)

In modo significativo, ho usato un join esterno partizione invece di un cross join e outer join. Anche i join esterni delle partizioni sono piuttosto strani, ma sono pensati proprio per questo tipo di situazione. Ciò semplifica il codice e dovrebbe migliorare le prestazioni.

select 
    price_dates.price_date 
    ,product 
    ,last_value(price ignore nulls) over (order by product, price_dates.price_date) price 
from 
(
    select trunc(sysdate) - level + 1 price_date 
    from dual 
    connect by level <= trunc(sysdate) - 
     (select min(trunc(price_date)) from prices_test) + 1 
) price_dates 
left outer join prices_test 
    partition by (prices_test.product) 
    on price_dates.price_date = prices_test.price_date; 
+0

Questo è un bel miglioramento. La clausola WITH era un artefatto di un po 'di altro SQL che avevo, ma continuo a dimenticare i join esterni delle partizioni. Bel lavoro. – Wolf

1

Ho appena realizzato che @Wolf e @jonearles miglioramenti non restituiscono i risultati esatti avevo bisogno perché il generatore fila alla lista tutte le date non genererà gli intervalli per prodotto. Se il primo prezzo del prodotto A è successivo a qualsiasi prezzo del prodotto B, la prima data elencata del prodotto A deve ancora essere la stessa. Ma in realtà mi ha aiutato a lavorare ulteriormente e ottenere i risultati attesi:

ho iniziato con il cambiamento della data selettore @ lupo da questo:

select min(price_date) beg_date, sysdate end_date from prices_test 

a questo:

select min(PRICE_DATE) START_DATE, sysdate as END_DATE, PRODUCT 
from PRICES_TEST group by sysdate, PRODUCT 

Ma, in qualche modo , il numero di righe per prodotto è in crescita esponenziale ripetutamente per ogni livello. Ho appena aggiunto un distinto nella query esterna. Il Infine selezionate è stato questo: soluzione @Mellamokb

select 
    DP.PRICE_DATE, 
    DP.PRODUCT, 
    LAST_VALUE(PT.PRICE ignore nulls) over (order by DP.PRODUCT, DP.PRICE_DATE) PRICE 
from (
    select distinct START_DATE + DAYS as PRICE_DATE, PRODUCT 
    from 
    (
    -- Row generator to list all dates from first date of each product to today 
    with DATES as (select min(PRICE_DATE) START_DATE, sysdate as END_DATE, PRODUCT from PRICES_TEST group by sysdate, PRODUCT) 
    select START_DATE, level - 1 as DAYS, PRODUCT 
    from DATES 
    connect by level < END_DATE - START_DATE + 1 
    order by 3, 2 
) d order by 2, 1 
) DP 
left outer join prices_test pt on pt.price_date = dp.price_date and pt.product = dp.product; 

è in realtà quello che ho veramente bisogno ed è certamente meglio che la mia soluzione noobie.

Grazie a tutti non solo per avermi aiutato con questo, ma anche per avermi presentato funzionalità come "con" e "connetti da".