2012-04-20 6 views
6

Ich muss etwas wirklich komisches tun, das ist gefälschte Datensätze in einer Ansicht zu schaffen, um die Lücke zwischen veröffentlichten Daten der Produktpreise zu füllen.Duplizieren von Datensätzen, um Lücke zwischen Daten zu füllen

Eigentlich ist mein Szenario ein bisschen komplizierter als das, aber ich habe Produkte/Daten/Preise vereinfacht.

Lassen Sie uns sagen, dass wir diese Tabelle haben:

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); 

Mit diesen Aufzeichnungen:

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); 

Auswählen von Datensätzen zurückkehren wird mir dies:

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      

heute 21. April 2012 Unter der Annahme, Ich brauche eine Ansicht, die jeden Preis jeden Tag un wiederholen soll Bis ein neuer Preis gebucht wird. Gefällt mir:

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      

Irgendwelche Ideen, wie man das macht? I kann nicht wirklich andere Hilfstabellen, Trigger oder PL/SQL-Programmierung verwenden, ich muss dies wirklich tun mit eine Ansicht.

Ich denke, dass dies mit Oracle-Analysen durchgeführt werden kann, aber ich bin nicht vertraut damit. Ich habe versucht, diese http://www.club-oracle.com/articles/analytic-functions-i-introduction-164/ zu lesen, aber ich habe es überhaupt nicht verstanden.

+0

NVM, ich verstehe es jetzt :) Es wird möglich sein, Daten mit der kreativen Nutzung der 'Dual' Tabelle zu generieren. – mellamokb

+0

Ich könnte davon ausgehen, dass Orakel Analytics seine eigene Dimension Tabelle hat, um eine solche Funktion durchzuführen. Könnten Sie einfach Ihre eigene Tabelle für die Datendimension erstellen? –

+0

hier ist ein interessanter Artikel über das Erstellen einer dynamischen Kalenderansicht in Tsql (ja, Sie möchten Orakel, aber vielleicht kann es geändert werden): http://sqlserverpedia.com/blog/sql-server-bloggers/tsql-tuesday18- using-a-rekursive-cte-to-create-a-kalender-tabelle/ –

Antwort

4

Ich glaube, ich habe eine Lösung, eine inkrementelle Ansatz in Richtung des Endergebnisses mit CTE mit:

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 ruft der frühestmöglichen Zeitpunkt in dem Datensatz
  • dates einen Kalender mit Terminen erzeugt aus frühestmögliches Datum bis heute.
  • productdates Kreuz verbindet alle möglichen Produkte mit allen möglichen Daten
  • ranges bestimmt, die an jedem Tag angewendet Preis Datum
  • die letzte Abfrage Links, die auf den tatsächlichen Preis angewendet Preis Datum und filtert Daten für die keine relevanten gibt es Preisdaten über die inner join Zustand

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

+0

Ich habe gerade den Verweis auf die Dual-Tabelle und die Bodenrundung in der zweiten 'mit' entfernt. Sie werden nicht wirklich benötigt. Ansonsten sieht es gut aus! Mein reales Szenario enthält zwei weitere Spalten in der PK (etwas wie Produkt, Marke, Modell) und mehrere andere Info-Spalten (etwas wie Preis, Nettogewicht, Notizen) und es funktioniert immer noch perfekt mit den entsprechenden Änderungen. Vielen Dank. –

6

Sie können eine Reihe Generator Anweisung erstellen diemit 0 Syntax, Kreuz mit den verschiedenen Produkten in Ihrer Tabelle verbunden, und dann äußere, dass zu Ihrer Preistabelle. Der letzte Schliff ist die LAST_VALUE Funktion zu nutzen und IGNORE NULLS den Preis zu wiederholen, bis ein neuer Wert angetroffen wird, und da man einen Blick wollte, mit einer CREATE VIEW Aussage:

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

Mann, du bist ein Meister! Ich habe in einer analytischen Funktion noch nie von 'mit', von 'verbinden' oder von 'Nullstellen ignorieren' gehört. Vielen Dank. Es hat perfekt mit meinem komplizierteren Szenario funktioniert. –

+0

@LeoHolanda Ausgezeichnet! Viel Glück. – Wolf

+0

Hallo @Wolf, obwohl deine Antwort diejenige war, die mich mehr lernen ließ als die anderen, ist Mellamokbs Antwort diejenige, die tatsächlich die gewünschten Ergebnisse hervorbringt, daher verdient er die Flagge "akzeptierte Antwort". Vielen Dank euch allen, die mir dabei geholfen haben. –

4

Ich habe ein paar Änderungen an Wolfs ausgezeichnete Antwort .

Ich ersetzte die Unterabfrage Factoring (WITH) durch eine regelmäßige Unterabfrage in der connect by. Dies macht den Code ein wenig einfacher.(Obwohl diese Art von Code auf den ersten Blick irgendwie komisch aussieht, kann es hier keinen großen Gewinn geben.)

Am wichtigsten ist, dass ich eine Partitions-Outer-Join statt einer Cross-Join und Outer-Join verwendet habe. Partition Outer Joins sind auch irgendwie seltsam, aber sie sind für genau diese Art von Situation gedacht. Dies macht den Code einfacher und sollte die Leistung verbessern.

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

Das ist eine nette Verbesserung. Die WITH-Klausel war ein Artefakt von etwas anderem SQL, das ich hatte, aber ich vergesse immer über äußere Verbindungen der Partition. Gute Arbeit. – Wolf

1

Ich habe erkannt, dass @Wolf und @jonearles Verbesserungen brauchte ich nicht die genauen Ergebnisse liefern, da der Zeilengenerator alle Daten aufzulisten werden die Bereiche von Produkt nicht erzeugen. Wenn der erste Preis von Produkt A später ist als irgendein Preis von Produkt B, muss das erste aufgeführte Datum von Produkt A immer noch dasselbe sein. Aber sie hat mir wirklich geholfen weiter zu arbeiten und die erwarteten Ergebnisse erhalten:

Ich begann mit von diesem @ Wolf Datum des Bereichswahl Wechsel:

select min(price_date) beg_date, sysdate end_date from prices_test 

dazu:

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

Aber irgendwie Die Anzahl der Zeilen pro Produkt wächst exponentiell für jedes Level. Ich habe gerade ein distinct in der utter-Abfrage hinzugefügt. Die schließlich wählen, war dies:

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; 

@Mellamokb Lösung ist eigentlich das, was ich wirklich brauchen, und ist auf jeden Fall besser als meine noobie Lösung.

Vielen Dank, dass Sie mir nicht nur dabei geholfen haben, sondern mir auch Funktionen wie "mit" und "verbinden" vorgestellt haben.

Verwandte Themen