2012-01-23 10 views
11

Aquí hay una pregunta que me he estado haciendo un desastre. Digamos que tengo una tabla que tiene una serie de marcas de tiempo y un número de parte como la clave principal. La tabla almacena cambios incrementales, lo que significa que para cada marca de tiempo, si un campo cambia, ese cambio se registra. Si el campo no cambia, entonces para la nueva marca de tiempo es NULL. Aquí está la idea básica.Base de datos: Seleccione las últimas entradas no nulas

part | timestamp | x-pos | y-pos | status 
------+-----------+-------+-------+-------- 
a5 |  151 |  5 | 15 |  g 
a5 |  153 | NULL | 17 | NULL 

(part, timestamp) es la clave principal. Los NULL s en el segundo registro indican valores que no han cambiado desde el primer registro.

Lo que quiero poder hacer es seleccionar los valores más recientes para cada campo agrupado por la parte. Por ejemplo, dadas las entradas anteriores, los resultados serán 153,5,17, g para la parte a5.

Por el momento, tengo esta consulta hackeada.

((SELECT x-pos FROM part_changes WHERE x-pos IS NOT NULL 
    ORDER BY timestamp DESC 
    LIMIT 1) 

    UNION 

    (SELECT y-pos FROM part_changesWHERE y-pos IS NOT NULL 
    ORDER BY timestamp DESC 
    LIMIT 1) 

    UNION 

    (SELECT status FROM part_changes WHERE status IS NOT NULL 
    ORDER BY timestamp DESC 
    LIMIT 1)) 

Pero esto devuelve una sola columna, lo que significa que puedo usar un grupo para organizar.

Tiene que haber una forma más elegante de hacer las cosas, como usar COALESCE o IS NULL de una manera creativa. Pero estoy atascado y no puedo entenderlo. Alguien tiene una idea?

Y no, no puedo cambiar la estructura de la base de datos.

EDITAR: ruakh tiene la idea correcta. El único problema ahora es agrupar por partes. Parece que no puedo evitar el LIMIT 1 para agrupar por varias partes. ¿Algunas ideas?

mdahlman, no estoy muy familiarizado con las funciones analíticas en postgresql. Entonces, si esa solución sería más fácil que una consulta compleja, entonces, por supuesto, publique su idea.

EDIT 2: Gracias a todos por la ayuda. Creo que tengo una buena comprensión de lo que tengo que hacer.

+1

Debe especificar si las funciones analíticas están permitidas. Con ellos, la respuesta debería ser simple. Sin ellos ... va a ser difícil. – mdahlman

+0

¿hay un límite de cuántos valores nulos pueden ocurrir? Si hubiera un límite, sería una solución con algunas combinaciones de la izquierda ... no es bueno, pero podría hacerse;) – rauschen

+0

No creo que haya un límite. De hecho, uno de los campos tiene la gran mayoría (algo así como el 99%) de los registros como NULL. –

Respuesta

5

En lugar de utilizar un UNION, parece que realmente desea subconsultas en la lista de campos. Es decir, en lugar de (SELECT ...) UNION (SELECT ...) UNION (SELECT ...), quiere SELECT (SELECT ...), (SELECT ...), (SELECT ...).


Por ejemplo:

SELECT part, 
     (SELECT x_pos 
      FROM part_changes 
      WHERE part = pc.part 
      AND x_pos IS NOT NULL 
      ORDER 
      BY timestamp DESC 
      LIMIT 1 
     ) AS x_pos, 
     (SELECT y_pos 
      FROM part_changes 
      WHERE part = pc.part 
      AND y_pos IS NOT NULL 
      ORDER 
      BY timestamp DESC 
      LIMIT 1 
     ) AS y_pos, 
     (SELECT status 
      FROM part_changes 
      WHERE part = pc.part 
      AND status IS NOT NULL 
      ORDER 
      BY timestamp DESC 
      LIMIT 1 
     ) AS status 
    FROM (SELECT DISTINCT 
       part 
      FROM part_changes 
     ) AS pc 
; 

Pero en este momento me gustaría realmente que no escribe un procedimiento almacenado.


Alternativamente:

