【问题标题】:How to create a Calendar table for 100 years in Sql [closed]如何在Sql中创建一个100年的日历表[关闭]
【发布时间】:2011-08-03 21:12:17
【问题描述】:

假设我想在一个表中存储数千天,我将如何从日历中检索它?

【问题讨论】:

  • ...以及您想要存储的有关这些日子的数据。
  • @das_weezul:我必须记住那个,这是提醒发布者问题质量问题的一种非常友好和好方法。
  • @DarkDust:我想我们正在同时解决同样的问题;)
  • 您使用的是哪个 dbms?
  • 这个问题已经在Meta提到过。

标签: sql


【解决方案1】:

由于这仅标记为sql(不表示任何特定的 DBMS),因此这里是 Postgres 的解决方案:

select d::date 
from generate_series(date '1990-01-01', date '1990-01-01' + interval '100' year, interval '1' day) as t(d);

如果您需要很多,将其存储在表中(例如可以被索引)会更有效:

create table calendar 
as
select d::date as the_date
from generate_series(date '1990-01-01', date '1990-01-01' + interval '100' year, interval '1' day) as t(d);

【讨论】:

    【解决方案2】:

    此 SQL Server 用户定义函数有效地解决了问题。没有递归,没有复杂的循环。生成时间很短。

    ALTER FUNCTION [GA].[udf_GenerateCalendar]
    (
         @StartDate  DATE        -- StartDate
       , @EndDate    DATE        -- EndDate
    )
    RETURNS @Results TABLE 
           (
               Date       DATE 
           )
    AS
    
    /**********************************************************
    Purpose:   Generate a sequence of dates based on StartDate and EndDate 
    ***********************************************************/
    
    BEGIN
    
        DECLARE @counter INTEGER = 1 
    
        DECLARE @days table(
            day INTEGER NOT NULL 
        )
    
        DECLARE @months table(
            month INTEGER NOT NULL 
        )
    
        DECLARE @years table(
            year INTEGER NOT NULL 
        )
    
        DECLARE @calendar table(
            Date DATE NOT NULL 
        )
    
    
        -- Populate generic days 
        SET @counter = 1 
        WHILE @counter <= 31 
        BEGIN 
            INSERT INTO @days 
            SELECT @counter dia 
    
            SELECT @counter = @counter + 1 
        END 
    
        -- Populate generic months 
        SET @counter = 1 
        WHILE @counter <= 12 
        BEGIN 
            INSERT INTO @months 
            SELECT @counter month 
    
            SELECT @counter = @counter + 1 
        END 
    
        -- Populate generic years 
        SET @counter = YEAR(@StartDate) 
        WHILE @counter <= YEAR(@EndDate) 
        BEGIN 
            INSERT INTO @years 
            SELECT @counter year 
    
            SELECT @counter = @counter + 1 
        END 
    
        INSERT @calendar (Date) 
        SELECT Date 
        FROM ( 
            SELECT 
                CONVERT(Date, [Date], 102) AS Date 
            FROM ( 
                SELECT 
                    CAST(
                        y.year * 10000 
                        + m.month * 100 
                        + d.day 
                        AS VARCHAR(8)) AS Date 
                FROM @days d, @months m, @years y 
                WHERE 
                    ISDATE(CAST(
                        y.year * 10000 
                        + m.month * 100 
                        + d.day 
                        AS VARCHAR(8)) 
                        ) = 1 
            ) A 
        ) A 
    
        INSERT @Results (Date) 
        SELECT Date 
        FROM @calendar 
        WHERE Date BETWEEN @StartDate AND @EndDate
    
       RETURN 
    /*
    DECLARE @StartDate DATE = '2015-08-01'
    DECLARE @EndDate   DATE = '2015-08-31'
    select * from [GA].[udf_GenerateCalendar](@StartDate, @EndDate)
    */
    END
    

    【讨论】:

      【解决方案3】:

      这会以闪电般的速度为您创造结果。

      select top 100000  identity (int ,1,1) as Sequence into Tally from sysobjects , sys.all_columns
      
      
      select dateadd(dd,sequence,-1) Dates into CalenderTable from tally
      
      
      delete from CalenderTable where dates < -- mention the mindate you need
      delete from CalenderTable where dates >  -- mention the max date you need
      

      第一步:创建序列表

      第 2 步:使用序列表生成所需日期

      第 3 步:删除不需要的日期

      【讨论】:

        【解决方案4】:
         declare @date int
         WITH CTE_DatesTable
        AS
        (
          SELECT CAST('20000101' as date) AS [date]
          UNION ALL
          SELECT   DATEADD(dd, 1, [date])
          FROM CTE_DatesTable
          WHERE DATEADD(dd, 1, [date]) <= '21001231'
        )
        SELECT [DWDateKey]=[date],[DayDate]=datepart(dd,[date]),[DayOfWeekName]=datename(dw,[date]),[WeekNumber]=DATEPART( WEEK , [date]),[MonthNumber]=DATEPART( MONTH , [date]),[MonthName]=DATENAME( MONTH , [date]),[MonthShortName]=substring(LTRIM( DATENAME(MONTH,[date])),0, 4),[Year]=DATEPART(YY,[date]),[QuarterNumber]=DATENAME(quarter, [date]),[QuarterName]=DATENAME(quarter, [date]) into DimDate   FROM CTE_DatesTable
        
        OPTION (MAXRECURSION 0);
        

        【讨论】:

          【解决方案5】:

          这是一个可以在 SQL Server 中使用的通用脚本。只需修改开始和结束日期:

          IF EXISTS (SELECT * FROM information_schema.tables WHERE Table_Name = 'Calendar' AND Table_Type = 'BASE TABLE')
          BEGIN
          DROP TABLE [Calendar]
          END
          
          CREATE TABLE [Calendar]
          (
              [CalendarDate] DATETIME
          )
          
          DECLARE @StartDate DATETIME
          DECLARE @EndDate DATETIME
          SET @StartDate = GETDATE()
          SET @EndDate = DATEADD(d, 365, @StartDate)
          
          WHILE @StartDate <= @EndDate
                BEGIN
                       INSERT INTO [Calendar]
                       (
                             CalendarDate
                       )
                       SELECT
                             @StartDate
          
                       SET @StartDate = DATEADD(dd, 1, @StartDate)
                END
          

          如果你想要更高级的日历,这是我不久前在网上找到的:

          CREATE SCHEMA Auxiliary
          -- We put our auxiliary tables and stuff in a separate schema
          -- One of the great new things in SQL Server 2005
          go
          
          CREATE FUNCTION Auxiliary.Computus
          -- Computus (Latin for computation) is the calculation of the date of
          -- Easter in the Christian calendar
          -- http://en.wikipedia.org/wiki/Computus
          -- I'm using the Meeus/Jones/Butcher Gregorian algorithm
          (
              @Y INT -- The year we are calculating easter sunday for
          )
          RETURNS DATETIME
          AS
          BEGIN
          DECLARE
          @a INT,
          @b INT,
          @c INT,
          @d INT,
          @e INT,
          @f INT,
          @g INT,
          @h INT,
          @i INT,
          @k INT,
          @L INT,
          @m INT
          
          SET @a = @Y % 19
          SET @b = @Y / 100
          SET @c = @Y % 100
          SET @d = @b / 4
          SET @e = @b % 4
          SET @f = (@b + 8) / 25
          SET @g = (@b - @f + 1) / 3
          SET @h = (19 * @a + @b - @d - @g + 15) % 30
          SET @i = @c / 4
          SET @k = @c % 4
          SET @L = (32 + 2 * @e + 2 * @i - @h - @k) % 7
          SET @m = (@a + 11 * @h + 22 * @L) / 451
          RETURN(DATEADD(month, ((@h + @L - 7 * @m + 114) / 31)-1, cast(cast(@Y AS VARCHAR) AS Datetime)) + ((@h + @L - 7 * @m + 114) % 31))
          END
          GO
          
          
          CREATE TABLE [Auxiliary].[Calendar] (
          -- This is the calendar table
            [Date] datetime NOT NULL,
            [Year] int NOT NULL,
            [Quarter] int NOT NULL,
            [Month] int NOT NULL,
            [Week] int NOT NULL,
            [Day] int NOT NULL,
            [DayOfYear] int NOT NULL,
            [Weekday] int NOT NULL,
            [Fiscal_Year] int NOT NULL,
            [Fiscal_Quarter] int NOT NULL,
            [Fiscal_Month] int NOT NULL,
            [KindOfDay] varchar(10) NOT NULL,
            [Description] varchar(50) NULL,
            PRIMARY KEY CLUSTERED ([Date])
          )
          GO
          
          ALTER TABLE [Auxiliary].[Calendar]
          -- In Celkoish style I'm manic about constraints (Never use em ;-))
          -- http://www.celko.com/
          
          ADD CONSTRAINT [Calendar_ck] CHECK (  ([Year] > 1900)
          AND ([Quarter] BETWEEN 1 AND 4)
          AND ([Month] BETWEEN 1 AND 12)
          AND ([Week]  BETWEEN 1 AND 53)
          AND ([Day] BETWEEN 1 AND 31)
          AND ([DayOfYear] BETWEEN 1 AND 366)
          AND ([Weekday] BETWEEN 1 AND 7)
          AND ([Fiscal_Year] > 1900)
          AND ([Fiscal_Quarter] BETWEEN 1 AND 4)
          AND ([Fiscal_Month] BETWEEN 1 AND 12)
          AND ([KindOfDay] IN ('HOLIDAY', 'SATURDAY', 'SUNDAY', 'BANKDAY')))
          GO
          
          
          
          
          SET DATEFIRST 1;
          -- I want my table to contain datedata acording to ISO 8601
          -- http://en.wikipedia.org/wiki/ISO_8601
          -- thus first day of a week is monday
          WITH Dates(Date)
          -- A recursive CTE that produce all dates between 1999 and 2020-12-31
          AS
          (
          SELECT cast('1999' AS DateTime) Date -- SQL Server supports the ISO 8601 format so this is an unambigious shortcut for 1999-01-01
          UNION ALL                            -- http://msdn2.microsoft.com/en-us/library/ms190977.aspx
          SELECT (Date + 1) AS Date
          FROM Dates
          WHERE
          Date < cast('2021' AS DateTime) -1
          ),
          
          DatesAndThursdayInWeek(Date, Thursday)
          -- The weeks can be found by counting the thursdays in a year so we find
          -- the thursday in the week for a particular date
          AS
          (
          SELECT
          Date,
          CASE DATEPART(weekday,Date)
          WHEN 1 THEN Date + 3
          WHEN 2 THEN Date + 2
          WHEN 3 THEN Date + 1
          WHEN 4 THEN Date
          WHEN 5 THEN Date - 1
          WHEN 6 THEN Date - 2
          WHEN 7 THEN Date - 3
          END AS Thursday
          FROM Dates
          ),
          
          Weeks(Week, Thursday)
          -- Now we produce the weeknumers for the thursdays
          -- ROW_NUMBER is new to SQL Server 2005
          AS
          (
          SELECT ROW_NUMBER() OVER(partition by year(Date) order by Date) Week, Thursday
          FROM DatesAndThursdayInWeek
          WHERE DATEPART(weekday,Date) = 4
          )
          INSERT INTO Auxiliary.Calendar
          SELECT
          d.Date,
          YEAR(d.Date) AS Year,
          DATEPART(Quarter, d.Date) AS Quarter,
          MONTH(d.Date) AS Month,
          w.Week,
          DAY(d.Date) AS Day,
          DATEPART(DayOfYear, d.Date) AS DayOfYear,
          DATEPART(Weekday, d.Date) AS Weekday,
          
          -- Fiscal year may be different to the actual year in Norway the are the same
          -- http://en.wikipedia.org/wiki/Fiscal_year
          YEAR(d.Date) AS Fiscal_Year,
          DATEPART(Quarter, d.Date) AS Fiscal_Quarter,
          MONTH(d.Date) AS Fiscal_Month,
          
          CASE
          -- Holidays in Norway
          -- For other countries and states: Wikipedia - List of holidays by country
          -- http://en.wikipedia.org/wiki/List_of_holidays_by_country
              WHEN (DATEPART(DayOfYear, d.Date) = 1)          -- New Year's Day
              OR (d.Date = Auxiliary.Computus(YEAR(Date))-7)  -- Palm Sunday
              OR (d.Date = Auxiliary.Computus(YEAR(Date))-3)  -- Maundy Thursday
              OR (d.Date = Auxiliary.Computus(YEAR(Date))-2)  -- Good Friday
              OR (d.Date = Auxiliary.Computus(YEAR(Date)))    -- Easter Sunday
              OR (d.Date = Auxiliary.Computus(YEAR(Date))+39) -- Ascension Day
              OR (d.Date = Auxiliary.Computus(YEAR(Date))+49) -- Pentecost
              OR (d.Date = Auxiliary.Computus(YEAR(Date))+50) -- Whitmonday
              OR (MONTH(d.Date) = 5 AND DAY(d.Date) = 1)      -- Labour day
              OR (MONTH(d.Date) = 5 AND DAY(d.Date) = 17)     -- Constitution day
              OR (MONTH(d.Date) = 12 AND DAY(d.Date) = 25)    -- Cristmas day
              OR (MONTH(d.Date) = 12 AND DAY(d.Date) = 26)    -- Boxing day
              THEN 'HOLIDAY'
              WHEN DATEPART(Weekday, d.Date) = 6 THEN 'SATURDAY'
              WHEN DATEPART(Weekday, d.Date) = 7 THEN 'SUNDAY'
              ELSE 'BANKDAY'
          END KindOfDay,
          CASE
          -- Description of holidays in Norway
              WHEN (DATEPART(DayOfYear, d.Date) = 1)            THEN 'New Year''s Day'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date))-7)  THEN 'Palm Sunday'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date))-3)  THEN 'Maundy Thursday'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date))-2)  THEN 'Good Friday'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date)))    THEN 'Easter Sunday'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date))+39) THEN 'Ascension Day'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date))+49) THEN 'Pentecost'
              WHEN (d.Date = Auxiliary.Computus(YEAR(Date))+50) THEN 'Whitmonday'
              WHEN (MONTH(d.Date) = 5 AND DAY(d.Date) = 1)      THEN 'Labour day'
              WHEN (MONTH(d.Date) = 5 AND DAY(d.Date) = 17)     THEN 'Constitution day'
              WHEN (MONTH(d.Date) = 12 AND DAY(d.Date) = 25)    THEN 'Cristmas day'
              WHEN (MONTH(d.Date) = 12 AND DAY(d.Date) = 26)    THEN 'Boxing day'
          END Description
          
          FROM DatesAndThursdayInWeek d
          -- This join is for getting the week into the result set
               inner join Weeks w
               on d.Thursday = w.Thursday
          
          OPTION(MAXRECURSION 0)
          GO
          
          CREATE FUNCTION Auxiliary.Numbers
          (
          @AFrom INT,
          @ATo INT,
          @AIncrement INT
          )
          RETURNS @RetNumbers TABLE
          (
          [Number] int PRIMARY KEY NOT NULL
          )
          AS
          BEGIN
          WITH Numbers(n)
          AS
          (
          SELECT @AFrom AS n
          UNION ALL
          SELECT (n + @AIncrement) AS n
          FROM Numbers
          WHERE
          n < @ATo
          )
          INSERT @RetNumbers
          SELECT n from Numbers
          OPTION(MAXRECURSION 0)
          RETURN;
          END
          GO
          
          CREATE FUNCTION Auxiliary.iNumbers
          (
          @AFrom INT,
          @ATo INT,
          @AIncrement INT
          )
          RETURNS TABLE
          AS
          RETURN(
          WITH Numbers(n)
          AS
          (
          SELECT @AFrom AS n
          UNION ALL
          SELECT (n + @AIncrement) AS n
          FROM Numbers
          WHERE
          n < @ATo
          )
          SELECT n AS Number from Numbers
          )
          GO
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-10-31
            • 1970-01-01
            • 1970-01-01
            • 2012-12-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多