sql >> Database teknologi >  >> RDS >> Mysql

Hvordan laver man flere LEFT JOINs med ELLER bruger fuldt ud et sammensat indeks? (del 2)

For det første er det bedre at sende dette spørgsmål fra din anden. Grunden til, at du fik flere poster, er muligheden for, at en person klokkede ind og ud flere gange på samme dag baseret på deres vagter. Nu, hvordan man løser dette.

I MySQL kan du lave inline variabeldeklaration og tildelinger ved hjælp af "@"-variabler som en del af select FROM-sætningen. Det, jeg starter med, er en simpel joinforbindelse fra arbejdsdagen til vagtbordet (og tror, ​​jeg forstår dette nu), med nogle @variabler.

For hver person, der er tilsluttet skiftet, forudberegner jeg, hvor midten af ​​skiftet finder sted, f.eks. samme dag vs næste dag. Begynd2 og slut2 ser også ud til at være outliers for en mulig clock-in vs clock-out. Eksempel:Person 1 arbejder på skift 1. Skift 1 er defineret for en given arbejdsdag som

shiftcode   shiftbegin2  shiftbegin  shiftmid  shiftend  shiftend2
        1     04:00:00     08:00:00  12:00:00  17:30:00  21:30:00 

Så jeg tolker dette, som om jeg arbejder den 28. juni, skift 1,

June 28 @ 4am Earliest allowed clock-in time
June 28 @ 8am Actual beginning of shift
June 28 @ 12pm (afternoon) is the middle of the work day
June 28 @ 5:30pm is the end of the work day
June 28 @ 9:30pm is the max expected clock-out recognized for the shift

Tilsvarende for skift 2, som vil afslutte en overnatning

shiftcode   shiftbegin2  shiftbegin  shiftmid  shiftend  shiftend2
        2     12:00:00     17:30:00  21:00:00  05:30:00  09:30:00

June 28 @ 12pm (afternoon) Earliest allowed clock-in time
June 28 @ 5:30pm Actual beginning of shift
June 28 @ 9pm is the middle of the shift
June 29 @ 5:30am (day roll-over) is the end of the work day 
June 29 @ 9:30am (day roll-over) is the max expected clock-out for the shift

Så hvis alt dette er korrekt, forudbestemmer min indre forespørgsel alle disse intervaller for hver person, så jeg vil kun have 1 registrering pr. person pr. arbejdsdag, uanset hvor mange scanninger der er foretaget nedenfor.

select 
      wd.wdpercode,
      wd.wdshift,
      wd.wddate,
      s.shiftbegin,
      s.shiftend,
      s.shiftbegin2,
      s.shiftmid,
      s.shiftend2,
      @midDay := if( s.shiftbegin < s.shiftmid, wd.wddate, date_add( wd.wddate, interval 1 day )) as NewMidDay,
      @endDay := if( s.shiftbegin < s.shiftend, wd.wddate, date_add( wd.wddate, interval 1 day )) as NewEndDay,
      cast( concat(wd.wddate, ' ', s.shiftbegin2 ) as DateTime ) as EarliestClockIn,
      cast( concat(wd.wddate, ' ', s.shiftbegin ) as DateTime ) as BeginShift,
      cast( concat(@midDay, ' ', s.shiftmid ) as DateTime ) as MidShift,
      cast( concat( @endDay, ' ', s.shiftend ) as DateTime ) as EndShift,
      cast( concat( @endDay, ' ', s.shiftend2 ) as DateTime ) as MaxClockOut
   from
      ( select 
              @endDay := '', 
              @midDay := '' ) sqlvars,
      tb_workday wd
         join tb_shift s
            on wd.wdshift = s.shiftcode

Den inline-beregning af @midDay og @endDay er så jeg ikke behøver at bekymre mig om at slutte mig til den scannede ur-tabel og blive ved med at tilføje 1 dag midt i alt andet, der overvejes. Så i slutningen af ​​denne forespørgsel ville jeg ende med noget i stil med...Bemærk mellem person 1 normal vagt og person 2 nattevagt, den beregnede slutdato viser også roll-over datoerne

wdpercode  wdshift  wddate      shiftbegin  shiftend  shiftbegin2  shiftmid  shiftend2  NewMidDay   NewEndDay   EarliestClockIn   BeginShift        MidShift          EndShift          MaxClockOut
000001     1        2010-10-10  08:00       17:30     04:00        12:00     21:30      2010-10-10  2010-10-10  2010-10-10 04:00  2010-10-10 08:00  2010-10-10 12:00  2010-10-10 17:30  2010-10-10 21:30:00
000001     1        2010-10-11  08:00       17:30     04:00        12:00     21:30      2010-10-11  2010-10-11  2010-10-11 04:00  2010-10-11 08:00  2010-10-11 12:00  2010-10-11 17:30  2010-10-11 21:30:00
000001     1        2010-10-12  08:00       17:30     04:00        12:00     21:30      2010-10-12  2010-10-12  2010-10-12 04:00  2010-10-12 08:00  2010-10-12 12:00  2010-10-12 17:30  2010-10-12 21:30:00
000001     1        2010-10-13  08:00       17:30     04:00        12:00     21:30      2010-10-13  2010-10-13  2010-10-13 04:00  2010-10-13 08:00  2010-10-13 12:00  2010-10-13 17:30  2010-10-13 21:30:00

