2012-04-19 13 views
5

Dadas 2 o más filas que se seleccionan para fusionarse, una de ellas se identifica como la fila de la plantilla. Las otras filas deben fusionar sus datos en cualquier columna de valores nulos que tenga la plantilla.Intentando combinar filas en una fila con ciertas condiciones

Ejemplo datos:

Id Name  Address   City   State Active Email    Date 
1 Acme1 NULL    NULL   NULL NULL [email protected]  3/1/2011 
2 Acme1 1234 Abc Rd  Springfield OR  0  [email protected] 1/12/2012 
3 Acme2 NULL    NULL   NULL 1  [email protected] 4/19/2012 

decir que un usuario ha elegido fila con Id 1 como la fila de plantilla, y filas con Ids 2 y 3 son a combinar en la fila 1 y luego eliminado. Las columnas con valores nulos en la fila Id 1 deben rellenarse (si existe) con el valor no nulo más reciente (ver Fecha), y los valores no nulos ya presentes en la fila Id 1 se deben dejar como están. El resultado de esta consulta en los datos anteriores debería ser exactamente esto:

Id Name  Address   City   State Active Email    Date 
1 Acme1 1234 Abc Road Springfield OR  1  [email protected]  3/1/2011 

en cuenta que el valor activo es 1, y no porque la fila 0 3 Id tenía la fecha más reciente.

P.S. Además, ¿hay alguna forma de hacerlo sin definir explícitamente/saber de antemano cuáles son todos los nombres de las columnas? La tabla real con la que estoy trabajando tiene una tonelada de columnas, y las nuevas se agregan todo el tiempo. ¿Hay alguna manera de buscar todos los nombres de columna en la tabla y luego usar esa subconsulta o temptable para hacer el trabajo?

+0

¿El correo electrónico también debería ser [email protected], o se considera que Acme2 está fuera de los límites de la fusión? –

+0

¿Cómo identifica el grupo de datos? Supongo que quieres fusionar dobletes de datos. Entonces, ¿cómo identifica un grupo de datos en miles o más de registros en su tabla? Si sabes eso, entonces puedes escribir un procedimiento almacenado para hacer esto. Si es necesario, puedo escribir un SP de muestra como respuesta, luego – YvesR

+0

Hola Russell Fox, no, el correo electrónico debe permanecer [email protected] porque ya estaba presente en la fila Id 1. Solo las columnas de valor nulo en Id 1 deberían modificarse. – noahC

Respuesta

2

Puede hacerlo ordenando filas primero por indicador de plantilla, luego por fecha desc. La fila de la plantilla siempre debe ser la última. A cada fila se le asigna un número en ese orden. Usando max() estamos encontrando la primera celda ocupada (en orden descendente de números). Luego seleccionamos columnas de filas que coincidan con esos máximos.

; with rows as (
    select test.*, 
    -- Template row must be last - how do you decide which one is template row? 
    -- In this case template row is the one with id = 1 
    row_number() over (order by case when id = 1 then 1 else 0 end, 
         date) rn 
    from test 
    -- Your list of rows to merge goes here 
    -- where id in (...) 
), 
-- Finding first occupied row per column 
positions as (
    select 
    max (case when Name is not null then rn else 0 end) NamePosition, 
    max (case when Address is not null then rn else 0 end) AddressPosition, 
    max (case when City is not null then rn else 0 end) CityPosition, 
    max (case when State is not null then rn else 0 end) StatePosition, 
    max (case when Active is not null then rn else 0 end) ActivePosition, 
    max (case when Email is not null then rn else 0 end) EmailPosition, 
    max (case when Date is not null then rn else 0 end) DatePosition 
    from rows 
) 
-- Finally join this columns in one row 
select 
    (select Name from rows cross join Positions where rn = NamePosition) name, 
    (select Address from rows cross join Positions where rn = AddressPosition) Address, 
    (select City from rows cross join Positions where rn = CityPosition) City, 
    (select State from rows cross join Positions where rn = StatePosition) State, 
    (select Active from rows cross join Positions where rn = ActivePosition) Active, 
    (select Email from rows cross join Positions where rn = EmailPosition) Email, 
    (select Date from rows cross join Positions where rn = DatePosition) Date 
