Bagaimana cara membuat hari kerja berulang sebagai kolom dalam pivot?

8

Saya pemula dalam pemrograman dan database dan saya akan berterima kasih atas bantuan pada skenario berikut.

Saya menggunakan PHP dengan SQL Server. Saya sedang membangun sistem absensi karyawan dan saya ingin membuat tabel (pivot) dengan bulan sebagai baris dan semua nama hari kerja sebagai kolom (untuk tahun tertentu). Nilai dalam sel akan menjadi jumlah hari (1, 2, 3 ... 31).

Warna latar belakang sel (sudah ada sebagai kolom tabel) menyatakan tipe cuti karyawan. Tabel ini memiliki kolom berikut: employee_id, leave_date, leave_type, leave_type_color.

Saya ingin mencapai hasil seperti di bawah ini:

masukkan deskripsi gambar di sini

Terima kasih.

Mike T
sumber
Terima kasih atas masalah yang menarik! Saya tidak senang tentang pencampuran data dan presentasi tetapi dalam beberapa kasus memiliki semua logika di satu tempat bisa praktis.
Aaron Bertrand

Jawaban:

11

Bagian paling kompleks dari ini adalah hanya membangun kalender dalam format itu. Berputar dan mengelilinginya dengan HTML cukup mudah. Pertama, mari kita mulai dengan ini, tabel karyawan Anda dengan tanggal cuti. leave_typetampaknya tidak relevan dengan masalah yang dihadapi.

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');

Prosedur yang saya buat tampak seperti ini (dan peringatan: diasumsikan @@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

Hasil dari panggilan ini:

EXEC dbo.BuildLeaveHTMLTable @EmployeeID = 1;

Terlihat seperti ini (saya berhenti di kolom 7 hari):

masukkan deskripsi gambar di sini

Anda harus menambahkan <table>/ </table>wrapper sendiri, tetapi inilah hasilnya seperti ketika dimasukkan di antara mereka dan disimpan sebagai HTML (dan tentu saja Anda dapat lebih meningkatkannya dengan CSS):

! [masukkan deskripsi gambar di sini

Ketika cuti jatuh pada akhir pekan, warna cuti mengalahkan warna akhir pekan, tetapi itu mudah disesuaikan. Ubah ini:

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

Untuk ini:

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

Untuk mengonversi warna dalam format desimal (seperti 65280) ke RGB-nya ( 00FF00), Anda harus melakukan banyak manipulasi. Saya akan mempertimbangkan menyimpannya sebagai RGB hex di tempat pertama, tetapi saya memperbarui solusi di sini dengan sesuatu yang mirip dengan ini:

SELECT RIGHT(CONVERT(varchar(10),CONVERT(varbinary(8), 65280),1),6);
Aaron Bertrand
sumber
Ya. Apa yang Harun katakan.
Rob Farley
2
Anda begitu aneh.
Erik Darling
Terima kasih atas bantuannya. Saya mendapatkan kesalahan: Konversi gagal saat mengonversi nilai varchar '>' ke int tipe data.
Mike T
@ MikeT kode itu diuji sepenuhnya, apa yang Anda ubah? Apakah leave_type_colorkolom angka?
Aaron Bertrand
1) Apakah "DECLARE @return_value int" berperan ketika saya menjalankan prosedur di SQL 2016? 2) Saya mengubah beberapa nama kolom karena tabel meninggalkan adalah gabungan dari 2 tables.leave_type_color lainnya adalah integer.
Mike T
1

Mulailah dengan mempertimbangkan apa yang ingin Anda miliki sebagai kolom, dan itu pada dasarnya “Minggu 1 Hari 1 (Minggu)”, “Minggu 1 Hari 2 (Senin)”, hingga “Minggu 6 Hari 7 (Sabtu)”. Intinya, Hari 1-42. 1 Januari lalu "Minggu 1 Hari 2" Januari. Saya akan menelepon WeekPlusDay ini sekarang.

Untuk mengetahui di mana masing-masing dimulai, pertimbangkan saja bagian hari kerja dari tanggal tersebut.

Kumpulan data Anda kemudian hanya harus memasukkan nilai "WeekPlusDay" itu, dan Anda menampilkan DayOfMonth.

Rob Farley
sumber