2012-04-20 14 views
6

Necesito hacer algo realmente extraño, que es crear registros falsos en una vista para llenar el espacio entre las fechas publicadas de los precios de los productos.Duplicar registros para llenar la brecha entre las fechas

En realidad, mi escenario es un poco más complicado que eso, pero me he simplificado a productos/fechas/precios.

Supongamos que tenemos la siguiente tabla:

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 estos registros:

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

selección me volverán esto:

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      

Suponiendo que hoy es Abr 21 de 2012, Necesito una vista que deberá repetir cada precio todos los días hasta que se publique un nuevo precio. De esta manera:

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      

¿Alguna idea de cómo hacer eso? I no puedo usar realmente usar otras tablas auxiliares, triggers ni programación PL/SQL, realmente necesito hacer esto usando a view.

Creo que esto se puede hacer utilizando Oracle Analytics, pero no estoy familiarizado con eso. Traté de leer esto http://www.club-oracle.com/articles/analytic-functions-i-introduction-164/ pero no lo conseguí en absoluto.

+0

NVM, lo entiendo ahora :) Será posible generar datos con el uso creativo de la tabla 'Dual'. – mellamokb

+0

Podría suponer que Oracle Analytics tiene su propia tabla de dimensiones de fecha para realizar dicha función. ¿Podrías crear tu propia tabla de dimensiones de fecha? –

+0

aquí hay un artículo interesante sobre la creación de una vista de calandrado dinámico en tsql (sí, quiere un oráculo, pero puede ser alterado): http://sqlserverpedia.com/blog/sql-server-bloggers/tsql-tuesday-18- using-a-recursive-cte-to-create-a-calendar-table/ –

Respuesta

4

creo que tengo una solución utilizando un enfoque gradual hacia el resultado final con CTE:

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 fecha más temprana posible del conjunto de datos
  • dates genera un calendario de fechas de la fecha más temprana posible hasta hoy.
  • productdates cruz une a todos los productos posibles con todas las fechas posibles
  • ranges determina que datan de precios aplicado en cada fecha
  • los enlaces consulta final que datan de precios aplicado al precio actual y filtra las fechas para las que no hay relevante fechas de precios a través de la condición inner join

demostración: http://www.sqlfiddle.com/#!4/e528f/126

+0

Acabo de eliminar la referencia a la tabla doble y el redondeo del suelo en el segundo 'con' seleccionar. No son realmente necesarios. ¡Aparte de eso, se ve muy bien! Mi escenario real contiene 2 columnas más en PK (algo así como producto, marca, modelo) y varias otras columnas de información (algo como precio, peso neto, notas) y todavía funciona perfectamente con los cambios apropiados. Muchas gracias. –

6

Usted puede crear una declaración generador de fila utilizando elSintaxis 0, cruz se unió con los distintos productos en su tabla, y luego unir eso externo a su tabla de precios. El toque final es utilizar la función LAST_VALUE y IGNORE NULLS repetir el precio hasta que se encuentre un nuevo valor, y puesto que quería una vista, con una declaración CREATE VIEW:

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

¡Hombre, eres un maestro! Nunca oí hablar de 'con', 'conectar por' ni 'ignorar nulos' en una función analítica. Muchas gracias. Funcionó perfectamente con mi escenario más complicado. –

+0

@LeoHolanda ¡Excelente! Buena suerte. – Wolf

+0

Hola @Wolf, aunque tu respuesta fue la que me hizo aprender más que los demás, la respuesta de mellamokb es la que realmente produce los resultados deseados, por lo tanto, merece la bandera de "respuesta aceptada". Muchas gracias a todos ustedes que me ayudaron con esto. –

4

me hizo algunos cambios a la excelente respuesta de Lobo .

Reemplacé la factorización de subconsulta (WITH) con una subconsulta regular en el connect by. Esto hace que el código sea un poco más simple.(Aunque este tipo de código parece extraño al principio de cualquier manera, entonces puede no haber una gran ganancia aquí.)

Lo más significativo es que utilicé una unión externa de partición en lugar de una combinación cruzada y una combinación externa. Las uniones externas de partición también son un tanto extrañas, pero están pensadas exactamente para este tipo de situación. Esto hace que el código sea más simple y debería mejorar el rendimiento.

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

Esa es una buena mejora. La cláusula WITH era un artefacto de un poco de otro SQL que tenía, pero sigo olvidándome de las uniones externas de partición. Buen trabajo. – Wolf

1

me di cuenta de que @Wolf y @jonearles mejoras no devuelven los resultados exactos que necesitaba porque el generador de fila a la lista todas las fechas no generará las gamas de producto. Si el primer precio del producto A es posterior a cualquier precio del producto B, la primera fecha indicada del producto A debe ser la misma. Pero lo que realmente me ayudaron a seguir trabajando y conseguir los resultados esperados:

empecé con el cambio de selector de período @ del lobo de esto:

select min(price_date) beg_date, sysdate end_date from prices_test 

a esto:

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

Pero, de alguna manera , el número de filas por producto crece exponencialmente de manera repetida para cada nivel. Acabo de agregar una distinta en la consulta externa. Por último, seleccione la era esto: solución @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; 

es en realidad lo que realmente necesita y es sin duda mejor que mi solución noobie.

Gracias a todos no solo por ayudarme con esto sino también por presentarme funciones como "con" y "conectar por".

Cuestiones relacionadas