传递周日名称以获取SQL中最近的日期

时间:2015-01-03 17:00:00

标签: sql-server date

我正在处理一个处理频率值的查询(即星期一,星期二等等。 - 考虑作业)。

所以在我的查询中,我目前有

的结果
jobId:1, personId:100, frequencyVal: 'Mondays'
jobId:2, personId:101, frequencyVal: 'Saturdays'

我需要的是frequencyVal的下一个4个未来(或当前)日期。

所以,如果今天是2015年1月3日

我需要我的结果集

jobId:1, personId:100, frequencyVal: 'Mondays', futureDates: '1/5,1/12,1/19,1/26'
jobId:2, personId:102, frequencyVal: 'Saturdays', futureDates: '1/3,1/10,1/17,1/24'

我正在看以下帖子: How to find the Nearest (day of the week) for a given date

但是这会将其设置为特定日期。我正在看这是一个Web应用程序,我想要当前日期的日期。因此,如果我尝试在下周二运行此查询,则jobId:1 would remove the 1/5 and add the 2/2的未来日期。

有没有办法传递工作日值以获得下一个最近的日期?

7 个答案:

答案 0 :(得分:2)

我更喜欢这种查询的日历表。实际上,对于大多数查询,我更喜欢日历表上的日历表。这是一个最小的。我在生产中使用的那个有更多列和更多行。 (100年的数据只有37k行。)

create table calendar (
  cal_date date not null primary key,
  day_of_week varchar(15)
  );

insert into calendar (cal_date) values 
('2015-01-01'), ('2015-01-02'), ('2015-01-03'), ('2015-01-04'),
('2015-01-05'), ('2015-01-06'), ('2015-01-07'), ('2015-01-08'),
('2015-01-09'), ('2015-01-10'), ('2015-01-11'), ('2015-01-12'),
('2015-01-13'), ('2015-01-14'), ('2015-01-15'), ('2015-01-16'),
('2015-01-17'), ('2015-01-18'), ('2015-01-19'), ('2015-01-20'),
('2015-01-21'), ('2015-01-22'), ('2015-01-23'), ('2015-01-24'),
('2015-01-25'), ('2015-01-26'), ('2015-01-27'), ('2015-01-28'),
('2015-01-29'), ('2015-01-30'), ('2015-01-31'),

('2015-02-01'), ('2015-02-02'), ('2015-02-03'), ('2015-02-04'),
('2015-02-05'), ('2015-02-06'), ('2015-02-07'), ('2015-02-08'),
('2015-02-09'), ('2015-02-10'), ('2015-02-11'), ('2015-02-12'),
('2015-02-13'), ('2015-02-14'), ('2015-02-15'), ('2015-02-16'),
('2015-02-17'), ('2015-02-18'), ('2015-02-19'), ('2015-02-20'),
('2015-02-21'), ('2015-02-22'), ('2015-02-23'), ('2015-02-24'),
('2015-02-25'), ('2015-02-26'), ('2015-02-27'), ('2015-02-28')
;

update calendar
set day_of_week = datename(weekday, cal_date);

alter table calendar 
alter column day_of_week varchar(15) not null;

alter table calendar
add constraint cal_date_matches_dow
check (datename(weekday, cal_date) = day_of_week);

create index day_of_week_ix on calendar (day_of_week);

设置权限以便

  • 每个人都可以选择,但
  • 几乎没有人可以插入新行,
  • 甚至更少的人可以删除行。

(或者写一个可以保证没有间隙的约束。我认为你可以在SQL Server中做到这一点。)

您可以使用非常简单的SQL语句选择今天接下来的四个星期一。 (当前日期是2015-01-05,这是星期一。)

select top 4 cal_date
from calendar
where cal_date > convert(date, getdate())
  and day_of_week = 'Monday'
order by cal_date;
CAL_DATE
--
2015-01-12
2015-01-19
2015-01-26
2015-02-02

对我来说,这是一个巨大的优势。没有程序代码。简单的SQL显然是正确的。大赢。

答案 1 :(得分:1)

没有内置功能。但是你可以尝试这个,你可以把它放在一个标量值函数中:

DECLARE @WeekDay VARCHAR(10) = 'Monday';
DECLARE @WeekDayInt INT;

SELECT @WeekDayInt = CASE @WeekDay
                     WHEN 'SUNDAY'    THEN 1 
                     WHEN 'MONDAY'    THEN 2 
                     WHEN 'TUESDAY'   THEN 3 
                     WHEN 'WEDNESDAY' THEN 4 
                     WHEN 'THURSDAY'  THEN 5 
                     WHEN 'FRIDAY'    THEN 6
                     WHEN 'SATURDAY'  THEN 7 END

SELECT CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + @WeekDayInt) % 7, GETDATE())) AS NearestDate

