我在人力资源管理系统工作。我的问题是在度假,
如果员工从19/3/2014
到5/4/2014
申请休假,则计算他每月需要18天而不是3天。
我在vacation
表
列:
emp_id | vac_type | from | to
现在,我该如何查询告诉我他在3月份的13天和4月份的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 |
我在这里的工作是基于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
--....