optimización de consultas: intervalos de tiempo

10

En general, tengo dos tipos de intervalos de tiempo:

presence time y absence time

absence time pueden ser de diferentes tipos (por ejemplo, descansos, ausencias, días especiales, etc.) y los intervalos de tiempo pueden superponerse y / o cruzarse.

Es no seguro, que sólo existen combinaciones posibles de intervalos en los datos en bruto, por ejemplo. los intervalos de presencia superpuestos no tienen sentido, pero pueden existir. He tratado de identificar los intervalos de tiempo de presencia resultantes de muchas maneras ahora; para mí, el más cómodo parece ser el siguiente.

;with "timestamps"
as
(
    select
        "id" = row_number() over ( order by "empId", "timestamp", "opening", "type" )
        , "empId"
        , "timestamp"
        , "type"
        , "opening"
    from
    (
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 1 as "type" from "worktime" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
        union all
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 2 as "type" from "break" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
        union all
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 3 as "type" from "absence" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
    ) as data
)
select 
      T1."empId"
    , "starttime"   = T1."timestamp"
    , "endtime"     = T2."timestamp"
from 
    "timestamps" as T1
    left join "timestamps" as T2
        on T2."empId" = T1."empId"
        and T2."id" = T1."id" + 1
    left join "timestamps" as RS
        on RS."empId" = T2."empId"
        and RS."id" <= T1."id"      
group by
    T1."empId", T1."timestamp", T2."timestamp"
having
    (sum( power( 2, RS."type" ) * RS."opening" ) = 2)
order by 
    T1."empId", T1."timestamp";

ver SQL-Fiddle para algunos datos de demostración.

Los datos en bruto existen en diferentes tablas en forma de "starttime" - "endtime"o "starttime" - "duration".

La idea era obtener una lista ordenada de cada marca de tiempo con una suma continua "enmascarada" de intervalos abiertos en cada momento para estimar el tiempo de presencia.

El violín funciona y proporciona resultados estimados, incluso si los tiempos de inicio de diferentes intervalos son iguales. No se utilizan índices en este ejemplo.

¿Es esta la forma correcta de lograr una tarea cuestionada o hay una forma más elegante para esto?

Si es relevante para responder: la cantidad de datos será de hasta varios diez mil conjuntos de datos por empleado por tabla. sql-2012 no está disponible para calcular una suma continua de predecesores en línea en conjunto.


editar:

Acabo de ejecutar la consulta contra una mayor cantidad de datos de prueba (1000, 10,000, 100,000, 1 millón) y puedo ver que el tiempo de ejecución aumenta exponencialmente. Obviamente una bandera de advertencia, ¿verdad?

Cambié la consulta y eliminé la agregación de la suma acumulada por una actualización peculiar.

He agregado una tabla auxiliar:

create table timestamps
(
  "id" int
  , "empId" int
  , "timestamp" datetime
  , "type" int
  , "opening" int
  , "rolSum" int
)

create nonclustered index "idx" on "timestamps" ( "rolSum" ) include ( "id", "empId", "timestamp" )

y me mudé calculando la suma acumulada a este lugar:

declare @rolSum int = 0
update "timestamps" set @rolSum = "rolSum" = @rolSum + power( 2, "type" ) * "opening" from "timestamps"

ver SQL-Fiddle aquí

El tiempo de ejecución disminuyó a 3 segundos con respecto a 1 millón de entradas en la tabla "tiempo de trabajo".

La pregunta sigue siendo la misma : ¿Cuál es la forma más efectiva de resolver esto?

Nico
fuente
Estoy seguro de que habrá disputas sobre esto, pero puede intentar no hacerlo en un CTE. Use tablas temporales en su lugar y vea si es más rápido.
rottengeek
Solo una pregunta de estilo: nunca he visto a nadie poner todos sus nombres de columnas y nombres de tablas entre comillas dobles. ¿Es esta la práctica de toda su empresa? Definitivamente me resulta incómodo. En mi opinión, no es necesario, y por lo tanto aumenta el ruido sobre la señal ...
ErikE
El método @ErikE Above es parte de un gran complemento. Algunos de los objetos se crean dinámicamente y dependen de la elección de entrada del usuario final. Entonces, por ejemplo, pueden aparecer espacios en blanco en los nombres de tabla o vista. las comillas dobles alrededor de esos no permitirán que la consulta se bloquee ...!
Nico
@Nico en mi mundo que generalmente se hace con corchetes y luego me gusta [this]. Supongo que me gusta más que las comillas dobles.
ErikE
@ErikE corchetes es tsql. estándar es comillas dobles! de todos modos, lo aprendí de esa manera y de alguna manera estoy acostumbrado.
Nico

Respuestas:

3