000002     2        2010-10-10  17:30       05:30     12:00        21:00     09:30      2010-10-10  2010-10-11  2010-10-10 12:00  2010-10-10 17:30  2010-10-10 21:00  2010-10-11 05:30  2010-10-11 09:30:00
000002     2        2010-10-11  17:30       05:30     12:00        21:00     09:30      2010-10-11  2010-10-12  2010-10-11 12:00  2010-10-11 17:30  2010-10-11 21:00  2010-10-12 05:30  2010-10-12 09:30:00
000002     2        2010-10-12  17:30       05:30     12:00        21:00     09:30      2010-10-12  2010-10-13  2010-10-12 12:00  2010-10-12 17:30  2010-10-12 21:00  2010-10-13 05:30  2010-10-13 09:30:00
000002     2        2010-10-13  17:30       05:30     12:00        21:00     09:30      2010-10-13  2010-10-14  2010-10-13 12:00  2010-10-13 17:30  2010-10-13 21:00  2010-10-14 05:30  2010-10-14 09:30:00

Du kunne fjerne de ekstra kolonner fra denne forespørgsel, men jeg inkluderede alle, så du kunne se/bekræfte, hvad værdierne er til overvejelse af hver række og dato for planlagt arbejde. Den forkortede liste, jeg stadig skal bruge, er

select 
      wd.wdpercode,
      @midDay := if( s.shiftbegin < s.shiftmid, wd.wddate, date_add( wd.wddate, interval 1 day )) as NewMidDay,
      @endDay := if( s.shiftbegin < s.shiftend, wd.wddate, date_add( wd.wddate, interval 1 day )) as NewEndDay,
      cast( concat(wd.wddate, ' ', s.shiftbegin2 ) as DateTime ) as EarliestClockIn,
      cast( concat(wd.wddate, ' ', s.shiftbegin ) as DateTime ) as BeginShift,
      cast( concat(@midDay, ' ', s.shiftmid ) as DateTime ) as MidShift,
      cast( concat( @endDay, ' ', s.shiftend ) as DateTime ) as EndShift,
      cast( concat( @endDay, ' ', s.shiftend2 ) as DateTime ) as MaxClockOut

Så hvis ovenstående er nøjagtigt, er vi nu nødt til at få uret ind og ud for hver person baseret på det MAKSIMUM interval beregnet ud fra denne forespørgsel, som KUNNE mere end én registrering pr. dato

wdpercode  EarliestClockIn    MidShift          MaxClockOut
000001     2010-10-10 04:00   2010-10-10 12:00  2010-10-10 21:30:00
000002     2010-10-10 12:00   2010-10-10 21:00  2010-10-11 09:30:00

Så her slutter jeg mig til scanningstiderne for alle datoer inden for det tidligste ur ind og maks. ur ud og bruger midshift som grundlag for at afgøre, om de klokkede ind sent frem for tidligt afgang. Jeg tilføjede de ekstra MIN() og MAX() for ankomst og afgang for en given person/vagt for at bekræfte, hvad du GØR OG burde se.

Formålet med MAX( IF() ) er KUN at fange den sene/tidlige status, HVIS de er sket. Da gruppen ved er pr. skift, kan den første rekord (klokke ind) være forsinket, og du vil have den tid, men den anden rekord for clocking er ikke gældende via midtskiftetiden og vil derfor være tom. Tilsvarende for at opdage tidlig afgang fra et skift.

select
      perPerson.wdPerCode,
      perPerson.BeginShift,
      perPerson.EndShift,
      min( TS.scScanTime ) as Arrival,
      max( TS.scScanTime ) as Departure,
      max( IF( TS.scScanTime > perPerson.BeginShift         
           AND TS.scScanTime <= perPerson.MidShift, TS.scScanTime, "" )) as LateArrival,
      max( IF( TS.scScanTime > perPerson.MidShift
           AND TS.scScanTime < perPerson.EndShift, TS.scScanTime, "" )) as EarlyDepart
   from
      ( select
              wd.wdpercode,
              @midDay := if( s.shiftbegin < s.shiftmid, wd.wddate, 
                 date_add( wd.wddate, interval 1 day )) as NewMidDay,
              @endDay := if( s.shiftbegin < s.shiftend, wd.wddate, 
                 date_add( wd.wddate, interval 1 day )) as NewEndDay,
              cast( concat(wd.wddate, ' ', s.shiftbegin2 ) as DateTime ) as EarliestClockIn,
              cast( concat(wd.wddate, ' ', s.shiftbegin ) as DateTime ) as BeginShift,
              cast( concat(@midDay, ' ', s.shiftmid ) as DateTime ) as MidShift,
              cast( concat( @endDay, ' ', s.shiftend ) as DateTime ) as EndShift,
              cast( concat( @endDay, ' ', s.shiftend2 ) as DateTime ) as MaxClockOut
           from
              ( select
                      @endDay := '',
                      @midDay := '' ) sqlvars,
              tb_workday wd
                 join tb_shift s
                    on wd.wdshift = s.shiftcode ) perPerson
         JOIN tb_scan TS
            on perPerson.wdpercode = TS.scpercode
            AND TS.scScanTime >= perPerson.EarliestClockIn
            AND TS.scScanTime <= perPerson.MaxClockOut
   group by
      perPerson.wdPerCode,
      perPerson.BeginShift;

