How to calculate the date and shift code?

Asked

Viewed 833 times

8

I have a table that specifies the starting time of a work shift and its duration in minutes:

codigo      descricao           inicio(datetime)        duracao(int)
S01         PRIMEIRO            1900-01-01 05:00:00.000 540
S02         SEGUNDO             1900-01-01 14:00:00.000 540
S03         TERCEIRO            1900-01-01 23:00:00.000 360

I also have a release table, which has the date and time of release:

serie       hora
743         2016-06-24 23:06:19.663
744         2016-06-25 00:05:17.852
745         2016-06-25 08:01:27.001
746         2016-06-25 16:08:01.923

I need to sort the releases based on the turn table so that I can get an output as follows:

serie       dia        codigo
743         2016-06-24 S03
744         2016-06-24 S03
745         2016-06-25 S01
746         2016-06-25 S02

For the corresponding shift comprises the time period between its beginning and its end, which is the sum of the duration and the initial time. Thus, releases made between 23:00:00 and 04:59:59 belong to the S03 turn, between 05:00:00 and 13:59:59 belong to the S01 turn and releases made between 14:00 and 22:59:59 to the S02 turn.

How to calculate the date and shift code to which a launch belongs under this scenario?

  • It seems to me that a simple Join + Between solves. Let’s see if someone posts an example.

  • @Antoniomano: Is the example of exit to line 744 correct? That is, although the point beat occurred on the day 2016-06-25, the result should include the start date of the turn (2016-06-24) ? // The solution depends on the answer.

  • Was any of the answer helpful? Don’t forget to choose one and mark it so it can be used if someone has a similar question!

5 answers

3

See if that’s what you need..

declare @horas table
(
  serie int,
  hora datetime
)


insert into @horas values
(743,'2016-06-24 23:06:19.663'),
(744,'2016-06-25 00:05:17.852'),
(745,'2016-06-25 08:01:27.001'),
(746,'2016-06-25 16:08:01.923')


select  serie, Convert(varchar(10),hora,126),
case 
    when DATEPART(HOUR, hora) BETWEEN 5 and 14  then 'S01'
    when DATEPART(HOUR, hora) BETWEEN 14 and 22 then 'S02' 
    when DATEPART(HOUR, hora) >= 23 or DATEPART(HOUR, hora) BETWEEN 0 and 5  then 'S03' ) then 'S03'
end as codigo

from @horas

inserir a descrição da imagem aqui

  • Tip: The final time of each turn is an open interval. Instead of col between n1 and N2 replace with col >= n1 and col < N2 // But I don’t think the slots should be hardcoded...

  • @Stormwind, truth, I made the correction....

  • @Josédiz, I do not see an open interval, hours are not fractionated the way he is asking, has exact time range ... DATEPART(HOUR, hour) returns an exact time ... If you need to know an interval between 13 and 18 hours wouldn’t need to look at the minutes here if that’s what you meant.

  • @Marconciliosouza: Note that the author of the topic cites verbatim that "launches made between (...) 05:00:00 and 13:59:59 belong to the S01 turn". That is, the slots in the table are open intervals on the right. For example, S01 is from 05h (inclusive) to 14h (exclusive). // Release held at 14:02, for example, should be on track S02 but in its code it will fall on track S01.

  • @Marconciliosouza: But the main difficulty is in case 744; observed that the retroactive date? I even posted a question to the author.

  • Simply change the value of the parameter.. is an example of how to do... in case just change the BETWEEN 4 and 14 then ... just need to see the need...

Show 1 more comment

0

I used the solution suggested by @Ernando as subquery (Mysql), I found more interesting because in the case of errors in the conditions the query returns error, facilitating the evaluation.

In my case I also had a working shift term.

Look at:

consulta

Tab turno

  • 3

    It would be interesting to put your SQL as text beyond the image

0

Although it seems a simple case, there is an additional complication that is the fact that the day of the launch should be set to the day before when the turn crosses day and the point was hit the day after the start of the turn. This is case 744 in the example posted by the author, on what date/time of the strike:

2016-06-25 00:05:17.852

is changed to the previous day:

2016-06-24

As the shifts start at full hour (always at 0 minute), one simple way is to transform the turn table per hour:

inserir a descrição da imagem aqui

on a table of shifts per hour, starting at 0h and going up to 23h:

inserir a descrição da imagem aqui

One way to get the hourly shift table is the one in the code below.

-- código #1
declare @tbHora table (Hora int);
INSERT into @tbHora values
  (0), (1), (2), (3), (4), (5), (6), (7),
  (8), (9), (10), (11), (12), (13), (14), (15), 
  (16), (17), (18), (19), (20), (21), (22), (23);