No puedo responder a tu pregunta sobre la mejor manera. Pero puedo ofrecer una forma diferente de resolver el problema, que puede o no ser mejor. Tiene un plan de ejecución razonablemente plano, y creo que funcionará bien. (Estoy ansioso por saberlo, ¡así que comparte los resultados!)

Pido disculpas por usar mi propio estilo de sintaxis en lugar del tuyo. Me ayuda a consultar la magia cuando todo se alinea en su lugar habitual.

La consulta está disponible en un SqlFiddle . Lancé una superposición para EmpID 1 solo para asegurarme de que tenía eso cubierto. Si finalmente encuentra que las superposiciones no pueden ocurrir en los datos de presencia, puede eliminar la consulta final y los Dense_Rankcálculos.

WITH Points AS (
  SELECT DISTINCT
    T.EmpID,
    P.TimePoint
  FROM
    (
      SELECT * FROM dbo.WorkTime
      UNION SELECT * FROM dbo.BreakTime
      UNION SELECT * FROM dbo.Absence
    ) T
    CROSS APPLY (VALUES (StartTime), (EndTime)) P (TimePoint)
), Groups AS (
  SELECT
    P.EmpID,
    P.TimePoint,
    Grp =
      Row_Number()
      OVER (PARTITION BY P.EmpID ORDER BY P.TimePoint, X.Which) / 2
  FROM
    Points P
    CROSS JOIN (VALUES (1), (2)) X (Which)
), Ranges AS (
  SELECT
    G.EmpID,
    StartTime = Min(G.TimePoint),
    EndTime = Max(G.TimePoint)
  FROM Groups G
  GROUP BY
    G.EmpID,
    G.Grp
  HAVING Count(*) = 2
), Presences AS (
  SELECT
    R.*,
    P.Present,
    Grp =
       Dense_Rank() OVER (PARTITION BY R.EmpID ORDER BY R.StartTime)
       - Dense_Rank() OVER (PARTITION BY R.EmpID, P.Present ORDER BY R.StartTime)
  FROM
    Ranges R
    CROSS APPLY (
      SELECT
        CASE WHEN EXISTS (
          SELECT *
          FROM dbo.WorkTime W
          WHERE
            R.EmpID = W.EmpID
            AND R.StartTime < W.EndTime
            AND W.StartTime < R.EndTime
        ) AND NOT EXISTS (
          SELECT *
          FROM dbo.BreakTime B
          WHERE
            R.EmpID = B.EmpID
            AND R.StartTime < B.EndTime
            AND B.StartTime < R.EndTime
        ) AND NOT EXISTS (
          SELECT *
          FROM dbo.Absence A
          WHERE
            R.EmpID = A.EmpID
            AND R.StartTime < A.EndTime
            AND A.StartTime < R.EndTime
        ) THEN 1 ELSE 0 END
    ) P (Present)
)
SELECT
  EmpID,
  StartTime = Min(StartTime),
  EndTime = Max(EndTime)
FROM Presences
WHERE Present = 1
GROUP BY
  EmpID,
  Grp
ORDER BY
  EmpID,
  StartTime;

Nota: el rendimiento de esta consulta mejoraría si combinara las tres tablas y agregara una columna para indicar qué tipo de tiempo fue: trabajo, descanso o ausencia.

¿Y por qué todos los CTE, preguntas? Porque cada uno está obligado por lo que necesito hacer con los datos. Hay un agregado, o necesito poner una condición WHERE en una función de ventana o usarla en una cláusula donde las funciones de ventana no están permitidas.

Ahora me voy a ir y ver si no puedo pensar en otra estrategia para lograr esto. :)

Por diversión incluyo aquí un "diagrama" que hice para ayudar a resolver el problema:

------------
   -----------------
                ---------------
                           -----------

    ---    ------   ------       ------------

----   ----      ---      -------

Los tres conjuntos de guiones (separados por espacios) representan, en orden: datos de presencia, datos de ausencia y el resultado deseado.

ErikE
fuente
Gracias por este enfoque. Lo comprobaré cuando regrese a la oficina y le daré resultados de tiempo de ejecución con una base de datos más grande.
Nico
El tiempo de ejecución es definitivamente mucho más alto que el primer enfoque. No tuve tiempo de verificar si otros índices pueden disminuirlo aún. Comprobará lo antes posible!
Nico
Tengo otra idea que no he tenido tiempo de poner a trabajar. Por lo que vale, su consulta devuelve resultados incorrectos con rangos superpuestos en todas las tablas.
ErikE
Revisé esto nuevamente, veo este violín que tiene intervalos completamente superpuestos en las tres tablas. devuelve resultados correctos, como puedo ver. ¿podría proporcionar un caso en el que se devuelven resultados incorrectos? ¡no dude en ajustar los datos de demostración en fiddle!
Nico
Muy bien, entendí tu punto. En el caso de intervalos de intersección en una tabla, los resultados se volvieron locos. Verificará esto.
Nico