SELECT DISTINCT 
     part, 
     FIRST_VALUE(x_pos) OVER 
     (PARTITION BY part 
       ORDER BY CASE WHEN x_pos IS NULL 
          THEN NULL 
          ELSE TIMESTAMP 
         END DESC NULLS LAST 
     ) AS x_pos, 
     FIRST_VALUE(y_pos) OVER 
     (PARTITION BY part 
       ORDER BY CASE WHEN y_pos IS NULL 
          THEN NULL 
          ELSE TIMESTAMP 
         END DESC NULLS LAST 
     ) AS y_pos, 
     FIRST_VALUE(status) OVER 
     (PARTITION BY part 
       ORDER BY CASE WHEN status IS NULL 
          THEN NULL 
          ELSE TIMESTAMP 
         END DESC NULLS LAST 
     ) AS status 
    FROM part_changes 
; 
+0

Eso parece haber resuelto un problema. Gracias. Sin embargo, no estoy seguro de dónde iría el 'grupo por' para agrupar por el número de parte. ¿Algunas ideas? –

+0

@BatMasterson: Ese tipo de detalle dependerá del dialecto de SQL. De su uso de 'ORDER BY ... LIMIT 1', supongo que está apuntando a MySQL; ¿Es eso correcto? (De ser así, o si no, debe editar las etiquetas de su pregunta.) – ruakh

+0

Ah, buena pregunta. Esto es postgreSQL. Actualizaré la publicación. –

0

ruakh es correcto. Alternativa: escriba un agregado definido por el usuario usando SQL-CLR. Este agregado puede ejecutarse en la parte superior-superior de las filas y recordar el primer valor no nulo de cada columna.

También puede hacerlo en la aplicación. Haga que su programa solicite filas en orden en lotes de, por ejemplo, 10 filas. Agregue estos lotes como se describe arriba. Si después del lote actual se hace una columna nula, se obtiene el siguiente lote.

+0

SQL-CLR no ayudará al OP con PostgreSQL. Sin embargo, se puede hacer con elementos incorporados, y hay un ejemplo de lo que describes exactamente ("PRIMERO") [en la wiki de PostgreSQL] (http://wiki.postgresql.org/wiki/First_%28aggregate%29) –

+0

Desearía que SQL Server tuviera FIRST. – usr

2

Para un solo parte esto debería darle una respuesta.Gracias a ruakh

Pero no me gusta esta versión ..

SELECT 
    (SELECT timestamp FROM part_changes WHERE part = $part 
    ORDER BY timestamp DESC 
    LIMIT 1) as timestamp, 

    (SELECT x-pos FROM part_changes WHERE part = $part and x-pos IS NOT NULL 
    ORDER BY timestamp DESC 
    LIMIT 1) as xpos, 

    (SELECT y-pos FROM part_changes WHERE part = $part and y-pos IS NOT NULL 
    ORDER BY timestamp DESC 
    LIMIT 1) as ypos, 

    (SELECT status FROM part_changes WHERE part = $part and status IS NOT NULL 
    ORDER BY timestamp DESC 
    LIMIT 1)) as status 
+0

Estoy de acuerdo, hacerlo recursivamente para cada parte es un poco difícil. – mvrak

+0

Supongo que simplemente podría escribir una función que tome el número de pieza como argumento, y luego usar esto. Gracias. –

1

lista de marcas de tiempo relevantes:

select max timestamp from part_changes where x_POS is not null group by part 

Usted puede hacer esto un punto de vista: le permite llamar a este Vista1

SELECT part_changes.part, part_changes.x-pos 
FROM part_changes left join view1 on part_changes.part = view1.part 
WHERE x-pos IS NOT NULL 
AND part_changes.timestamp = view1.timestamp 
GROUP BY part_changes.part 

¿A dónde voy a ir? Eso debería darle la lista completa de x-pos.

+0

idea interesante. Pensé en utilizar la calidad de eliminación nula de ciertas combinaciones para mi ventaja, pero no sabía por dónde empezar. Voy a experimentar con esto un poco. Mi única preocupación es que en mi caso, en realidad hay muchas más columnas. El ejemplo en la publicación fue una versión simplificada. ¿Tantas uniones tienen un problema con la memoria? –

+0

Si tiene unos pocos cientos podría ... También edité mi publicación para incluir al grupo. Ahora que lo pienso, creo que esto te dará exactamente lo que quieres para x_pos. ¡SIN PRUEBA! – mvrak

+0

Creo que solo hay unos 15 campos y hay miles de registros por día. Voy a tratar de salir. Esto podría ser justo lo que necesito. –

Cuestiones relacionadas