每月有两个日期

时间:2015-05-05 04:28:08

标签: c# sql-server sql-server-2008

我在人力资源管理系统工作。我的问题是在度假, 如果员工从19/3/20145/4/2014申请休假,则计算他每月需要18天而不是3天。

我在vacation

中存放假期

列:

emp_id | vac_type | from | to

现在,我该如何查询告诉我他在3月份的13天和4月份的5天?

5 个答案:

答案 0 :(得分:3)

很棒的问题!我能够找到一种方法来做到这一点,但我确实必须使用稍微不同的日期表示法(见下文)。

DECLARE @startDate DATETIME, @endDate DATETIME, @lastDayOfStartMonth INT

SET @startDate = '3/19/2014'
SET @endDate = '4/5/2014'

SELECT @lastDayOfStartMonth =
    1+DATEPART(dd, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,@startDate)+1,0)))

SELECT DATENAME(month, @startDate) AS [Month],
    @lastDayOfStartMonth - DATEPART(dd, @startDate) AS [DaysSpent],
    DATENAME(month, @endDate) AS [Month],
    DATEPART(dd, @endDate) AS [DaysSpent]

输出:

| Month | DaysSpent | Month | DaysSpent |
|-------|-----------|-------|-----------|
| March |        13 | April |         5 |

SQL Fiddle example

我在这里的工作是基于Pinal Dave的帖子SQL SERVER – Find Last Day of Any Month – Current Previous Next

的设计

<小时/>

处理超过两个月的日期

DECLARE @startDate DATETIME, @endDate DATETIME, @currentDate DATETIME, @currentDay INT
DECLARE @currentMonth INT, @lastDayOfStartMonth INT 
CREATE TABLE #VacationDays ([Month] VARCHAR(10), [DaysSpent] INT)

SET @startDate = '1/19/2014'
SET @endDate = '4/5/2014'
SET @currentMonth = DATEPART(mm, @startDate)
SET @currentDay = DATEPART(dd, @startDate)
SET @currentDate = @startDate

WHILE @currentMonth < DATEPART(mm, @endDate)
BEGIN
    SELECT @lastDayOfStartMonth =
        DATEPART(dd, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,@currentDate)+1,0)))
    PRINT @lastDayOfStartMonth
    INSERT INTO #VacationDays
    SELECT DATENAME(month, @currentDate) AS [Month],
        @lastDayOfStartMonth - @currentDay + 1 AS [DaysSpent]

    SET @currentDate = DATEADD(mm, 1, @currentDate)
    SET @currentMonth = @currentMonth + 1
    SET @currentDay = 1
END

IF DATEPART(mm, @startDate) = DATEPART(mm, @endDate)
BEGIN
    INSERT INTO #VacationDays
    SELECT DATENAME(month, @endDate) AS [Month],
        DATEPART(dd, @endDate) - DATEPART(dd, @startDate) + 1 AS [DaysSpent]
END
ELSE
BEGIN
    INSERT INTO #VacationDays
    SELECT DATENAME(month, @endDate) AS [Month],
        DATEPART(dd, @endDate) AS [DaysSpent]
END

SELECT * FROM #VacationDays
DROP TABLE #VacationDays

输出:

|    Month | DaysSpent |
|----------|-----------|
|  January |        13 |
| February |        28 |
|    March |        31 |
|    April |         5 |

SQL Fiddle example - 运行大约需要一分钟。它在SSMS的本地实例中运行得更快。

<小时/>

以下是它的工作原理

对于下面的示例,我使用的@startDate值为05-05-2015

CAST(0 AS DATETIME)的值是1900-01-01的日期,这意味着行DATEDIFF(m,0,@startDate)基本上要求,自1900年1月1日以来经过了多少个月?对于此示例,该值为1384

DATEADD(mm, DATEDIFF(m,0,@startDate)+1,0)DATEADD(mm, 1384+1,0)说,将1385个月添加到日期值0(或1900-01-01)。这将为我们提供DATETIME值本月的第一天 @ startDate的月份之后。对于我们的示例,2015-06-01

DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,@startDate)+1,0))DATEADD(s,-1,'2015-06-01')从下个月的第一天开始减1秒,给我们当月的最后一秒,或2015-05-31 23:59:59

然后我们使用DATEPART获取该日期的日期值:31

31是五月的最后一天。

答案 1 :(得分:1)

我不知道如何使用SQL查询直接在SQL中执行此操作。但是,如果您在C#代码中有假期的开始和结束日期,那么您可以通过执行以下操作来计算每个月的假期天数。这比我想象的要复杂得多,但它是我能想到的最好的。对于您的示例,此代码生成以下输出:

