Làm cách nào để tạo các ngày trong tuần lặp lại dưới dạng cột trong trục?


8

Tôi là người mới đến lập trình và cơ sở dữ liệu và tôi sẽ biết ơn sự giúp đỡ về kịch bản sau đây.

Tôi sử dụng PHP với SQL Server. Tôi đang xây dựng một hệ thống chấm công của nhân viên và tôi muốn tạo một bảng (trục) với các tháng là hàng và tất cả các ngày trong tuần dưới dạng cột (cho một năm cụ thể). Các giá trị trong các ô sẽ là số ngày (1, 2, 3 ... 31).

Màu nền của ô (đã tồn tại dưới dạng cột bảng) khai báo kiểu nghỉ của nhân viên. Bảng có các cột sau : employee_id, leave_date, leave_type, leave_type_color.

Tôi muốn đạt được một kết quả như dưới đây:

nhập mô tả hình ảnh ở đây

Cảm ơn bạn.


Cảm ơn bạn cho một vấn đề thú vị! Tôi không hào hứng với việc trộn dữ liệu và trình bày nhưng trong một số trường hợp có tất cả logic ở một nơi có thể thực tế.
Aaron Bertrand

Câu trả lời:


11

Phần phức tạp nhất của việc này chỉ là xây dựng lịch theo định dạng đó. Xoay vòng và bao quanh nó với HTML là khá dễ dàng. Trước tiên, hãy bắt đầu với điều này, bảng nhân viên của bạn với ngày nghỉ. leave_typedường như không liên quan đến vấn đề trong tay.

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

Thủ tục tôi đưa ra có vẻ như thế này (và cảnh báo: nó giả định @@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

Kết quả của cuộc gọi này:

EXEC dbo.BuildLeaveHTMLTable @EmployeeID = 1;

Trông như thế này (tôi dừng lại ở cột ngày thứ 7):

nhập mô tả hình ảnh ở đây

Bạn sẽ phải tự thêm <table>/ </table>trình bao bọc, nhưng đây là kết quả đầu ra trông như thế nào khi được đặt giữa chúng và được lưu dưới dạng HTML (và tất nhiên bạn có thể nâng cao hơn nữa bằng CSS):

! [nhập mô tả hình ảnh ở đây

Khi nghỉ rơi vào một ngày cuối tuần, màu sắc bỏ qua màu sắc cuối tuần, nhưng điều đó rất dễ điều chỉnh. Thay đổi cai nay đi:

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

Về điều này:

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

Để chuyển đổi một màu ở định dạng thập phân (như 65280) thành tương đương RGB ( 00FF00), bạn phải thực hiện một loạt các thao tác. Tôi sẽ xem xét việc lưu trữ nó dưới dạng hex hex ở vị trí đầu tiên, nhưng tôi đã cập nhật giải pháp ở đây với một cái gì đó tương tự như sau:

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

Vâng Aaron nói gì.
Rob Farley

2
Bạn thật kỳ lạ.
Erik Darling

Cảm ơn sự giúp đỡ. Tôi nhận được lỗi: Chuyển đổi không thành công khi chuyển đổi giá trị varchar '>' thành kiểu dữ liệu int.
Mike T

@MikeT Mã đó đã được kiểm tra đầy đủ, bạn đã thay đổi điều gì? Là leave_type_colorcột số?
Aaron Bertrand

1) "DECLARE @return_value int" có đóng vai trò khi tôi thực hiện quy trình trong SQL 2016 không? 2) Tôi đã thay đổi một vài tên cột vì bảng rời là một phép nối của 2 bảng khác.leave_type_color là số nguyên.
Mike T

1

Bắt đầu bằng cách xem xét những gì bạn muốn có như các cột, và về cơ bản đó là Tuần Tuần 1 Ngày 1 (Chủ Nhật), Tuần Tuần 1 Ngày 2 (Thứ Hai), qua đến Tuần Tuần 6 Ngày 7 (Thứ Bảy). Về cơ bản, ngày 1-42. Ngày 1 tháng 1 sau đó là Tuần Tuần 1 Ngày 2 tháng 1. Bây giờ tôi sẽ gọi đây là WeekPlusDay.

Để tìm ra nơi mỗi người bắt đầu, chỉ cần xem xét phần ngày trong ngày.

Sau đó, bộ dữ liệu của bạn chỉ cần bao gồm giá trị đó trong tuần WeekPlusDay và bạn hiển thị DayOfMonth.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.