none
Evitar Marcacion Doble en asistencia de personal RRS feed

  • Pregunta

  • Buen día, en la empresa donde estoy laborando tienen un marcador de huella digital de marca "zkteco k40", el cual manda los registros directamente a una tabla en sql, muchas veces un empleados marca dos veces casi al mismo tiempo, como en la imagen que muestro a continuación.


    Quería saber de que forma puedo evitar esos registros que tienen muy poco margen de tiempo entre uno y otro.

    jueves, 6 de septiembre de 2018 7:18

Respuestas

  • Hola Abram N. Cueva:

    Dudo mucho que puedas evitarlo, pero aunque pudieses, no es más fácil, ya los registros si se hicieron porque el usuario los hizo, no mostrarlos en la vista, que utilices.

    create table tiemposdif (userid int, checktime datetime)
    go
    insert into tiemposdif (userid, checktime) values
    (1,'20180905 22:23:38.000'),
    (1,'20180905 22:23:49.000'),
    (2,'20180910 00:23:39.000'),
    (2,'20180910 01:55:42.000'),
    (3,'20180910 02:23:45.000'),
    (3,'20180910 02:23:50.000')
    go
    
    ;with cte as (select userid, checktime from tiemposdif
    )
    select c.* from cte c left join cte d on c.userid = d.userid
    and d.checktime >= c.checktime and  d.checktime<  DATEADD(second,20,c.checktime)
    where d.userid is null
    

    Resultado

    Algo del estilo.

    Un saludo

    jueves, 6 de septiembre de 2018 7:56
  • Aca otra sugerencia para no tomar en cuenta esas filas.

    DECLARE @tiemposdif table (
    userid int NOT NULL, 
    checktime datetime NOT NULL
    );
    
    insert into @tiemposdif (userid, checktime) 
    VALUES
    	(1,'20180905 22:23:38.000'),
    	(1,'20180905 22:23:49.000'),
    	(2,'20180910 00:23:39.000'),
    	(2,'20180910 01:55:42.000'),
    	(3,'20180910 02:23:45.000'),
    	(3,'20180910 02:23:50.000');
    
    DECLARE @seconds int = 20;
    
    WITH R AS (
    SELECT
    	*,
    	CASE
    	WHEN DATEDIFF(SECOND, LAG(checktime) OVER(PARTITION BY userid ORDER BY checktime), checktime) < @seconds THEN 1 
    	ELSE 0 
    	END lt_3s_flag
    FROM
    	@tiemposdif
    )
    SELECT
    	*
    FROM
    	R
    WHERE
    	lt_3s_flag = 0
    ;
    GO
    Si adicionas un indice por (userid, checktime) evitaras el operador de ordenar (sort operator) en el plan de ejecucion.


    AMB

    Some guidelines for posting questions...

    AYÚDANOS A AYUDARTE, guía básica de consejos para formular preguntas


    jueves, 6 de septiembre de 2018 14:56
  • En caso de insistir en no permitir esas filas donde la diferencia en segundos entre esta y la immediata anterior para el mismo usuario sea menor a 20 u otro numero de referencia, entonces pudieras jugar con crear una funcion escalar y usar esta en una restriccion de chequeo.

    CREATE table dbo.tiemposdif (
    sk int NOT NULL IDENTITY PRIMARY KEY,
    userid int NOT NULL, 
    checktime datetime NOT NULL,
    UNIQUE NONCLUSTERED (userid, checktime)
    )
    GO
    CREATE FUNCTION dbo.ufn_myfn (
    @userid int,
    @checktime datetime
    )
    RETURNS int
    AS
    BEGIN
    DECLARE @diff int = NULL;
    
    WITH R AS (
    SELECT TOP (1)
    	checktime
    FROM
    	dbo.tiemposdif
    WHERE
    	userid = @userid
    	AND checktime < @checktime
    ORDER BY
    	checktime DESC
    )
    SELECT @diff = DATEDIFF(SECOND, R.checktime, @checktime) FROM R;
    
    RETURN @diff;
    END
    GO
    ALTER TABLE dbo.tiemposdif
    ADD CONSTRAINT check_diff_gt_seconds CHECK (ISNULL(dbo.ufn_myfn(userid, checktime), 20) >= 20)
    GO
    -- ok
    insert into tiemposdif (userid, checktime) 
    VALUES (1,'20180905 22:23:38.000');
    GO
    -- will fail
    insert into tiemposdif (userid, checktime) 
    VALUES (1,'20180905 22:23:49.000');
    GO
    -- ok
    insert into tiemposdif (userid, checktime) 
    VALUES
    	(2,'20180910 00:23:39.000'),
    	(2,'20180910 01:55:42.000'),
    	(3,'20180910 02:23:45.000');
    GO
    -- will fail
    insert into tiemposdif (userid, checktime) 
    VALUES (3,'20180910 02:23:50.000');
    GO
    SELECT * FROM dbo.tiemposdif
    GO
    DROP TABLE dbo.tiemposdif
    GO
    DROP FUNCTION dbo.ufn_myfn
    GO


    AMB

    Some guidelines for posting questions...

    AYÚDANOS A AYUDARTE, guía básica de consejos para formular preguntas

    jueves, 6 de septiembre de 2018 18:01
  • Abram,

    Seria de mucha ayuda para nosotros que postearas la estructura de la tabla, data de ejemplo en forma de sentencias INSERT, asi como el resultado esperado. Tal como Javi lo hizo en su primera respuesta.

    Ayudanos para poder ayudarte.

    dicho lo anterior, es importante tambien saber con que version de SQL Server cuentas pues para este tipo de problemas usar funciones de off-set (LEAD / LAG) hacen el trabajo mas simple.

    Primero debrias excluir las filas que segun tu criterio consideras duplicadas, luego con las filas restantes hacer la logica.

    Un forma seria enumerar las filas por cada usiario y dia para que puedes identificar pares de filas y por lo tanto las filas impares serian entrada y las pares salida.


    AMB

    Some guidelines for posting questions...

    AYÚDANOS A AYUDARTE, guía básica de consejos para formular preguntas


    viernes, 7 de septiembre de 2018 12:07
  • Que te parece esta opción.

    select ID_TRABAJADOR,cast(FechaAsistencia as date)Fecha,min(FechaAsistencia)Entrada,
    case when  DATEDIFF ( Hour , min(FechaAsistencia) , max(FechaAsistencia) )  >= 1 /*Horas laborables*/
    then   CONVERT(varchar ,MAX(FechaAsistencia),20)  else 'Sin Marcación de Salida ó Hora Por defecto de salida.' end Salida,
    DATEDIFF ( Hour , min(FechaAsistencia) , max(FechaAsistencia) ) HorasTrabajadas
     from Asistencia
    group by ID_TRABAJADOR,cast(FechaAsistencia as date)

    Coméntame.


    viernes, 7 de septiembre de 2018 22:09