Vacation days in March:         13 days
Vacation days in April:         5 days

代码:

class Program
{
    class DateRange
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
    }

    static void Main(string[] args)
    {
        DateRange vacation = new DateRange();
        vacation.Start = new DateTime(2014, 3, 19);
        vacation.End = new DateTime(2014, 4, 5);

        // Assuming April 5 represents the last day of vacation, let's
        // add one to it, to show that his vacation actually ends on the
        // following day.
        vacation.End = vacation.End.AddDays(1);

        DateRange currentMonth = new DateRange();
        currentMonth.Start = new DateTime(vacation.Start.Year, vacation.Start.Month, 1);
        currentMonth.End = currentMonth.Start.AddMonths(1);

        while (currentMonth.Start < vacation.End)
        {
            Console.WriteLine("Vacation days in {0}: \t{1} days",
                currentMonth.Start.ToString("MMMM"),
                IntersectDates(currentMonth, vacation));

            currentMonth.Start = currentMonth.Start.AddMonths(1);
            currentMonth.End = currentMonth.End.AddMonths(1);
        }
    }

    // Returns the number of days represented by the intersection of the two
    // date ranges.
    static int IntersectDates(DateRange dateRange1, DateRange dateRange2)
    {
        DateTime startOfIntersection = MaxDate(dateRange1.Start, dateRange2.Start);
        DateTime endOfIntersection = MinDate(dateRange1.End, dateRange2.End);

        return (startOfIntersection < endOfIntersection) ?
            (int)(endOfIntersection - startOfIntersection).TotalDays :
            0;
    }

    static DateTime MinDate(DateTime d1, DateTime d2)
    {
        return (d1 < d2) ? d1 : d2;
    }

    static DateTime MaxDate(DateTime d1, DateTime d2)
    {
        return (d1 > d2) ? d1 : d2;
    }
}

答案 2 :(得分:1)

DECLARE @FromDate date = '2014-03-19', @ToDate date = '2014-05-04'

WITH CTE AS
(
    SELECT 
        YEAR(@FromDate) * 100 + MONTH(@FromDate) AS Month,
        DATEADD(DAY, -DAY(@FromDate) + 1, @FromDate) AS FirstDateOfMonth, 
        DATEADD(DAY, -1, DATEADD(MONTH, 1, DATEADD(DAY, -DAY(@FromDate) + 1, @FromDate))) AS LastDateOfMonth
    UNION ALL
    SELECT 
        YEAR(DATEADD(MONTH, 1, FirstDateOfMonth)) * 100 + MONTH(DATEADD(MONTH, 1, FirstDateOfMonth)) AS Month,
        DATEADD(MONTH, 1, FirstDateOfMonth), 
        DATEADD(DAY, -1, DATEADD(MONTH, 2, FirstDateOfMonth)) 
    FROM CTE 
    WHERE @ToDate >= LastDateOfMonth
)
SELECT 
    *,
    CASE
        -- Same month 
        WHEN YEAR(@FromDate) * 100 + MONTH(@FromDate) = YEAR(@ToDate) * 100 + MONTH(@ToDate) THEN DATEDIFF(DAY, @FromDate, @ToDate) + 1
        -- Get day from vacation start date to last date of month
        WHEN Month = YEAR(@FromDate) * 100 + MONTH(@FromDate) THEN DATEDIFF(DAY, @FromDate, LastDateOfMonth) + 1
        -- Get day from first date of month to vacation end date
        WHEN Month = YEAR(@ToDate) * 100 + MONTH(@ToDate) THEN DATEDIFF(DAY, FirstDateOfMonth, @ToDate) + 1
        -- Full month day
        ELSE DATEDIFF(DAY, FirstDateOfMonth, LastDateOfMonth) + 1
    END AS Day
FROM CTE

结果

Month       FirstDateOfMonth LastDateOfMonth Day
----------- ---------------- --------------- -----------
201403      2014-03-01       2014-03-31      13
201404      2014-04-01       2014-04-30      30
201405      2014-05-01       2014-05-31      4

答案 3 :(得分:1)

对于像这样的应用程序,日期表可以以合理的性能成本大大简化查询。它只不过是

SELECT emp_id, d.month, COUNT(*) as days 
FROM Vacations v 
INNER JOIN dates d on d.theDate BETWEEN v.Start and v.End
GROUP BY emp_id, d.month

有关如何创建日期表的示例,请参阅Create Date Dimension Table in SQL Server