-- transforma a tabela de turno de faixa horária para hora
with 
cteTurnoFH as (
SELECT codigo, inicio, dateadd(minute, duracao, inicio) as fim
  from tbTurno
),
cteTurnoH as (
SELECT T.codigo, H.Hora
    from cteTurnoFH as T
         cross join @tbHora as H
    where 1 = case when datepart(day, T.inicio) = datepart(day, T.fim)
                        then case when H.Hora >= datepart(hour, T.inicio) 
                                       and H.Hora < datepart(hour, T.fim)
                                  then 1 else 0 end
                   else case when H.Hora >= datepart(hour, T.inicio) 
                                       or H.Hora < datepart(hour, T.fim)
                                  then 1 else 0 end
               end
)
...

And to show off the launches with the turn would suffice something like

-- código #2
...
SELECT L.serie, cast(L.hora as date) as dia, TH.codigo
  from tbLançamento as L
       inner join cteTurnoH as TH on TH.Hora = datepart(hour, L.hora)
  order by L.serie;

However, there is an additional complication, which is the fact that the day of release must be set to the day before when the turn crosses the day and the spike was hit the day after the start of the turn. This is case 744, in the example posted by the author.

To treat the day setting, the CTE cteTurnoH has been enlarged, containing column indicating at which times it is necessary to adjust the date. At the end the code went like this:

-- código #3 v2
set nocount on;

-- 
IF Object_ID('tempDB..#TurnoH', 'U') is not null
  DROP TABLE #TurnoH;
CREATE TABLE #TurnoH (Hora int primary key, codigo char(3), AjusteDia int);

--
declare @tbHora table (Hora int);
INSERT into @tbHora values
  (0), (1), (2), (3), (4), (5), (6), (7),
  (8), (9), (10), (11), (12), (13), (14), (15), 
  (16), (17), (18), (19), (20), (21), (22), (23);

-- transforma a tabela de turno de faixa horária para hora
with 
cteTurnoFH as (
SELECT codigo, inicio, dateadd(minute, duracao, inicio) as fim
  from tbTurno
),
cteTurnoH as (
SELECT H.Hora, T.codigo,
       case when datepart(day, T.inicio) = datepart(day, T.fim) 
                 then 0
            else case when datepart(hour, T.inicio) <= H.Hora
                           then 0
                      else -1 
                 end
       end as AjusteDia
    from cteTurnoFH as T
         cross join @tbHora as H
    where 1 = case when datepart(day, T.inicio) = datepart(day, T.fim)
                        then case when H.Hora >= datepart(hour, T.inicio) 
                                       and H.Hora < datepart(hour, T.fim)
                                  then 1 else 0 end
                   else case when H.Hora >= datepart(hour, T.inicio) 
                                       or H.Hora < datepart(hour, T.fim)
                                  then 1 else 0 end
               end
)
INSERT into #TurnoH (Hora, codigo, AjusteDia)
  SELECT Hora, codigo, AjusteDia
    from cteTurnoH;

--
SELECT L.serie, L.hora as batida,
       cast(dateadd(day, TH.AjusteDia, L.hora) as date) as dia,
       TH.codigo
  from tbLançamento as L
       inner join #TurnoH as TH on TH.Hora = datepart(hour, L.hora);

And the final result obtained is

inserir a descrição da imagem aqui

If the turn is possible to start at fractional time, code #3 needs to be changed.

There’s probably a more efficient solution, but it was the first that came to mind this morning.

0

It is possible to find the turn of each release by making a direct relationship between the tables lançamentos and turnos.

It may seem like a more complex solution, but it leaves the generic query, that is, if the turn table changes, there will be no need to change the query.

Conceptually, the logic of relationship between the tables would be the following:

FROM lançamento
LEFT JOIN turno
     ON (lançamento.hora em D+0) between (turno.inicio em D+0) AND (turno.término em D+0)
     or (lançamento.hora em D+1) between (turno.inicio em D+0) AND (turno.término em D+1)

D+0 corresponds to the standard day (in the case of Sqlserver, the standard is 01-01-1900) and D+1 corresponds to the next day.

The idea is to relate the records whose hora de lançamento em D+0 is among the início and the término shift.

As a shift can start one day and end the next day, it is also necessary to check whether the hora do lançamento em D+1 is between the início and the término shift.

Solution

The solution below is available for online testing at http://sqlfiddle.com/#! 6/0b387/6

