none
Eliminar duplicados con ciertas condiciones RRS feed

  • Pregunta

  • Hola:

    Tengo una consulta en la base de datos que estoy manejando de tal manera que quiero obtener lo siguiente:

    Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion

    Mi problema es que para cada fecha, tengo varios registros donde unas veces el bit_informacion vale 'false' y otras veces vale 'true' y la columna Nombre se puede repetir. Pues quiero obtener para cada fecha, ordenados por el id_columna, todos los registros en los que valga bit_informacion el valor 'false' incluso aunque el valor de la columna Nombre se repita pero al mismo tiempo, cuando la columna bit_información valga 'true' me saque únicamente el primer registro que encuentre del campo Nombre. Es decir, que me elimine los duplicados de la columna Nombre cuando bit_información valga 'true'.

    No sé si me he explicado bien. Soy relativamente nuevo en esto del SQL y me estoy perdiendo con esta consulta. Sé que existe la opción del DISTINCT, pero aquí no me vale porque Id_columna es clave primaria y no se repite nunca, aparte del hecho de que cuando bit_informacion valga 'false' no me importa que salgan duplicados, pero sí cuando valga 'true'.

    Muchas gracias por adelantado.
    jueves, 14 de mayo de 2009 6:08

Respuestas

  • Hola.

    Si te he entendido bien, serían dos partes, por un lado cuando Bit_Informacion vale "false" (todos) y por otro cuando Bit_Información es "true" (sólo el primer nombre, ya que habrá duplicados). Por "true" o "false" entenderé 1 ó 0. Si son otros valorse, los deberás cambiar.

    La primera parte, con el bit a 0 (false):

    select Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion from MiTabla where Bit_Informacion = 0

    La segunda es un pelín más compleja. Para obtener el primero de los nombres, la consulta sería:

    select min(Id_columna) as Id_Columna, Nombre from MiTabla where Bit_Informacion = 1 group by Nombre

    Y eso lo cruzas con la tabla otra vez para obtener el resto de campos:

    select A.Id_columna, A.Fecha, A.Nombre, A.Columna_A, A.Columna_B, A.Bit_informacion
    from (select min(Id_columna) as Id_Columna, Nombre from MiTabla where Bit_Informacion = 1 group by Nombre) G inner join
    MiTabla A on G.Id_Columna = G.Id_Columna

    Ahora, unes ambas consultas:
    select Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion from MiTabla where Bit_Informacion = 0
    union all
    select A.Id_columna, A.Fecha, A.Nombre, A.Columna_A, A.Columna_B, A.Bit_informacion
    from (select min(Id_columna) as Id_Columna, Nombre from MiTabla where Bit_Informacion = 1 group by Nombre) G inner join
    MiTabla A on G.Id_Columna = G.Id_Columna
    
    Me despita un poco lo de "para cada fecha". No sé si es un filtro o una agrupación adicional. Lo he obviado, si no te vale esto que te paso, ¿podrías detallar un poco más el papel de la fecha en la consulta?

    Alberto López Grande.


    jueves, 14 de mayo de 2009 7:19
    Moderador
  • Dagomar,

    Si estas usando sql server 2005, otra forma seria usando la funcion dense_rank. Chequea los BOL para mas info sobre este tipo de funciones (ranking function).

    with r_set
    as
    (
    select
        Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion,
        dense_rank() over(
        PARTITION BY Fecha, Nombre
        ORDER BY CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END, CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END) as drnk
    from
        tu_tabla
    )
    select 
        Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion
    from
        r_set
    where
        drnk <= 2
    order by
        Fecha,
        Nombre,
        Bit_informacion,
        Id_columna;
    go


    Para ayudar el desempenio de este query, seria conveniente tener in indice por (Fecha, Nombre, Bit_informacion, Id_columna).


    Te pongo un ejemplo de lo que este hace.

    WITH r_set
    AS
    (
    SELECT
    	Id_columna, 
    	Fecha, 
    	Nombre, 
    	Bit_informacion,
    	DENSE_RANK() OVER(PARTITION BY Fecha, Nombre ORDER BY CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END, CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END) AS drnk
    FROM
    	(
    	SELECT 1, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 2, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 3, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 4, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 5, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 6, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 7, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 8, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 9, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 10, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 11, '20090102', 'Luis', 0 UNION ALL
    	SELECT 12, '20090102', 'Luis', 0 UNION ALL
    	SELECT 13, '20090102', 'Luis', 0 UNION ALL
    	SELECT 14, '20090102', 'Luis', 1 UNION ALL
    	SELECT 15, '20090102', 'Luis', 1 UNION ALL
    	SELECT 16, '20090102', 'Luis', 1	
    	) AS t(Id_columna, Fecha, Nombre, Bit_informacion)
    )
    SELECT *
    FROM r_set
    ORDER BY Fecha, Nombre, drnk;
    GO

    Por cada [Fecha] y [Nombre], si [Bit_informacion] = 0 entonces pone esa fila en un grupo que le llame 1 y si no lo pone en el grupo 2, para su ordenamiento. Cuando ordenamos dentro de la clausula over, los de el grupo 1 (bit_information = 0) seran primeros que los de el grupo 2 y estos seran tambien ordenados por una constante (select 0). Si la fila cae en el grupo 2, entonces ordeno ese subgrupo por la columna [Id_columna]. La funcion dense_rank asigna el mismo valor a todas la filas con el mismo rango (nivel de ordenamiento), en este caso todas las filas en el grupo 1 tendran el mismo valor de rango 1. Como la primera fila de el grupo 2 cambia de rango, entonces la funcion asigna el sgte valor, que seria 2 (esta funcion no deja huecos en la numeracion), e incrementara el valor de rango de las filas subsequentes en 1 porque se supone que aunque estan en el grupo 2, estas tiene diferente [Id_Columna]. Recuerda, esto se hace por cada combinacion de (fecha,  nombre), osea que los valores se reinicializan por cada combinacion de estas dos columnas, que son las que definen la particion. Veamos el case de "Ivan" en la fecha "20090101".


    Id_columna Fecha       Nombre Bit_informacion drnk
    5                20090101 Ivan      0                     1
    6                20090101 Ivan      0                     1
    7                20090101 Ivan      0                     1
    8                20090101 Ivan      1                     2
    9                20090101 Ivan      1                     3
    10              20090101 Ivan      1                     4


    Si agregamos los valores por los que se ordena para asignar el valor de rango dentro de cada particion, entonces podras entender un poquito mejor.


    WITH r_set
    AS
    (
    SELECT
    	Id_columna, 
    	Fecha, 
    	Nombre, 
    	Bit_informacion,
    	CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END AS [Primer valor por el que se ordena], 
    	CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END AS [Segundo valor por el que se ordena],
    	DENSE_RANK() OVER(PARTITION BY Fecha, Nombre ORDER BY CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END, CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END) AS drnk
    FROM
    	(
    	SELECT 1, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 2, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 3, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 4, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 5, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 6, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 7, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 8, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 9, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 10, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 11, '20090102', 'Luis', 0 UNION ALL
    	SELECT 12, '20090102', 'Luis', 0 UNION ALL
    	SELECT 13, '20090102', 'Luis', 0 UNION ALL
    	SELECT 14, '20090102', 'Luis', 1 UNION ALL
    	SELECT 15, '20090102', 'Luis', 1 UNION ALL
    	SELECT 16, '20090102', 'Luis', 1	
    	) AS t(Id_columna, Fecha, Nombre, Bit_informacion)
    )
    SELECT *
    FROM r_set
    ORDER BY Fecha, Nombre, drnk
    GO

    Espero les haya sido de ayuda.


    AMB
       
    • Marcado como respuesta Dagomar lunes, 18 de mayo de 2009 5:39
    jueves, 14 de mayo de 2009 13:00

