Imagen Aleatoria:
Buscar:
Tags:
Blog
virus
Microsoft
Viajes
SQL
.NET
Gaming
Tutoriales
Frustraciones
videos
Encuesta:
Que sucedera primero?
Registrarse:
         Log-In
 
      Registrarse
Blog Roll:
Jesus Uribe
Franco Castro
spooky
Alex Centeno
Ale Fierro
DAE
Concursos:
    

  
 






"Property is theft. Nobody “owns” anything. When you die, it all stays here. " 

invitaciones google wave
12/18/2009
Tengo 25 invitaciones de google wave para el q quiera una deje un mensaje!
1 Comentarios
   
update incremental...
11/21/2009

estoy harto de telmex y su maldito internet lento, sus malditos inutiles de soporte tecnico q no saben ni resuelven nada.

harto de telcel y sus cobros extraños.

harto de la gente matandose en la calle.

harto de los politicos cobrando mas impuestos nuevos.

harto de los malos musicos haciendose famosos y q los buenos ni se escuchen.

harto de MTV y sus programas de teen gossip.

harto de mi mismo...

0 Comentarios
   
un poco de logica
9/20/2009

En este corto post mostrare un ejemplo basico de como con pura logica podemos cambiar el flujo de una consulta, esto es algo muy basico pero pueden aplicarlo a casos mas complejos y ahorrarse un par de lineas de codigo:

-- Creamos una tabla en la cual almacenaremos todo.

create table tablaLogica

(

id int identity,

nombre nvarchar(50),

email nvarchar(20),

Sector int,

Filtro int

)

-- Insertamos un par de registros

INSERT INTO tablaLogica (Nombre,Email,Sector,Filtro) VALUES('Pancho','pancho@mail.com',6,1)

INSERT INTO tablaLogica (Nombre,Email,Sector,Filtro) VALUES('Maria','Maria@mail.com',6,1)

INSERT INTO tablaLogica (Nombre,Email,Sector,Filtro) VALUES('Eduwiges','Eduwiges@mail.com',6,1)

INSERT INTO tablaLogica (Nombre,Email,Sector,Filtro) VALUES('Gonzalo','Gonzalo@mail.com',6,2)

-- con las siguientes lineas filtraremos un campo de acuerdo a un bit solo hay que cambiar el bit de 0 a 1

declare @Sector int,@SoloFiltro1 bit

SET @Sector = 6

SET @SoloFiltro1 = 0

SELECT * FROM tablaLogica WHERE Sector = @Sector AND ( ( @SoloFiltro1=1 AND Filtro=1) OR ( @SoloFiltro1 = 0 ) )

-- borramos el rastro de nuestros errores

drop table tablaLogica

1 Comentarios
   
fuzzy lookups parte 2
9/11/2009

Después de una pequeña ausencia y downtime por exceso de pago, continuare con la serie de mini-tutorías de data mining, en esta ocasión veremos el algoritmo jaro-winkler (aplausos por favor).

Este algoritmo es parecido al anterior (distancia Levenshtein), solo que este es mas preciso, con el anterior podíamos tener 3 palabras:

1)      Rene

2)      Ale

3)      Alejandra

 

Y nos diría que ’rene’ y ‘ale’ son mas parecidas pues a ‘ale’ le faltan menos letras para ser ‘rene’ que para ser ‘alejandra’, con el jaro-winkler no tendremos ese problema, sin mas preámbulo veamos lo que nos interesa, tiene una formula medio rara pero ahí les va, consiste en los siguientes pasos:

 

1)      Calcular cantidad de caracteres en común y guardarlos en ‘m’.

2)      Calcular longitud de cadena 1 y 2 y almacenarlas en ‘s1’ y ‘s2’ respectivamente.

3)      Calcular el número de transposiciones almacenarlo en ‘t’ y dividirlo entre 2. Las transposiciones son los caracteres que son iguales pero están en diferente orden, ejemplo: paco y paoc, la ‘c’ y la ‘o’ están en las dos palabras solo q en diferente orden ;).

4)      Sumar m/s1 + m/s2 + (m-t/m) y el resultado de esto multiplicarlo por 1 / 3 ( uno sobre tres, nada de que .3 ).

5)      Y esa será nuestra distancia jaro-winkler.

 

Veamos una primer implementación (algo ruda) del algoritmo, en la cual se realizara en 2 pasos, el primero para limpiar las cadenas de caracteres extraños y el segundo para realizar el algoritmo.

 

-- funcion que elimina caracteres no comunes

CREATE FUNCTION [dbo].ufnNormalizarCadena