<强>更新

看起来雷达是对的,这是解决方案:

DECLARE @WeekDay    VARCHAR(10) = 'Monday';
DECLARE @WeekDayInt INT;
DECLARE @Date       DATETIME = GETDATE();

SELECT @WeekDayInt = CASE @WeekDay
                     WHEN 'SUNDAY'    THEN 1 
                     WHEN 'MONDAY'    THEN 2 
                     WHEN 'TUESDAY'   THEN 3 
                     WHEN 'WEDNESDAY' THEN 4 
                     WHEN 'THURSDAY'  THEN 5 
                     WHEN 'FRIDAY'    THEN 6
                     WHEN 'SATURDAY'  THEN 7 END
DECLARE @Diff INT = DATEPART(WEEKDAY, @Date) - @WeekDayInt;
SELECT CONVERT(DATE, DATEADD(DAY, CASE WHEN @Diff >= 0 THEN 7 - @Diff ELSE ABS(@Diff) END, @Date)) AS NearestDate

答案 2 :(得分:1)

您的样本表

create table #t
(
    jobId int,
    personId int,
    frequencyVal varchar(10)
);

insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');

查询1:选择当月最近4周的特定工作日

-- Gets first day of month
DECLARE @FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)

;WITH  CTE as
(
     -- Will find all dates in current month
     SELECT CAST(@FIRSTDAY AS DATE) as DATES
     UNION ALL
     SELECT DATEADD(DAY,1,DATES)    
     FROM    CTE
     WHERE   DATES < DATEADD(MONTH,1,@FIRSTDAY)
 )
,CTE2 AS
(
   -- Join the #t table with  CTE on the datename+'s' 
   SELECT jobId,personId,frequencyVal,DATES,
   -- Get week difference for each weekday        
   DATEDIFF(WEEK,DATES,GETDATE()) WEEKDIFF,
   -- Count the number of weekdays in a month
   COUNT(DATES) OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES)) WEEKCOUNT
   FROM CTE
   JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal 
   WHERE MONTH(DATES)= MONTH(GETDATE())   
)
-- Converts to CSV and make sure that only nearest 4 week of days are generated for month
SELECT  DISTINCT C2.jobId,C2.personId,frequencyVal,
         SUBSTRING(
        (SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/'  + 
                       CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
        FROM CTE2 
        WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal AND
                       ((WEEKDIFF<3 AND WEEKDIFF>-3 AND WEEKCOUNT = 5) OR WEEKCOUNT <= 4)
        ORDER BY CTE2.DATES
        FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2

例如,在Query2中,

的最近日期(这里我们以星期六为例)
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/10,01/17,01/24,01/31

查询2:选择与月份无关的特定工作日的下一个4周的日期

;WITH  CTE as
(
     -- Will find the next 4 week details
     SELECT CAST(GETDATE() AS DATE) as DATES
     UNION ALL
     SELECT DATEADD(DAY,1,DATES)    
     FROM    CTE
     WHERE   DATES < DATEADD(DAY,28,GETDATE())
 )
,CTE2 AS
(
   -- Join the #t table with  CTE on the datename+'s' 
   SELECT jobId,personId,frequencyVal, DATES,
   ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
   FROM CTE
   JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal  
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT  DISTINCT C2.jobId,C2.personId,frequencyVal,   
        SUBSTRING(
        (SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/'  + 
                       CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
        FROM CTE2 
        WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal 
              AND DATECNT < 5
        ORDER BY CTE2.DATES
        FOR XML PATH('')),2,200000) futureDates
        FROM CTE2 C2

如果GETDATE()(如果是星期六)

,则输出如下
2015-01-05 - 1/10, 1/17, 1/24, 1/31
2015-01-24 - 1/24, 1/31, 2/7, 2/14

答案 3 :(得分:0)

尝试此操作 - 根据king.code's回答来获取最近的日期。

create table #t
(
    jobId int,
    personId int,
    frequencyVal varchar(10)
);

insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');

WITH cte(n) AS
(
    SELECT 0
    UNION ALL
    SELECT n+1 FROM cte WHERE n < 3
)

select #t.jobId, #t.personId, #t.frequencyVal, STUFF(a.d, 1, 1, '') AS FutureDates
from #t
cross apply (SELECT CASE #t.frequencyVal
                         WHEN 'SUNDAYS'    THEN 1 
                         WHEN 'MONDAYS'    THEN 2 
                         WHEN 'TUESDAYS'   THEN 3 
                         WHEN 'WEDNESDAYS' THEN 4 
                         WHEN 'THURSDAYS'  THEN 5 
                         WHEN 'FRIDAYS'    THEN 6
                         WHEN 'SATURDAYS'  THEN 7 
                    END)tranlationWeekdays(n)
cross apply (select ',' +  CONVERT(varchar(10),  CONVERT(date,dateadd(WEEK, cte.n,CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + tranlationWeekdays.n) % 7, GETDATE()))))) from cte FOR XML PATH('')) a(d);