这也简化了复杂的查询,例如假期中的工作日数:

SELECT emp_id, COUNT(*) as days 
FROM Vacations v 
INNER JOIN dates d on d.theDate BETWEEN v.Start and v.End
WHERE d.IsWeekend = 0
GROUP BY emp_id

按付款期或季度分组:

SELECT emp_id, d.year, d.quarter, COUNT(*) as days 
FROM Vacations v 
INNER JOIN dates d on d.theDate BETWEEN v.Start and v.End
GROUP BY emp_id, d.year, d.quarter

答案 4 :(得分:1)

我在calendar语句中创建了一个需要WITH的查询。在此日历中,您可以设置月份名称和月份日期 - 闰年 - 。
对于假期表中使用的任何一年,日历将是灵活的 - 自动生成闰年的正确日期 - 还支持从一年到下一年的假期。

;WITH calendar AS (
SELECT  years.[Year], months.monthId, months.[monthName],
        CASE ISDATE(CONVERT(varchar, years.[Year])+'-'+CONVERT(varchar,months.monthId)+'-'+CONVERT(varchar,months.monthDays)) 
            WHEN 1 THEN months.monthDays
            ELSE months.monthDays-1
        END AS monthDays,
        CONVERT(datetime, (CONVERT(varchar, years.[Year])+'-'+CONVERT(varchar,months.monthId)+'-1')) AS startDay,
        CASE ISDATE(CONVERT(varchar, years.[Year])+'-'+CONVERT(varchar,months.monthId)+'-'+CONVERT(varchar,months.monthDays)) 
            WHEN 1 THEN CONVERT(datetime, CONVERT(varchar, years.[Year])+'-'+CONVERT(varchar,months.monthId)+'-'+CONVERT(varchar,months.monthDays))
            ELSE CONVERT(datetime, CONVERT(varchar, years.[Year])+'-'+CONVERT(varchar,months.monthId)+'-'+CONVERT(varchar,months.monthDays-1))
        END AS EndDay
FROM 
    (SELECT DISTINCT YEAR(vi.[from]) As [Year] FROM vacation vi
     UNION
     SELECT DISTINCT YEAR(vi.[to]) As [Year] FROM vacation vi
    ) As years
    CROSS JOIN
    (SELECT 1 As monthId, 31 As monthDays, 'January' As [monthName] UNION ALL
     SELECT 2, 29, 'February' UNION ALL
     SELECT 3, 31, 'March' UNION ALL
     SELECT 4, 30, 'April' UNION ALL
     SELECT 5, 31, 'May' UNION ALL
     SELECT 6, 30, 'June' UNION ALL
     SELECT 7, 31, 'July' UNION ALL
     SELECT 8, 31, 'August' UNION ALL
     SELECT 9, 30, 'September' UNION ALL
    SELECT 10, 31, 'October' UNION ALL
    SELECT 11, 30, 'November' UNION ALL
    SELECT 12, 31, 'December' ) As months
)
SELECT c.[year], c.monthId, c.[monthName],
    CASE 
        WHEN v.emp_id IS NULL THEN 0
        WHEN c.monthId = MONTH(v.[from]) THEN DATEDIFF(DAY, v.[from], c.EndDay) + 1
        WHEN c.monthId = MONTH(v.[to]) THEN DATEDIFF(DAY, c.startDay, v.[to]) + 1
        WHEN c.monthId BETWEEN MONTH(v.[from]) AND MONTH(v.[to]) THEN c.monthDays
    END As vacationDays,
    CASE 
        WHEN v.emp_id IS NULL THEN c.monthDays
        WHEN c.monthId = MONTH(v.[from]) THEN DATEDIFF(DAY, c.startDay, v.[from]) 
        WHEN c.monthId = MONTH(v.[to]) THEN DATEDIFF(DAY, v.[to], c.EndDay)
        WHEN c.monthId BETWEEN MONTH(v.[from]) AND MONTH(v.[to]) THEN 0
    END As nonvacationDays,
    c.monthDays

FROM 
    calendar c
    LEFT JOIN
    vacation v ON c.monthId BETWEEN MONTH(v.[from]) AND MONTH(v.[to])

结果如下:

year | monthId | monthName | vacationDays | nonvacationDays | monthDays
-----+---------+-----------+--------------+-----------------+------------
2014 | 1       | January   | 0            | 31              | 31
2014 | 2       | February  | 0            | 28              | 28
2014 | 3       | March     | 13           | 18              | 31
2014 | 4       | April     | 5            | 25              | 30
2014 | 5       | May       | 0            | 31              | 31
--....