SELECT a.serie
      ,CAST(a.hora as DATE) as dia
      ,b.codigo
  FROM Lancamentos a
  LEFT JOIN Turnos b 
       ON CAST( CAST(a.hora as TIME) as datetime) -- hora em D+0
          BETWEEN inicio AND DATEADD( minute, duracao, inicio ) -- entre inicio AND término do turno
          OR DATEADD( day, 1, CAST( CAST( a.hora as TIME ) as datetime) ) -- hora em D+1
             BETWEEN inicio AND DATEADD( minute, duracao, inicio ) -- entre inicio AND término (no caso de término do turno em D+1)

0

First of all the best type of data for the start of the shift is TIME, after all you don’t need the part DATE of the data. Whereas the shift may start in one day and end in the other, the condition BETWEEN would not work with just one record. You can transform the records to facilitate validation with WITH:

WITH horas_ajustadas AS
(
  SELECT h.codigo,
         h.descricao,
         CAST(h.inicio AS TIME) AS inicio,
         DATEADD(MINUTE, h.duracao, CAST(h.inicio AS TIME)) AS fim
    FROM horas h
),
turnos AS
(
  -- Turnos que iniciam e terminam no mesmo dia
  SELECT ha.codigo,
         ha.descricao,
         ha.inicio,
         ha.fim,
         0 AS diferenca
    FROM horas_ajustadas ha
   WHERE ha.inicio < ha.fim
  -- Turnos que terminam no dia seguinte
  UNION ALL
  SELECT ha.codigo,
         ha.descricao,
         CAST('00:00:00.000' AS TIME) AS inicio,
         ha.fim,
         -1 AS diferenca
    FROM horas_ajustadas ha
   WHERE ha.inicio > ha.fim
  UNION ALL
  SELECT ha.codigo,
         ha.descricao,
         ha.inicio,
         CAST('23:59:59.999' AS TIME) AS fim,
         0 AS diferenca
    FROM horas_ajustadas ha
   WHERE ha.inicio > ha.fim
)
SELECT t.codigo,
       t.descricao,
       t.inicio,
       t.fim,
       t.diferenca
  FROM turnos t

Then cross-reference the data with INNER JOIN in the created table:

...
SELECT d.serie,
       t.codigo,
       t.descricao,
       d.hora
  FROM dados d
       INNER JOIN turnos t ON CAST(d.hora AS TIME) BETWEEN t.inicio AND t.fim

Combining the two parts:

WITH horas_ajustadas AS
(
  SELECT h.codigo,
         h.descricao,
         CAST(h.inicio AS TIME) AS inicio,
         DATEADD(MINUTE, h.duracao, CAST(h.inicio AS TIME)) AS fim
    FROM horas h
),
turnos AS
(
  -- Turnos que iniciam e terminam no mesmo dia
  SELECT ha.codigo,
         ha.descricao,
         ha.inicio,
         ha.fim,
         0 AS diferenca
    FROM horas_ajustadas ha
   WHERE ha.inicio < ha.fim
  -- Turnos que terminam no dia seguinte
  UNION ALL
  SELECT ha.codigo,
         ha.descricao,
         CAST('00:00:00.000' AS TIME) AS inicio,
         ha.fim,
         -1 AS diferenca
    FROM horas_ajustadas ha
   WHERE ha.inicio > ha.fim
  UNION ALL
  SELECT ha.codigo,
         ha.descricao,
         ha.inicio,
         CAST('23:59:59.999' AS TIME) AS fim,
         0 AS diferenca
    FROM horas_ajustadas ha
   WHERE ha.inicio > ha.fim
)
SELECT d.serie,
       t.codigo,
       t.descricao,
       CONVERT(VARCHAR(10), DATEADD(DAY, t.diferenca, d.hora), 103) AS DATA
  FROM dados d
       INNER JOIN turnos t ON CAST(d.hora AS TIME) BETWEEN t.inicio AND t.fim
 ORDER BY d.serie

The result of query previous is as follows:

serie | dia         | codigo
------|-------------|-------
743   | 24/06/2016  | S03
744   | 24/06/2016  | S03
745   | 25/06/2016  | S01
746   | 25/06/2016  | S02

See working on SQL Fiddle.

  • As already commented with Marconcílio, the main difficulty is in case 744, because the date retroacts when the shift crosses day and the point beat was the day after the start of the shift. // For cases similar to the 744 series the date should be backdated...

  • @Josédiz yes, it is already foreseen in the explanation and resolution of my reply.

  • Note that in the expected result, informed by the author of the topic, for series 744 the day value should be 2016-06-24. Day 24, not 25.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.