(

      @cadena VARCHAR(MAX)

)

RETURNS VARCHAR(MAX)

AS BEGIN

    DECLARE @cadenaNormalizada VARCHAR(MAX)

    SET @cadenaNormalizada = @cadena

    SET @cadenaNormalizada = REPLACE(@cadenaNormalizada, '.', '')

    SET @cadenaNormalizada = REPLACE(@cadenaNormalizada, ',', '')

    SET @cadenaNormalizada = REPLACE(@cadenaNormalizada, '-', '')

    SET @cadenaNormalizada = REPLACE(@cadenaNormalizada, ';', '')

    SET @cadenaNormalizada = REPLACE(@cadenaNormalizada, ':', '')

    RETURN @cadenaNormalizada

END

 

Y ahora lo bueno:

-- funcion que nos regresa la distancia jaro-winkler

CREATE FUNCTION [dbo].[ufnJaroWinkler]

(

  @Cadena1 varchar(max),

  @Cadena2 varchar(max)

)

RETURNS float

AS BEGIN

    Declare @Cadena1Normalizada varchar(max), -- cadena 1 normalizada

        @Cadena2Normalizada varchar(max), -- cadena 2 normalizada

        @LongitudCadena1 int,  -- longitud cadena 1 normalizada

        @LongitudCadena2 int        -- longitud cadena 2 normalizada

 

    Declare @LongitudTemporal int, -- temporal usado para invertir longitudes de cadenas normalizadas

        @LongitudMayor int, -- longitud de la cadena normalizada mas grande

        @m int, -- distancia :: (max/2) -1

        @i int  -- usado para recorrer la primer cadena

 

    Declare @f int,

        @z int,

        @tr int, -- transposiciones

        @a1 varchar(max), -- usado para determinar si el caracter en i es igual en cadena 1 y cadena 2

        @a2 varchar(max) -- usado para determinar si el caracter en i es igual en cadena 1 y cadena 2

 

    Declare @CadenasTemporal varchar(max),  -- temporal usado para invertir cadenas normalizadas

        @wcd float -- .3333 usado para multiplicar al final

 

    Declare @CantidadCaracteresComun float, -- cantidad de caracteres en comun

        @DistanciaJaro float    -- resultado final

 

    Declare @TablaTemporal1 Table

        (

          [FID] [int],

          [FStatus] [bit] NOT NULL

        )

 

    Declare @TablaTemporal2 Table

        (

          [FID] [int],

          [FStatus] [bit] NOT NULL

        )

 

    set @tr = 0

    Set @CantidadCaracteresComun = 0

    Set @DistanciaJaro = 0

 

      -- normalizar cadenas

    Set @Cadena1Normalizada = [dbo].ufnNormalizarCadena(@Cadena1)

    Set @Cadena2Normalizada = [dbo].ufnNormalizarCadena(@Cadena2)

 

    Set @LongitudCadena1 = LEN(@Cadena1Normalizada)

    Set @LongitudCadena2 = LEN(@Cadena2Normalizada)

 

    Set @wcd = 1.0 / 3.0

 

      -- cadena 2 debera ser la mas grande

    if @LongitudCadena1 > @LongitudCadena2

        begin

            set @LongitudTemporal = @LongitudCadena2

            set @LongitudCadena2 = @LongitudCadena1

            set @LongitudCadena1 = @LongitudTemporal

            set @CadenasTemporal = @Cadena1Normalizada

            set @Cadena1Normalizada = @Cadena2Normalizada

            set @Cadena2Normalizada = @CadenasTemporal

        end

 

    set @LongitudMayor = @LongitudCadena2

 

      -- inserta en tabla temporal de la cadena 1 el numero de caracter y un 0

    Declare @ContadorI int

    Set @ContadorI = 1

    while( @ContadorI <= @LongitudCadena1 )

        Begin            

            insert  into @TablaTemporal1

            values  ( @ContadorI, 0 )                

            Set @ContadorI = @ContadorI + 1

        End

 

      -- inserta en tabla temporal de la cadena 2 el numero de caracter y un 0

    Set @ContadorI = 1

    while( @ContadorI <= @LongitudCadena2 )

        Begin

            insert  into @TablaTemporal2

            values  ( @ContadorI, 0 )   

            Set @ContadorI = @ContadorI + 1  

        End

 

      -- calcula M

    Set @ContadorI = 1

    Set @m = ROUND(( @LongitudMayor / 2 ) - 1, 1)

    set @i = 1

 

    while( @i <= @LongitudCadena1 )

        Begin

      -- Lee el caracter

            Set @a1 = SubString(@Cadena1Normalizada, @i, 1)

       

            -- # iteraciones

            if @m >= @i

                Begin

                    set @f = 1

                    set @z = @i + @m

                End

            else

                Begin

                    set @f = @i - @m

                    set @z = @i + @m

                End

 

            if @z > @LongitudMayor

                Begin

                    set @z = @LongitudMayor

                End

 

            -- si el caracter esta en las dos tablas pone el bit en 1

            while( @f <= @z )

                Begin

                    Set @a2 = SubString(@Cadena2Normalizada, @f, 1)

                    Declare @valStatus bit

                    Select  @valStatus = [FStatus]

                    from    @TablaTemporal2

                    Where   [FID] = @f

                    if ( ( @a2 = @a1 )

                         and ( @valStatus = 0 )

                       )

                        Begin

                            Set @CantidadCaracteresComun = @CantidadCaracteresComun + 1 -- incrementamos la cantidad de caracteres en comun

                            Update  @TablaTemporal1

                            set     [FStatus] = 1

                            Where   [FID] = @i

                            Update  @TablaTemporal2

                            set     [FStatus] = 1

                            Where   [FID] = @f

                            Goto saltar_linea

                        End

                    set @f = @f + 1      

                end

            saltar_linea:

            set @i = @i + 1

        End

 

 

    set @i = 1

    set @z = 1

      -- recorremos X y Y hasta encontrar en Y un bit con 1 y luego nos saltamos de renglon,

    while( @i <= @LongitudCadena1 )

        Begin

            Declare @v1Status bit

            set @v1Status = 0

            Select  @v1Status = [FStatus]

            from    @TablaTemporal1

            Where   [FID] = @i

            if ( @v1Status = 1 )

                Begin

                    while( @z <= @LongitudCadena2 )

                        Begin

                            Declare @v2Status bit

                            set @v2Status = 0

                            Select  @v2Status = [FStatus]

                            from    @TablaTemporal2

                            Where   [FID] = @z

                            if ( @v2Status = 1 )

                                Begin

                                    set @z = 1

                                    Set @a1 = SubString(@Cadena1Normalizada, @i, 1)

                                    Set @a2 = SubString(@Cadena2Normalizada, @z, 1)

                              -- si los caracteres en el ciclo son diferentes sumamos las transposiciones

                                    if ( @a1 <> @a2 )

                                        Begin

                                            set @tr = @tr + 0.5

                                        End

                                    Goto j_loop

                                End

                            set @z = @z + 1

                        End

                    j_loop:

                End

            set @i = @i + 1

        End

 

      -- calculamos la distancia jaro

    if @CantidadCaracteresComun <> 0

        begin

            Set @DistanciaJaro = @wcd * ( @CantidadCaracteresComun / @LongitudCadena1 + @CantidadCaracteresComun

                                       / @LongitudCadena2 + ( @CantidadCaracteresComun - @tr )

                                       / @CantidadCaracteresComun )

        end

     

    return @DistanciaJaro

   end

 

 

