2010-08-12 36 views
7

Necesito producir la columna "requerida" en la siguiente tabla usando SQL sin usar bucles y subconsultas correlacionadas. ¿Es esto posible en SQL 2008?SQL: ¿Cómo llenar las celdas vacías con el valor de fila anterior?

Date Customer Value Required Rule 
20100101  1  12   12 
20100101  2     0 If no value assign 0 
20100101  3  32   32 
20100101  4  42   42 
20100101  5  15   15 
20100102  1     12 Take last known value 
20100102  2     0 Take last known value 
20100102  3  39   39 
20100102  4     42 Take last known value 
20100102  5  16   16 
20100103  1  13   13 
20100103  2  24   24 
20100103  3     39 Take last known value 
20100103  4     42 Take last known value 
20100103  5  21   21 
20100104  1  14   14 
20100104  2     24 Take last known value 
20100104  3     39 Take last known value 
20100104  4  65   65 
20100104  5  23   23 

Básicamente estoy rellenando las células vacías "Valor" con el último valor conocido para ese cliente. Recuerde que la última fila puede no tener un valor válido, por lo que tendrá que seleccionarla de la fila anterior con un valor válido.

+1

Como activador o como consulta general? – Tobiasopdenbrouw

+0

Consulta general solamente. – Faiz

Respuesta

5

Faiz,

¿qué hay de la siguiente consulta, que hace lo que quieras, por lo que yo lo entiendo. Los comentarios explican cada paso. Eche un vistazo a los CTE en Libros en línea. Este ejemplo podría incluso ser cambiado para utilizar el nuevo comando MERGE para SQL 2008.

/* Test Data & Table */ 
DECLARE @Customers TABLE 
    (Dates datetime, 
    Customer integer, 
    Value integer) 

    INSERT INTO @Customers 
    VALUES ('20100101', 1, 12), 
     ('20100101', 2, NULL), 
     ('20100101', 3, 32), 
     ('20100101', 4, 42), 
     ('20100101', 5, 15), 
     ('20100102', 1, NULL), 
     ('20100102', 2, NULL), 
     ('20100102', 3, 39), 
     ('20100102', 4, NULL), 
     ('20100102', 5, 16), 
     ('20100103', 1, 13), 
     ('20100103', 2, 24), 
     ('20100103', 3, NULL), 
     ('20100103', 4, NULL), 
     ('20100103', 5, 21), 
     ('20100104', 1, 14), 
     ('20100104', 2, NULL), 
     ('20100104', 3, NULL), 
     ('20100104', 4, 65), 
     ('20100104', 5, 23) ; 

/* CustCTE - This gives us a RowNum to allow us to build the recursive CTE CleanCust */ 
WITH CustCTE 
      AS (SELECT Customer, 
         Value, 
         Dates, 
         ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Dates) RowNum 
       FROM  @Customers), 

/* CleanCust - A recursive CTE. This runs down the list of values for each customer, checking the Value column, if it is null it gets the previous non NULL value.*/ 
     CleanCust 
      AS (SELECT Customer, 
         ISNULL(Value, 0) Value, /* Ensure we start with no NULL values for each customer */ 
         Dates, 
         RowNum 
       FROM  CustCte cur 
       WHERE  RowNum = 1 
       UNION ALL 
       SELECT Curr.Customer, 
         ISNULL(Curr.Value, prev.Value) Value, 
         Curr.Dates, 
         Curr.RowNum 
       FROM  CustCte curr 
       INNER JOIN CleanCust prev ON curr.Customer = prev.Customer 
              AND curr.RowNum = prev.RowNum + 1) 

/* Update the base table using the result set from the recursive CTE */ 
    UPDATE trg 
    SET Value = src.Value 
    FROM @Customers trg 
    INNER JOIN CleanCust src ON trg.Customer = src.Customer 
           AND trg.Dates = src.Dates 

/* Display the results */ 
SELECT * FROM @Customers 
+0

Excelente William, ya casi estaba allí. Sabía que CTE sería la única opción (aunque odio los CTE) si tuviera que evitar los bucles, pero quería ver si alguien puede encontrar otros trucos con TSQL. ¡Buen trabajo! Necesito probar esta solución con la existente y ver cómo funciona ... :) Creo que el problema que presenté aquí es genérico y se ve a menudo en proyectos bancarios. Espero que esto ayude a muchos ... – Faiz

+0

¿Puedo preguntar por qué odias los CTE? Son extremadamente útiles y, a menudo, pueden superar muchas otras soluciones. Los CTE recursivos suelen ser problemáticos y costosos, aunque la naturaleza de la recursión no es culpa de CTE. Tendría la opción de usar una actualización peculiar (google para eso), pero es una solución controvertida. –

2

necesito para producir la columna "necesaria" en la tabla siguiente utilizando SQL sin utilizar bucles y consultas sub correlacionados. ¿Es esto posible en SQL 2008?

Imposible. Punto. Posiblemente en CUALQUIER servidor basado en SQL, incluido Oracle.

El principal problema aquí es que descarta bucles Y subconsultas correlacionadas y cualquier forma de recuperar el valor en tiempo de consulta utilizará finalmente otra consulta para encontrar el valor válido (en realidad uno por campo). Así es como funciona SQL. Sí, podría ocultarlos en una función escalar personalizada, pero aún así contendrían una sub consulta lógica.

0