Todas las respuestas

  • Hola Abram N. Cueva:

    Dudo mucho que puedas evitarlo, pero aunque pudieses, no es más fácil, ya los registros si se hicieron porque el usuario los hizo, no mostrarlos en la vista, que utilices.

    create table tiemposdif (userid int, checktime datetime)
    go
    insert into tiemposdif (userid, checktime) values
    (1,'20180905 22:23:38.000'),
    (1,'20180905 22:23:49.000'),
    (2,'20180910 00:23:39.000'),
    (2,'20180910 01:55:42.000'),
    (3,'20180910 02:23:45.000'),
    (3,'20180910 02:23:50.000')
    go
    
    ;with cte as (select userid, checktime from tiemposdif
    )
    select c.* from cte c left join cte d on c.userid = d.userid
    and d.checktime >= c.checktime and  d.checktime<  DATEADD(second,20,c.checktime)
    where d.userid is null
    

    Resultado

    Algo del estilo.

    Un saludo

    jueves, 6 de septiembre de 2018 7:56
  • Perdón, no es que dude mucho que puedas evitarlo, dudo mucho que debas de hacerlo.

    Piensalo tranquilamente, porque esas pocas unidades de tiempo, que por cierto en el ejemplo son 10 segundos, dentro de un tiempo te pueden traer problemas.

    Un saludo

    jueves, 6 de septiembre de 2018 7:58
  • Aca otra sugerencia para no tomar en cuenta esas filas.

    DECLARE @tiemposdif table (
    userid int NOT NULL, 
    checktime datetime NOT NULL
    );
    
    insert into @tiemposdif (userid, checktime) 
    VALUES
    	(1,'20180905 22:23:38.000'),
    	(1,'20180905 22:23:49.000'),
    	(2,'20180910 00:23:39.000'),
    	(2,'20180910 01:55:42.000'),
    	(3,'20180910 02:23:45.000'),
    	(3,'20180910 02:23:50.000');
    
    DECLARE @seconds int = 20;
    
    WITH R AS (
    SELECT
    	*,
    	CASE
    	WHEN DATEDIFF(SECOND, LAG(checktime) OVER(PARTITION BY userid ORDER BY checktime), checktime) < @seconds THEN 1 
    	ELSE 0 
    	END lt_3s_flag
    FROM
    	@tiemposdif
    )
    SELECT
    	*
    FROM
    	R
    WHERE
    	lt_3s_flag = 0
    ;
    GO
    Si adicionas un indice por (userid, checktime) evitaras el operador de ordenar (sort operator) en el plan de ejecucion.


    AMB

    Some guidelines for posting questions...

    AYÚDANOS A AYUDARTE, guía básica de consejos para formular preguntas


    jueves, 6 de septiembre de 2018 14:56
  • En caso de insistir en no permitir esas filas donde la diferencia en segundos entre esta y la immediata anterior para el mismo usuario sea menor a 20 u otro numero de referencia, entonces pudieras jugar con crear una funcion escalar y usar esta en una restriccion de chequeo.

    CREATE table dbo.tiemposdif (
    sk int NOT NULL IDENTITY PRIMARY KEY,
    userid int NOT NULL, 
    checktime datetime NOT NULL,
    UNIQUE NONCLUSTERED (userid, checktime)
    )
    GO
    CREATE FUNCTION dbo.ufn_myfn (
    @userid int,
    @checktime datetime
    )
    RETURNS int
    AS
    BEGIN
    DECLARE @diff int = NULL;
    
    WITH R AS (
    SELECT TOP (1)
    	checktime
    FROM
    	dbo.tiemposdif
    WHERE
    	userid = @userid
    	AND checktime < @checktime
    ORDER BY
    	checktime DESC
    )
    SELECT @diff = DATEDIFF(SECOND, R.checktime, @checktime) FROM R;
    
    RETURN @diff;
    END
    GO
    ALTER TABLE dbo.tiemposdif
    ADD CONSTRAINT check_diff_gt_seconds CHECK (ISNULL(dbo.ufn_myfn(userid, checktime), 20) >= 20)
    GO
    -- ok
    insert into tiemposdif (userid, checktime) 
    VALUES (1,'20180905 22:23:38.000');
    GO
    -- will fail
    insert into tiemposdif (userid, checktime) 
    VALUES (1,'20180905 22:23:49.000');
    GO
    -- ok
    insert into tiemposdif (userid, checktime) 
    VALUES
    	(2,'20180910 00:23:39.000'),
    	(2,'20180910 01:55:42.000'),
    	(3,'20180910 02:23:45.000');
    GO
    -- will fail
    insert into tiemposdif (userid, checktime) 
    VALUES (3,'20180910 02:23:50.000');
    GO
    SELECT * FROM dbo.tiemposdif
    GO
    DROP TABLE dbo.tiemposdif
    GO
    DROP FUNCTION dbo.ufn_myfn
    GO


    AMB

    Some guidelines for posting questions...

    AYÚDANOS A AYUDARTE, guía básica de consejos para formular preguntas

    jueves, 6 de septiembre de 2018 18:01
  • Agradezco la respuesta que me han brindado, me han servido a elaborar una consulta, que aun no esta finalizada, la cual me muestra los siguientes resultados.


    Como ven en la imagen, en la segunda fila la fecha ha pasado mas de un día con respecto al registro anterior, por lo tanto ya no correspondería la salida, sino una nueva entrada, y eso conllevaría a cambiar tambien la columna "ENTRADA_SALIDA" de las restantes filas del mismo trabajador.

    viernes, 7 de septiembre de 2018 6:20
  • Abram,

    Seria de mucha ayuda para nosotros que postearas la estructura de la tabla, data de ejemplo en forma de sentencias INSERT, asi como el resultado esperado. Tal como Javi lo hizo en su primera respuesta.

    Ayudanos para poder ayudarte.

    dicho lo anterior, es importante tambien saber con que version de SQL Server cuentas pues para este tipo de problemas usar funciones de off-set (LEAD / LAG) hacen el trabajo mas simple.

    Primero debrias excluir las filas que segun tu criterio consideras duplicadas, luego con las filas restantes hacer la logica.

    Un forma seria enumerar las filas por cada usiario y dia para que puedes identificar pares de filas y por lo tanto las filas impares serian entrada y las pares salida.


    AMB

    Some guidelines for posting questions...

    AYÚDANOS A AYUDARTE, guía básica de consejos para formular preguntas


    viernes, 7 de septiembre de 2018 12:07
  • Buen dia, estoy usando el sql server 2012 y la tabla que tengo es la siguiente:

    create table Asistencia (ID_TRABAJADOR int, FechaAsistencia datetime)

    go

    insert into Asistencia (ID_TRABAJADOR, FechaAsistencia) values

    (3,    '20180906 21:55:08.000'),

    (3,    '20180906 21:55:44.000'),

    (3,    '20180906 21:56:32.000'),

    (3,    '20180907 22:21:24.000'),

    (13,'20180906 21:55:28.000'),

    (13,'20180906 21:56:24.000'),

    (13,'20180906 22:00:59.000'),

    (13,'20180906 22:05:40.000'),

    (13,'20180906 22:19:24.000'),

    (13,'20180906 22:21:03.000'),

    (13,'20180906 22:21:54.000')

    Go

    -- Esta la consulta que muestra el siguiente resultado

    SELECT    Asistencia.Id_Trabajador AS ID_TRABAJADOR,    FechaAsistencia AS FECHA, 

    row_number() OVER (PARTITION BY Asistencia.Id_Trabajador

    ORDER BY (FechaAsistencia)) AS FILA, (CASE WHEN (row_number() OVER (PARTITION BY Asistencia.Id_Trabajador

    ORDER BY (FechaAsistencia))) % 2 = '0' THEN 'SALIDA' ELSE 'ENTRADA' END) AS ENTRADA_SALIDA

    FROM            dbo.Asistencia

    GROUP BY FechaAsistencia, Id_Trabajador


    Como ven cada numeo impar es entrada e impar es salida, ahora lo que quiero cambiar es que cuando entre una entrada y salida haya una diferencia de 24 horas o mas la salida, sea considerada como entrada y por lo tanto sus demas filas tambien cambiarian, como se ve en la imagen mostrada a continuacion


    viernes, 7 de septiembre de 2018 21:15
  • Que te parece esta opción.

    select ID_TRABAJADOR,cast(FechaAsistencia as date)Fecha,min(FechaAsistencia)Entrada,
    case when  DATEDIFF ( Hour , min(FechaAsistencia) , max(FechaAsistencia) )  >= 1 /*Horas laborables*/
    then   CONVERT(varchar ,MAX(FechaAsistencia),20)  else 'Sin Marcación de Salida ó Hora Por defecto de salida.' end Salida,
    DATEDIFF ( Hour , min(FechaAsistencia) , max(FechaAsistencia) ) HorasTrabajadas
     from Asistencia
    group by ID_TRABAJADOR,cast(FechaAsistencia as date)

    Coméntame.


    viernes, 7 de septiembre de 2018 22:09
  • Hola Blueh Scouth, la consulta no me muestra le resultado esperado, mas bien esperaria algo asi.

    sábado, 8 de septiembre de 2018 0:36
  • Es el software de captura que debería considerar ese problema, debe existir una interfaz que está el reloj marcador y el SQL.
    sábado, 8 de septiembre de 2018 23:56
  • Claro hay una interfaz en la que permite eligir entrada y salida, pero lamentablemente deja marcar asi no selecciones alguna opcion.
    domingo, 9 de septiembre de 2018 23:36