Ahora veamos un ejemplo de esto:

-- creamos tablas de prueba

CREATE TABLE #tmp1

(

      nombre NVARCHAR(50)

)

 

CREATE TABLE #tmp2

(

      nombre NVARCHAR(50)

)

 

-- Insertamos valores de ejemplo

INSERT INTO [#tmp1] SELECT ('ale')

INSERT INTO [#tmp1] SELECT ('pancho')

 

INSERT INTO [#tmp2] SELECT ('rene')

INSERT INTO [#tmp2] SELECT ('alejandra')

INSERT INTO [#tmp2] SELECT ('poncho')

 

-- ejecutamos la consulta

SELECT * FROM (

      SELECT a.nombre NombreA,b.nombre NombreB,dbo.ufnJaroWinkler(a.nombre,b.nombre) AS Distancia

            FROM [#tmp1] a CROSS JOIN [#tmp2] b

            ) cte

      ORDER BY Distancia DESC

     

-- eliminamos evidencia

DROP TABLE #tmp1

DROP TABLE #tmp2

Esto nos regresara lo siguiente:

NombreA            NombreB            Distancia

pancho                 poncho                 0.888888

ale                          alejandra             0.777777

ale                          rene                      0.52777725

pancho                 alejandra             0.518518

pancho                 rene                      0.47222175

ale                          poncho                 0

 

Si se fijan los 2 mejores resultados son los que si tienen sentido, analizando nuestra fuente de datos podríamos establecer un margen y decir (por ejemplo) superior a .7 es un valor valido, cambiemos un poco nuestra consulta final:

-- ejecutamos la consulta mejorada

SELECT * FROM (

      SELECT a.nombre NombreA,b.nombre NombreB,dbo.ufnJaroWinkler(a.nombre,b.nombre) AS Distancia

            FROM [#tmp1] a CROSS JOIN [#tmp2] b

            ) cte

            WHERE [Distancia] > 0.7

      ORDER BY Distancia DESC

 

Y ahora solo tendremos los 2 mas parecidos, esto concluye este gran post, espero les guste y les sirva, en el siguiente vamos a hacer un reader de RSS usando SQL Server 2005, hasta la próxima.

0 Comentarios
   
fuzzy lookups parte 1
8/20/2009

El día de hoy vamos a ver un poco mas de minería de datos con SQL. Vamos a suponer que tenemos 2 bases de datos las cuales queremos relacionar pero no tienen campos que directamente sean iguales, por ejemplo el campo con el que las queremos relacionar es el nombre, en la base de datos 1 tenemos “maria” y en la base de datos 2 tenemos “mari”, otra seria claves de producto, en una base de datos usan el espacio para separar los grupos y en la otra un guion bajo y muchas cosas mas así. Haremos uso de un concepto llamado “fuzzy search” o "fuzzy look-ups" (búsquedas difusas para los compas), la cual no nos encontrara los registros exactamente iguales sino los mas parecidos, por lo cual tendremos un cierto porcentaje de datos perdidos, pero mejor tener que ajustar un leve porcentaje de nuestro universo de registros a mano o usando otro método a hacerlo todo a mano.

Hay varios algoritmos de los cuales nos podremos asistir para realizar esto, hay dos que son los más comunes para esta tarea:

·         Jaro-winkler.

·         Levenshtein.

 

En este post solo veremos la segunda opción pues es solo una introducción (el código aquí descrito lo saque de internet pues solo quería usarlo como introducción a estos procedimientos, el de jaro-winkler si es una implementación mía).

El de jaro-winkler nos dará mejores resultados pero el de levenshtein es más fácil de implementar, lo cual los hará entender más fácil lo que queremos lograr.

El algoritmo consiste de 7 pasos:

1: crear una matriz de n ( longitud de la cadena 1 ) por m ( longitud de la cadena 2 ).

2: inicializar el primer renglon con valores de 0 a n y la primer columna con valores de 0 a m.

3: examinar cada caracter de la cadena 1 ( i desde 1 hasta n ).

4: examinar cada caracter de la cadena 2 ( j desde 1 hasta m ).

5: si la cadena 1 en i es igual a la cadena 2 en j el costo es 0 si no el costo es 1.

6: en la posicion de i con j poner el valor minimo de comparar:

a)      La celda inmediatamente arriba mas 1: d[i-1,j]+1.

b)      La celda inmediatamente a la izquierda mas 1: d[i,j-1] +1

c)       La celda diagonalmente arriba y a la izquierda mas el costo: d[1-1,j-1] + costo

7: despues de repetir los pasos del 3 al 6 hasta terminar, regresamos el valor en n con m.

 

Ahora veamos esto en codigo, para lo cual requeriremos de dos funciones:

CREATE FUNCTION [dbo].[Min3]( @a int, @b int, @c int ) returns int AS

   BEGIN

      DECLARE @Retval int

      if @a <= @b AND @a <= @c

            BEGIN

            SET @Retval = @a

            END

      if @b < @a AND @b <= @c

            BEGIN

            SET @Retval = @b

            END

      if @c < @a AND @c < @b

            BEGIN

            SET @Retval = @c

            END

  RETURN @Retval

  END

 

La cual simplemente nos regresa el valor minimo de 3 que se le envien como parametro (paso 6).

Y ahora si, la otra funcion:

 

CREATE function [dbo].[levenshtein]( @s varchar(50), @t varchar(50) )

returns varchar(50)

as

BEGIN

      DECLARE

            @d varchar(100), -- aqui almacenaremos todo, si usamos cadenas mas grandes a 50 caracteres hay q ajustar

            @LD int, -- distancia entre las 2 palabras

            @m int, -- longitud de la cadena 1

            @n int, -- longitud de la cadena 2

            @i int, -- usado para recorer el eje X

            @j int, -- usado para recorrer el eje Y

            @s_i char(1), -- el caracter de la cadena 1 en la posicion i

            @t_j char(1), -- el caracter de la cadena 2 en la posicion j

            @costo int -- costo por incremento

 

      -- 1: inicializar valores con los tamaños de los caracteres

      SET @n=LEN(@s)

      SET @m=LEN(@t)

      SET @d=replicate(CHAR(0),100)

      If @n = 0

      BEGIN

            SET @LD = @m

            GOTO fin

      END

     

      If @m = 0

      BEGIN

            SET @LD = @n

            GOTO fin

      END

 

      -- 2: inicializar el primer renglon con valores de 0 a n y la primer columna con valores de 0 a m

      SET @i=0

      WHILE @i<=@n

      BEGIN

            SET @d=STUFF(@d,@i+1,1,CHAR(@i))

            SET @i=@i+1

      END

 

      SET @i=0

      WHILE @i<=@m

      BEGIN

            SET @d=STUFF(@d,@i*(@n+1)+1,1,CHAR(@i))

            SET @i=@i+1

      END

       

      -- 3: examinar cada caracter de la cadena 1 ( i desde 1 hasta n )

      SET @i=1

      WHILE @i<=@n

      BEGIN

            SET @s_i=(substring(@s,@i,1))

 

            -- 4: examinar cada caracter de la cadena 2 ( j desde 1 hasta m )

            SET @j=1

            WHILE @j<=@m

            BEGIN

                  SET @t_j=(substring(@t,@j,1))

 

      -- 5: si la cadena 1 en i es igual a la cadena 2 en j el costo es 0 si no el costo es 1

                  If @s_i = @t_j

                        SET @costo=0

                  ELSE

                        SET @costo=1

 

      -- 6: en la posicion de i con j poner el valor minimo dictado por el algoritmo (ver referencia)

                  SET @d=STUFF(@d,@j*(@n+1)+@i+1,1,CHAR(dbo.MIN3(

                  ASCII(substring(@d,@j*(@n+1)+@i-1+1,1))+1,

                  ASCII(substring(@d,(@j-1)*(@n+1)+@i+1,1))+1,

                  ASCII(substring(@d,(@j-1)*(@n+1)+@i-1+1,1))+@costo)

                  ))

                  SET @j=@j+1

            END

            SET @i=@i+1

      END     

 

      -- 7: despues de repetir los pasos del 3 al 6 hasta terminar, regresamos el valor en n con m

      SET @LD = ASCII(substring(@d,@n*(@m+1)+@m+1,1))

      fin:

      RETURN @LD

END

 

Esta es la que ejecuta todo el algoritmo y nos regresa un numerito diciendonos los cambios que hay que realizarle a las cadenas para que sean iguales, veamos un ejemplo:

select dbo.levenshtein('rene','rene_')

 

Esto nos regresara un numero 1, pues si quitamos el guion bajo, las cadenas serian iguales.

Veamos otro ejemplo, ahora un poco mas complejo:

 

-- creamos un par de tablas para simular 2 fuentes de datos:

create table camposBaseDatos1 (

      id int identity,

      Nombre varchar(50)

)

 

create table camposBaseDatos2 (

      id int identity,

      Nombre varchar(50)

)

 

-- insertamos datos de ejemplo:

INSERT INTO camposBaseDatos1 (Nombre) VALUES('rene')

INSERT INTO camposBaseDatos1 (Nombre) VALUES('maria')

INSERT INTO camposBaseDatos1 (Nombre) VALUES('rebeca')

 

INSERT INTO camposBaseDatos2 (Nombre) VALUES('beca')

INSERT INTO camposBaseDatos2 (Nombre) VALUES('mari')

INSERT INTO camposBaseDatos2 (Nombre) VALUES('rene_')

 

-- mezclamos los registros de todas las tablas

SELECT bd1.Nombre,bd2.Nombre FROM camposBaseDatos1 AS bd1 CROSS JOIN camposBaseDatos2 AS bd2

 

-- lo mismo que el anterior pero ahora determinamos la distancia entre ambos valores y los ordenamos:

SELECT Nombre1,Nombre2,Distancia FROM (

      SELECT bd1.Nombre Nombre1, bd2.Nombre Nombre2, dbo.levenshtein(bd1.Nombre,bd2.Nombre) as Distancia

      FROM camposBaseDatos1 AS bd1 CROSS JOIN camposBaseDatos2 AS bd2

) cte Order By Distancia ASC

 

 

Aquí nos va a dar los primeros 3 registros con sus elementos mas parecidos :D.

Espero les guste este post y en el siguiente veremos el super efectivo y preciso jaro-winkler.

 

0 Comentarios
   
  Siguiente Pagina
Usuario Actual: ..::No estas logeado::..