from test 
-- Any id will suffice, or even DISTINCT 
where id = 1 

You might check it at Sql Fiddle.

EDIT:

Cruz se une en la última sección en realidad podría ser combinaciones internas en rows.rn = xxxPosition. Funciona de esta manera, pero cambiar a una combinación interna sería una mejora.

1

No es tan complicado.

Al principio .. DECLARE @templateID INT = 1 .so que usted puede recordar qué fila se trata como plantilla ..

Ahora Buscar NOT NULL valores (excluir fila plantilla). La forma más sencilla es utilizar TOP 1 subconsultas para cada columna:

SELECT 
(SELECT TOP 1 Name FROM DataTab WHERE Name IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS LatestName, 
(SELECT TOP 1 Address FROM DataTab WHERE Address IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS AddressName 
-- add more columns here 

Wrap arriba en CTE (expresión de tabla común) para que tenga buena entrada para su UDPATE ..

WITH Latest_CTE (CTE_LatestName, CTE_AddressName) -- add more columns here; I like CTE prefix to distinguish source columns from target columns.. 
AS 
-- Define the CTE query. 
(
    SELECT 
    (SELECT TOP 1 Name FROM DataTab WHERE Name IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS LatestName, 
    (SELECT TOP 1 Address FROM DataTab WHERE Address IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS AddressName 
    -- add more columns here 
) 
UPDATE 
<update statement here (below)> 

Ahora, no inteligente UPDATE de la fila de plantilla mediante ISNULL - que actuará como actualización condicional - actualización sólo si la columna de destino es nulo

WITH 
<common expression statement here (above)> 
UPDATE DataTab 
SET 
Name = ISNULL(Name, CTE_LatestName), -- if Name is null then set Name to CTE_LatestName else keep Name as Name 
Address = ISNULL(Address, CTE_LatestAddress) 
-- add more columns here.. 
WHERE ID = @templateID 

Y la última tarea es borrar filas otra fila de la plantilla ..

DELETE FROM DataTab WHERE NOT ID = @templateID 

¿Borrar?

+0

Creo que tiene sentido, y lo intentaré, pero ¿hay alguna manera posible de hacerlo sin definir explícitamente/saber de antemano cuáles son todos los nombres de las columnas? La tabla real con la que estoy trabajando tiene una tonelada de columnas, y las nuevas se agregan todo el tiempo. ¿Hay alguna manera de buscar todos los nombres de columna en la tabla y luego usar esa subconsulta o temptable para hacer el trabajo? – noahC

+0

Utilice la vista de catálogo sys.columns para obtener todos los nombres de columnas de su tabla (excluya la columna ID de este conjunto). Ahora 1) uso SQL dinámico: creo que es un dolor de cabeza y el rendimiento baja. O bien, 2) nombres de columna de salida de sys.columns en archivo de texto, luego use el programa AWK/GAWK para generar el script de destino usando "plantilla". Guarde su programa GAWK para uso futuro (reconstruya/actualice su secuencia de comandos SQL). – huhu78

+0

Vea mis soluciones GAWK en otro problema SQL para obtener la idea: http://stackoverflow.com/a/10122169/1280816 Estoy seguro de que tiene muchas columnas, pero el número de columnas en el producto final será estático . Es mejor hacer un generador de scripts SQL externo que perder rendimiento en cada ejecución de consulta y bucle a través de nombres de columna. – huhu78

1

Para las columnas dinámicas, debe escribir una solución utilizando SQL dinámico.

Puede consultar sys.columns y sys.tables para obtener la lista de columnas que necesita, luego desea retroceder una vez para cada columna nula encontrando la primera fila no nula para esa columna y actualizando su fila de salida para esa columna Una vez que llega a 0 en el ciclo, tiene una fila completa que luego puede mostrar al usuario.

+0

Gracias por su respuesta; eso suena exactamente a lo que tengo que hacer, pero me está resultando difícil tener una coneptualización concreta sin ver un código. Si no es demasiado problema, ¿podría arrojar algún código aquí para que yo pueda entender mejor? Muchas gracias. – noahC

1

Debo prestar atención a las fechas de publicación. En cualquier caso, aquí hay una solución que usa SQL dinámico para construir una declaración de actualización. Debería darte algo para construir, de todos modos.

Hay un código adicional para validar los resultados en el camino, pero traté de hacer un comentario que hiciera evidente ese código no vital.

CREATE TABLE 
dbo.Dummy 
    (
    [ID] int , 
    [Name] varchar(30), 
    [Address] varchar(40) null, 
    [City] varchar(30) NULL, 
    [State] varchar(2) NULL, 
    [Active] tinyint NULL, 
    [Email] varchar(30) NULL, 
    [Date] date NULL 
    ); 
-- 
INSERT dbo.Dummy 
VALUES 
(
    1, 'Acme1', NULL, NULL, NULL, NULL, '[email protected]', '3/1/2011' 
) 
, 
(
    2, 'Acme1', '1234 Abc Rd', 'Springfield', 'OR', 0, '[email protected]', '1/12/2012' 
) 
, 
(
    3, 'Acme2', NULL, NULL, NULL, 1, '[email protected]', '4/19/2012' 
); 
DECLARE 
    @TableName nvarchar(128) = 'Dummy', 
    @TemplateID int = 1, 
    @SetStmtList nvarchar(max) = '', 
    @LoopCounter int = 0, 
    @ColumnCount int = 0, 
    @SQL nvarchar(max) = '' 
    ; 
-- 
--Create a table to hold the column names 
DECLARE  
    @ColumnList table 
     (
     ColumnID tinyint IDENTITY, 
     ColumnName nvarchar(128) 
     ); 
-- 
--Get the column names 
INSERT @ColumnList 
(
    ColumnName 
) 
    SELECT 
     c.name 
    FROM 
     sys.columns AS c 
     JOIN 
     sys.tables AS t 
      ON 
       t.object_id = c.object_id 
    WHERE 
     t.name = @TableName; 
-- 
--Create loop boundaries to build out the SQL statement 
SELECT 
    @ColumnCount = MAX(l.ColumnID), 
    @LoopCounter = MIN (l.ColumnID) 
FROM 
    @ColumnList AS l; 
-- 
--Loop over the column names 
WHILE @LoopCounter <= @ColumnCount 
BEGIN 
    --Dynamically construct SET statements for each column except ID (See the WHERE clause) 
    SELECT 
     @SetStmtList = @SetStmtList + ',' + l.ColumnName + ' =COALESCE(' + l.ColumnName + ', (SELECT TOP 1 ' + l.ColumnName + ' FROM ' + @TableName + ' WHERE ' + l.ColumnName + ' IS NOT NULL AND ID <> ' + CAST(@TemplateID AS NVARCHAR(MAX)) + ' ORDER BY Date DESC)) ' 
    FROM 
     @ColumnList AS l 
    WHERE 
     l.ColumnID = @LoopCounter 
     AND 
     l.ColumnName <> 'ID'; 
-- 
    SELECT 
     @LoopCounter = @LoopCounter + 1; 
-- 
END; 

--TESTING - Validate the initial table values 
SELECT * FROM dbo.Dummy ; 
-- 
--Get rid of the leading common in the SetStmtList 
SET @SetStmtList = SUBSTRING(@SetStmtList, 2, LEN(@SetStmtList) - 1); 
--Build out the rest of the UPDATE statement 
SET @SQL = 'UPDATE ' + @TableName + ' SET ' + @SetStmtList + ' WHERE ID = ' + CAST(@TemplateID AS NVARCHAR(MAX)) 
--Then execute the update 
EXEC sys.sp_executesql 
    @SQL; 
-- 
--TESTING - Validate the updated table values 
SELECT * FROM dbo.Dummy ; 
-- 
--Build out the DELETE statement 
SET @SQL = 'DELETE FROM ' + @TableName + ' WHERE ID <> ' + CAST(@TemplateID AS NVARCHAR(MAX)) 
--Execute the DELETE 
EXEC sys.sp_executesql 
    @SQL; 
-- 
--TESTING - Validate the final table values 
SELECT * FROM dbo.Dummy; 
-- 
DROP TABLE dbo.Dummy; 
Cuestiones relacionadas