Come creare giorni feriali ricorrenti come colonne in un perno?


8

Sono alle prime armi con la programmazione e i database e sarei grato per l'aiuto nel seguente scenario.

Uso PHP con SQL Server. Sto creando un sistema di presenza dei dipendenti e vorrei creare una tabella (pivot) con mesi come righe e tutti i nomi dei giorni della settimana come colonne (per un anno specifico). I valori nelle celle saranno il numero del giorno (1, 2, 3 ... 31).

Il colore di sfondo della cella (esiste già come colonna della tabella) dichiara il tipo di congedo dei dipendenti. La tabella include le seguenti colonne: employee_id, leave_date, leave_type, leave_type_color.

Voglio ottenere un risultato come di seguito:

inserisci qui la descrizione dell'immagine

Grazie.


Grazie per un problema interessante! Non sono entusiasta di mescolare dati e presentazioni, ma in alcuni casi avere tutta la logica in un unico posto può essere pratico.
Aaron Bertrand

Risposte:


11

La parte più complessa di questo è solo la costruzione del calendario in quel formato. Ruotare e circondarlo con HTML è abbastanza semplice. Innanzitutto, cominciamo con questo, la tabella dei dipendenti con le date delle ferie. leave_typenon sembrava rilevante per il problema in questione.

CREATE TABLE dbo.EmpLeave
(
  EmployeeID int,
  leave_date date,
  leave_type_color char(6)
);

INSERT dbo.EmpLeave(EmployeeID,leave_date,leave_type_color)
  VALUES(1,'2018-01-02','7777cc'),(1,'2018-04-01','ffffac');

La procedura che mi è venuta in mente è simile a questa (e avvertenza: presuppone @@DATEFIRST = 7):

CREATE PROCEDURE dbo.BuildLeaveHTMLTable
  @EmployeeID int,
  @Year smallint = NULL
AS
BEGIN
  SET NOCOUNT ON;
  SET @Year = COALESCE(@Year, DATEPART(YEAR, GETDATE()));
  DECLARE @FirstDay date = DATEADD(YEAR, @Year-1900, 0);

  ;WITH Numbers AS ( -- 366 possible days (leap year)
    SELECT n = 1 UNION ALL SELECT n + 1 FROM Numbers WHERE n <= 365
  ),
  Calendar AS ( -- a year's worth of dates and dateparts 
    SELECT [Date] = d,
      MonthStart = DATEADD(DAY, 1-DAY(d),d),
      Y  = CONVERT(smallint, DATEPART(YEAR,   d)),
      M  = CONVERT(tinyint,  DATEPART(MONTH,  d)),
      D  = CONVERT(tinyint,  DATEPART(DAY,    d)),
      WY = CONVERT(tinyint,  DATEPART(WEEK,   d)),
      DW = CONVERT(tinyint,  DATEPART(WEEKDAY,d))
    FROM
    (
      SELECT d = CONVERT(date,DATEADD(DAY, n-1, @FirstDay)) FROM Numbers
    ) AS c WHERE YEAR(d) = @Year -- in case it's not a leap year
  ),
  BaseSlots AS ( -- base set of 37 ints 
   -- month can be spread across 6 weeks, but no more than 2 days in 6th week
    SELECT TOP (37) slot = n FROM Numbers ORDER BY n
  ),
  Months AS ( -- base set of 12 ints
    SELECT TOP (12) m = slot FROM BaseSlots ORDER BY slot
  ),
  SlotAlignment AS ( -- align days of week to slot numbers
    -- this is the most cryptic part of this solution
    -- determines which set of 7 slots, and which slot 
    -- exactly, a given date will appear under
    SELECT c.*, slot = DW+(c.WY+1-DATEPART(WEEK,c.MonthStart)-1)*7
      FROM Calendar AS c 
      INNER JOIN Months AS m ON c.M = m.m
  ),
  SlotMatrix AS ( -- extrapolate actual dates to 37 x 12 matrix
    SELECT m.m, s.slot, sa.[Date] 
      FROM BaseSlots AS s 
      CROSS JOIN Months AS m
      LEFT OUTER JOIN SlotAlignment AS sa
      ON sa.m = m.m AND sa.slot = s.slot
  ),
  FinalHTML AS ( -- build some HTML!
    SELECT m = '<!-- ' + RIGHT('0' + RTRIM(m), 2) + ' -->', 
      slot, cell = CASE WHEN slot = 1 THEN '<tr><th>' 
        + COALESCE(DATENAME(MONTH,DATEADD(MONTH, m-1, 0)),'') 
        + '</th>' ELSE '' END + '<td' + COALESCE(' bgcolor=#' 
        + RIGHT(CONVERT(varchar(10),CONVERT(varbinary(8), el.leave_type_color),1),6),
          CASE WHEN DATEPART(WEEKDAY, [Date]) IN (1,7) 
          THEN ' bgcolor=#cccccc' ELSE '' END)
        + '>' + COALESCE(RTRIM(DATEPART(DAY,[Date])), '&nbsp;')
        + '</td>' + CASE WHEN slot = 37 THEN '</tr>' ELSE '' END
      FROM SlotMatrix AS q LEFT OUTER JOIN dbo.EmpLeave AS el
      ON q.Date = el.leave_date
      AND el.EmployeeID = @EmployeeID
  ) -- now turn it sideways
  SELECT m = '<!-- 00 -->', 
    [1]  = '<tr><th>Month</th><th>S</th>',    [2]  = '<th>M</th>', 
    [3]  = '<th>T</th>', [4]  = '<th>W</th>', [5]  = '<th>T</th>', 
    [6]  = '<th>F</th>', [7]  = '<th>S</th>', [8]  = '<th>S</th>', 
    [9]  = '<th>M</th>', [10] = '<th>T</th>', [11] = '<th>W</th>',
    [12] = '<th>T</th>', [13] = '<th>F</th>', [14] = '<th>S</th>', 
    [15] = '<th>S</th>', [16] = '<th>M</th>', [17] = '<th>T</th>',
    [18] = '<th>W</th>', [19] = '<th>T</th>', [20] = '<th>F</th>', 
    [21] = '<th>S</th>', [22] = '<th>S</th>', [23] = '<th>M</th>',
    [24] = '<th>T</th>', [25] = '<th>W</th>', [26] = '<th>T</th>', 
    [27] = '<th>F</th>', [28] = '<th>S</th>', [29] = '<th>S</th>', 
    [30] = '<th>M</th>', [31] = '<th>T</th>', [32] = '<th>W</th>', 
    [33] = '<th>T</th>', [34] = '<th>F</th>', [35] = '<th>S</th>',
    [36] = '<th>S</th>', [37] = '<th>M</th>'
  UNION ALL
  (
    SELECT * FROM FinalHTML PIVOT (MAX(cell) FOR slot IN 
    (
     [1], [2], [3], [4], [5], [6], [7], [8], [9], [10],[11],[12],[13],[14],
     [15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],
     [29],[30],[31],[32],[33],[34],[35],[36],[37]
    )) AS p
  )
  ORDER BY m OPTION (MAXRECURSION 366);