Todas las respuestas

  • Hola.

    Si te he entendido bien, serían dos partes, por un lado cuando Bit_Informacion vale "false" (todos) y por otro cuando Bit_Información es "true" (sólo el primer nombre, ya que habrá duplicados). Por "true" o "false" entenderé 1 ó 0. Si son otros valorse, los deberás cambiar.

    La primera parte, con el bit a 0 (false):

    select Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion from MiTabla where Bit_Informacion = 0

    La segunda es un pelín más compleja. Para obtener el primero de los nombres, la consulta sería:

    select min(Id_columna) as Id_Columna, Nombre from MiTabla where Bit_Informacion = 1 group by Nombre

    Y eso lo cruzas con la tabla otra vez para obtener el resto de campos:

    select A.Id_columna, A.Fecha, A.Nombre, A.Columna_A, A.Columna_B, A.Bit_informacion
    from (select min(Id_columna) as Id_Columna, Nombre from MiTabla where Bit_Informacion = 1 group by Nombre) G inner join
    MiTabla A on G.Id_Columna = G.Id_Columna

    Ahora, unes ambas consultas:
    select Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion from MiTabla where Bit_Informacion = 0
    union all
    select A.Id_columna, A.Fecha, A.Nombre, A.Columna_A, A.Columna_B, A.Bit_informacion
    from (select min(Id_columna) as Id_Columna, Nombre from MiTabla where Bit_Informacion = 1 group by Nombre) G inner join
    MiTabla A on G.Id_Columna = G.Id_Columna
    
    Me despita un poco lo de "para cada fecha". No sé si es un filtro o una agrupación adicional. Lo he obviado, si no te vale esto que te paso, ¿podrías detallar un poco más el papel de la fecha en la consulta?

    Alberto López Grande.


    jueves, 14 de mayo de 2009 7:19
    Moderador
  • Pues muchas gracias. Sí, es lo que estaba buscando. Lo de por cada fecha es para ordenarlo por fechas, pero eso es fácil. Lo dicho, muchas gracias y saludos.
    jueves, 14 de mayo de 2009 8:12
  • Dagomar,

    Si estas usando sql server 2005, otra forma seria usando la funcion dense_rank. Chequea los BOL para mas info sobre este tipo de funciones (ranking function).

    with r_set
    as
    (
    select
        Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion,
        dense_rank() over(
        PARTITION BY Fecha, Nombre
        ORDER BY CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END, CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END) as drnk
    from
        tu_tabla
    )
    select 
        Id_columna, Fecha, Nombre, Columna_A, Columna_B, Bit_informacion
    from
        r_set
    where
        drnk <= 2
    order by
        Fecha,
        Nombre,
        Bit_informacion,
        Id_columna;
    go


    Para ayudar el desempenio de este query, seria conveniente tener in indice por (Fecha, Nombre, Bit_informacion, Id_columna).


    Te pongo un ejemplo de lo que este hace.

    WITH r_set
    AS
    (
    SELECT
    	Id_columna, 
    	Fecha, 
    	Nombre, 
    	Bit_informacion,
    	DENSE_RANK() OVER(PARTITION BY Fecha, Nombre ORDER BY CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END, CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END) AS drnk
    FROM
    	(
    	SELECT 1, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 2, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 3, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 4, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 5, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 6, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 7, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 8, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 9, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 10, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 11, '20090102', 'Luis', 0 UNION ALL
    	SELECT 12, '20090102', 'Luis', 0 UNION ALL
    	SELECT 13, '20090102', 'Luis', 0 UNION ALL
    	SELECT 14, '20090102', 'Luis', 1 UNION ALL
    	SELECT 15, '20090102', 'Luis', 1 UNION ALL
    	SELECT 16, '20090102', 'Luis', 1	
    	) AS t(Id_columna, Fecha, Nombre, Bit_informacion)
    )
    SELECT *
    FROM r_set
    ORDER BY Fecha, Nombre, drnk;
    GO

    Por cada [Fecha] y [Nombre], si [Bit_informacion] = 0 entonces pone esa fila en un grupo que le llame 1 y si no lo pone en el grupo 2, para su ordenamiento. Cuando ordenamos dentro de la clausula over, los de el grupo 1 (bit_information = 0) seran primeros que los de el grupo 2 y estos seran tambien ordenados por una constante (select 0). Si la fila cae en el grupo 2, entonces ordeno ese subgrupo por la columna [Id_columna]. La funcion dense_rank asigna el mismo valor a todas la filas con el mismo rango (nivel de ordenamiento), en este caso todas las filas en el grupo 1 tendran el mismo valor de rango 1. Como la primera fila de el grupo 2 cambia de rango, entonces la funcion asigna el sgte valor, que seria 2 (esta funcion no deja huecos en la numeracion), e incrementara el valor de rango de las filas subsequentes en 1 porque se supone que aunque estan en el grupo 2, estas tiene diferente [Id_Columna]. Recuerda, esto se hace por cada combinacion de (fecha,  nombre), osea que los valores se reinicializan por cada combinacion de estas dos columnas, que son las que definen la particion. Veamos el case de "Ivan" en la fecha "20090101".


    Id_columna Fecha       Nombre Bit_informacion drnk
    5                20090101 Ivan      0                     1
    6                20090101 Ivan      0                     1
    7                20090101 Ivan      0                     1
    8                20090101 Ivan      1                     2
    9                20090101 Ivan      1                     3
    10              20090101 Ivan      1                     4


    Si agregamos los valores por los que se ordena para asignar el valor de rango dentro de cada particion, entonces podras entender un poquito mejor.


    WITH r_set
    AS
    (
    SELECT
    	Id_columna, 
    	Fecha, 
    	Nombre, 
    	Bit_informacion,
    	CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END AS [Primer valor por el que se ordena], 
    	CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END AS [Segundo valor por el que se ordena],
    	DENSE_RANK() OVER(PARTITION BY Fecha, Nombre ORDER BY CASE WHEN Bit_informacion = 0 THEN 1 ELSE 2 END, CASE WHEN Bit_informacion = 0 THEN (SELECT 0) ELSE Id_columna END) AS drnk
    FROM
    	(
    	SELECT 1, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 2, '20090101', 'Alejandro', 0 UNION ALL
    	SELECT 3, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 4, '20090101', 'Alejandro', 1 UNION ALL
    	SELECT 5, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 6, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 7, '20090101', 'Ivan', 0 UNION ALL
    	SELECT 8, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 9, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 10, '20090101', 'Ivan', 1 UNION ALL
    	SELECT 11, '20090102', 'Luis', 0 UNION ALL
    	SELECT 12, '20090102', 'Luis', 0 UNION ALL
    	SELECT 13, '20090102', 'Luis', 0 UNION ALL
    	SELECT 14, '20090102', 'Luis', 1 UNION ALL
    	SELECT 15, '20090102', 'Luis', 1 UNION ALL
    	SELECT 16, '20090102', 'Luis', 1	
    	) AS t(Id_columna, Fecha, Nombre, Bit_informacion)
    )
    SELECT *
    FROM r_set
    ORDER BY Fecha, Nombre, drnk
    GO

    Espero les haya sido de ayuda.


    AMB
       
    • Marcado como respuesta Dagomar lunes, 18 de mayo de 2009 5:39
    jueves, 14 de mayo de 2009 13:00
  • Interesante alternativa. No conocía la función dense_rank. Muchas gracias a ambos.
    lunes, 18 de mayo de 2009 5:39