Jeg oprettede tabeller og eksempeldata ud fra det, du har leveret via (hvoraf nogle af dine data ikke matchede eksempeldatoerne og -intervallerne, så jeg justerede for at gøre det).

CREATE TABLE `tb_scan` (
  `scpercode` varchar(6) DEFAULT NULL,
  `scscantime` datetime,
  KEY `all` (`scyear`,`scmonth`,`scday`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

insert into tb_scan 
( scpercode, scscantime ) 
values
( '000001', '2010-10-10 08:02:00' ),
( '000001', '2010-10-10 17:33:00' ),
( '000001', '2010-10-11 07:48:00' ),
( '000001', '2010-10-11 17:29:00' ),
( '000001', '2010-10-12 08:04:00' ),
( '000001', '2010-10-12 17:28:00' ),
( '000002', '2010-10-10 17:31:00' ),
( '000002', '2010-10-11 05:35:00' ),
( '000002', '2010-10-11 17:28:00' ),
( '000002', '2010-10-12 05:29:00' ),
( '000002', '2010-10-12 17:32:00' ),
( '000002', '2010-10-13 05:27:00' );

CREATE TABLE `tb_workday` (
  `wdpercode` varchar(6) DEFAULT NULL,
  `wdshift` varchar(1) DEFAULT NULL,
  `wddate` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

insert into tb_workday 
( wdpercode, wdshift, wddate )
values
( '000001', '1', '2010-10-10' ),
( '000001', '1', '2010-10-11' ),
( '000001', '1', '2010-10-12' ),
( '000001', '1', '2010-10-13' ),
( '000002', '2', '2010-10-10' ),
( '000002', '2', '2010-10-11' ),
( '000002', '2', '2010-10-12' ),
( '000002', '2', '2010-10-13' );


CREATE TABLE `tb_shift` (
  `shiftcode` varchar(1) DEFAULT NULL,
  `shiftbegin2` varchar(8) DEFAULT NULL,
  `shiftbegin` varchar(8) DEFAULT NULL,
  `shiftmid` varchar(8) DEFAULT NULL,
  `shiftend` varchar(8) DEFAULT NULL,
  `shiftend2` varchar(8) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

insert into tb_shift
( shiftcode, shiftbegin2, shiftbegin, shiftmid, shiftend, shiftend2 )
values
( '1', '04:00:00', '08:00:00', '12:00:00', '17:30:00', '21:30:00' ), 
( '2', '12:00:00', '17:30:00', '21:00:00', '05:30:00', '09:30:00' );

Eksempeldataene viser hver person med et 1:ankomme sent, 2:afgå tidligt, 3:ankomme sent OG tage af sted tidligt.

wdPerCode  BeginShift         EndShift           Arrival            Departure          LateArrival        EarlyDepart
000001     2010-10-10 08:00   2010-10-10 17:30   2010-10-10 08:02   2010-10-10 17:33   2010-10-10 08:02
000001     2010-10-11 08:00   2010-10-11 17:30   2010-10-11 07:48   2010-10-11 17:29                      2010-10-11 17:29
000001     2010-10-12 08:00   2010-10-12 17:30   2010-10-12 08:04   2010-10-12 17:28   2010-10-12 08:04   2010-10-12 17:28

000002     2010-10-10 17:30   2010-10-11 05:30   2010-10-10 17:31   2010-10-11 05:35   2010-10-10 17:31
000002     2010-10-11 17:30   2010-10-12 05:30   2010-10-11 17:28   2010-10-12 05:29                      2010-10-12 05:29
000002     2010-10-12 17:30   2010-10-13 05:30   2010-10-12 17:32   2010-10-13 05:27   2010-10-12 17:32   2010-10-13 05:27

For at optimere forespørgslen ville jeg ændre dit indeks på scanningstabellen

CREATE TABLE `tb_scan` (
  `scpercode` varchar(6) DEFAULT NULL,
  `scscantime` datetime,
  KEY `personDate` (`scpercode`, `scscantime` )



  1. Vælg ulåst række i Postgresql

  2. Hvordan opretter man forbindelse til MySQL-databasen?

  3. Hvordan man ikke bygger PostgreSQL 9.0-udvidelser på RPM-platforme

  4. Et eksempel til at demonstrere sårbarheden af ​​SQL-injektion og dens forebyggelse i Oracle