END
GO

Risultati di questa chiamata:

EXEC dbo.BuildLeaveHTMLTable @EmployeeID = 1;

Assomiglia a questo (mi sono fermato alla colonna del 7 ° giorno):

inserisci qui la descrizione dell'immagine

Dovrai aggiungere tu stesso <table>/ </table>wrapper, ma ecco come appare l'output quando inserito tra questi e salvato come HTML (e ovviamente puoi migliorarlo ulteriormente con CSS):

! [inserisci la descrizione dell'immagine qui

Quando il congedo cade durante un fine settimana, il colore del congedo supera il colore del fine settimana, ma è facile da regolare. Cambia questo:

  + COALESCE(' bgcolor=#' + RTRIM(el.leave_type_color),
      CASE WHEN DATEPART(WEEKDAY, [Date]) IN (1,7) 
      THEN ' bgcolor=#cccccc' ELSE '' END)

A questo:

  + CASE WHEN DATEPART(WEEKDAY, [Date]) IN (1,7) 
      THEN ' bgcolor=#cccccc' ELSE COALESCE(' bgcolor=#' 
      + RTRIM(el.leave_type_color), '') END

Per convertire un colore in formato decimale (come 65280) nel suo equivalente RGB ( 00FF00), devi fare un sacco di manipolazioni. Vorrei considerare la memorizzazione come esadecimale RGB in primo luogo, ma ho aggiornato la soluzione qui con qualcosa di simile a questo:

SELECT RIGHT(CONVERT(varchar(10),CONVERT(varbinary(8), 65280),1),6);

Sì. Quello che ha detto Aaron.
Rob Farley,

2
Sei così strano.
Erik Darling,

Grazie per l'aiuto. Ricevo l'errore: la conversione non è riuscita durante la conversione del valore varchar '>' nel tipo di dati int.
Mike T,

@MikeT Quel codice è stato completamente testato, cosa hai cambiato? La leave_type_colorcolonna è numerica?
Aaron Bertrand

1) "DECLARE @return_value int" svolge un ruolo quando eseguo la procedura in SQL 2016? 2) Ho cambiato alcuni nomi di colonna perché la tabella di partenza è un join di altre 2 tabelle.leave_type_color è intero.
Mike T,

1

Inizia considerando quello che vuoi avere come colonne, e in pratica è "Settimana 1 Giorno 1 (Dom)", "Settimana 1 Giorno 2 (Lun)", fino a "Settimana 6 Giorno 7 (Sab)". In sostanza, giorno 1-42. Il 1 ° gennaio è quindi "Settimana 1 Giorno 2" di gennaio. Chiamerò questo WeekPlusDay per ora.

Per capire dove inizia ognuno, considera solo la parte del giorno della settimana della data.

Il tuo set di dati deve solo includere quel valore "WeekPlusDay" e visualizzi DayOfMonth.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.