drop table #t;

答案 4 :(得分:0)

我认为这是一种更简单的方式,我认为它符合您的要求。

请注意,我已将您的frequency_val列更改为一个整数,该整数表示从SQL Server角度来看星期几,并添加了一个计算列,以说明如何从中轻松派生日期名称。


declare @t table
(
    jobId int,
    personId int,
    --frequencyVal varchar(10)
    frequency_val int,
    frequency_day as datename(weekday,frequency_val -1) + 's'
);

declare @num_occurances int = 4 declare @from_date date = dateadd(dd,3,getdate()) -- this will allow you to play with the date simply by changing the increment value

insert into @t values (1,100,1),--'Mondays'), (2,101,6),--'Saturdays'); (3,101,7),--'Saturdays'); (4,100,2)--'Mondays'), --select * from @t

;with r_cte (days_ahead, occurance_date) as (select 0, convert(date,@from_date,121) union all select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, @from_date),121) from r_cte where r_cte.days_ahead < 7 * @num_occurances ) select t.*, r_cte.occurance_date from @t t inner join r_cte on DATEPART(WEEKDAY, dateadd(dd,@@DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val

答案 5 :(得分:0)

试试这个,

DECLARE @YEAR INT=2015
DECLARE @MONTH INT=1
DECLARE @DAY INT=1
DECLARE @DATE DATE = (SELECT DateFromParts(@Year, @Month, @Day))
DECLARE @TOTAL_DAYS INT =(SELECT DatePart(DY, @DATE));

WITH CTE1
     AS (SELECT T_DAY=(SELECT DateName(DW, @DATE)),
                @DATE AS T_DATE,
                @DAY  AS T_DDAY
         UNION ALL
         SELECT T_DAY=(SELECT DateName(DW, DateAdd(DAY, T_DDAY + 1, @DATE))),
                DateAdd(DAY, T_DDAY + 1, @DATE) AS T_DATE,
                T_DDAY + 1
         FROM   CTE1
         WHERE  T_DDAY + 1 <= 364)
SELECT DISTINCT T_DAY,
       Stuff((SELECT ',' + CONVERT(VARCHAR(30), T_DATE)
              FROM   CTE1 A
              WHERE  A.T_DAY=CTE1.T_DAY AND A.T_DATE > GetDate() AND A.T_DATE<(DATEADD(WEEK,4,GETDATE())) 
              FOR XML PATH('')), 1, 1, '') AS FUTURE
FROM   CTE1
ORDER  BY T_DAY
OPTION (MAXRECURSION 365) 

答案 6 :(得分:0)

在已经给出的一些答案中看到DATENAME的使用后,我想指出DATENAME的返回值可能会因您{{3}而异但是,您可以保存当前语言设置并确保使用us_english,这样您就可以放心使用英文工作日名称。

现在,这是一种稍微不同的方法,可以使用用户定义的表值函数来获取属于特定(已知)工作日的4个下一个日期,这些函数允许创建数字序列表(是的,这是一个漂亮的沉闷的功能,你必须传递MaxValue更大的MinValue,但如果需要,这可以很容易地增强,但是嘿,它完成了工作)。使用该函数跨越一个超过28个值的表(接下来28天应该包括接下来的4个相关工作日;)),在DATEADD上应用GETDATE并减少结果集WHERE仅限那些具有正确工作日的值:

CREATE FUNCTION GetIntSequence(@MinValue INT, @MaxValue INT)
RETURNS @retSequence TABLE
(
    IntValue INT NOT NULL
)
BEGIN
    DECLARE @i INT = (SELECT @MinValue)

    WHILE @i <= @MaxValue
    BEGIN
        INSERT INTO @retSequence (IntValue) SELECT @i

        SELECT @i = @i + 1
    END

    RETURN
END

GO
DECLARE @weekDay NVARCHAR(MAX) = 'Monday'   --(or Tuesday, wednesday, ...)

--save current language setting
DECLARE @languageBackup NVARCHAR(MAX) = (SELECT @@LANGUAGE)
--ensure us english language setting for reliable weekday names
SET LANGUAGE us_english;


SELECT FourWeeks.SomeDay FROM
    (
        SELECT
                DATEADD(DAY, IntValue, GETDATE()) AS SomeDay
            FROM dbo.GetIntSequence(1, 28)
    ) AS FourWeeks
    WHERE DATENAME(WEEKDAY, SomeDay) = @weekDay

--restore old language setting
SET LANGUAGE @languageBackup;

GO

DROP FUNCTION dbo.GetIntSequence