¿Qué tal una unión externa izquierda en la misma tabla donde la fecha es menor que la actual y el valor no está vacío, ordenado por fecha desc (límite 1), devolviendo cero cuando nulo? (No hay servidor disponible para probar en este momento). A menos que esto cuente como una subconsulta ...

+0

¿Alguien puede venir con esta consulta? – Faiz

+1

Parece que Lieven acaba de hacerlo. – pritaeas

1

No estoy seguro de si los siguientes factores consideran sus limitaciones, pero hacen el trabajo.

Los datos de prueba

DECLARE @Customers TABLE (Date DATETIME, Customer INTEGER, Value INTEGER) 

INSERT INTO @Customers VALUES ('20100101', 1, 12 )  
INSERT INTO @Customers VALUES ('20100101', 2, NULL)   
INSERT INTO @Customers VALUES ('20100101', 3, 32 ) 
INSERT INTO @Customers VALUES ('20100101', 4, 42 ) 
INSERT INTO @Customers VALUES ('20100101', 5, 15 ) 
INSERT INTO @Customers VALUES ('20100102', 1, NULL) 
INSERT INTO @Customers VALUES ('20100102', 2, NULL) 
INSERT INTO @Customers VALUES ('20100102', 3, 39 ) 
INSERT INTO @Customers VALUES ('20100102', 4, NULL) 
INSERT INTO @Customers VALUES ('20100102', 5, 16 ) 
INSERT INTO @Customers VALUES ('20100103', 1, 13 ) 
INSERT INTO @Customers VALUES ('20100103', 2, 24 ) 
INSERT INTO @Customers VALUES ('20100103', 3, NULL) 
INSERT INTO @Customers VALUES ('20100103', 4, NULL) 
INSERT INTO @Customers VALUES ('20100103', 5, 21 ) 
INSERT INTO @Customers VALUES ('20100104', 1, 14 ) 
INSERT INTO @Customers VALUES ('20100104', 2, NULL) 
INSERT INTO @Customers VALUES ('20100104', 3, NULL) 
INSERT INTO @Customers VALUES ('20100104', 4, 65 ) 
INSERT INTO @Customers VALUES ('20100104', 5, 23 ) 

consulta

SELECT c.Date 
     , c.Customer 
     , Value = COALESCE(c.Value, cprevious.Value, 0) 
FROM @Customers c 
     INNER JOIN (
      SELECT c.Date 
        , c.Customer 
        , MaxDate = MAX(cdates.Date) 
      FROM @Customers c 
        LEFT OUTER JOIN (
        SELECT Date 
          , Customer 
        FROM @Customers 
       ) cdates ON cdates.Date < c.Date AND cdates.Customer = c.Customer 
      GROUP BY 
        c.Date, c.Customer 
     ) cmax ON cmax.Date = c.Date AND cmax.Customer = c.Customer     
     LEFT OUTER JOIN @Customers cprevious ON cprevious.Date = cmax.MaxDate AND cprevious.Customer = cmax.Customer 
ORDER BY 
     1, 2, 3   

instrucción Update

UPDATE @Customers 
SET  Value = c2.Value 
OUTPUT Inserted.* 
FROM @Customers c 
     INNER JOIN ( 
      SELECT c.Date 
        , c.Customer 
        , Value = COALESCE(c.Value, cprevious.Value, 0) 
      FROM @Customers c 
        INNER JOIN (
        SELECT c.Date 
          , c.Customer 
          , MaxDate = MAX(cdates.Date) 
        FROM @Customers c 
          LEFT OUTER JOIN (
           SELECT Date 
             , Customer 
           FROM @Customers 
          ) cdates ON cdates.Date < c.Date AND cdates.Customer = c.Customer 
        GROUP BY 
          c.Date, c.Customer 
       ) cmax ON cmax.Date = c.Date AND cmax.Customer = c.Customer     
        LEFT OUTER JOIN @Customers cprevious ON cprevious.Date = cmax.MaxDate AND cprevious.Customer = cmax.Customer 
     ) c2 ON c2.Date = c.Date 
       AND c2.Customer = c.Customer 
+0

Gracias por todo el esfuerzo. Pero esto solo manejará casos donde haya un valor válido presente en la fila anterior o en la anterior, ¿verdad? ¿Qué pasa con los casos donde el valor válido está lejos del actual? – Faiz

+0

@Faiz, para esos casos, la consulta se vuelve un poco más compleja :). Requiere que te unas con el máximo (c2.Fecha)

+0

Supongo que devolverá varios registros y la actualización solo considerará el registro más alto. Ahora, cómo ordenarlos para que el registro con el último valor válido aparezca en la parte superior, es la siguiente pregunta :( – Faiz

0

Ésta es la "Last non-null puzzle," y aquí es una de las varias soluciones elegantes:

Si la tabla de "escasa" es SparseTable con columnas Fecha, cliente, Valor continuación:

with C as 
(select *, 
    max(case when Value is not null then [Date] end) 
     over (partition by Customer order by [Date] rows unbounded preceding) as grp 
from SparseTable 
) 
insert into FullTable 
select *, 
    max(Value) over (partition by Customer, grp order by [Date] rows unbounded preceding) as Required 
from C 

Dónde Value no pueden ser ocupadas hacia adelante seguirá siendo NULL, por lo que puede entonces

update FullTable set Required = 0 where Required is null 
Cuestiones relacionadas