Cách tốt nhất để xây dựng lại một ngày từ đầu vào số nguyên là gì?


8

Tôi đã có một loạt các báo cáo tài chính và chúng tôi muốn có thể chuyển cho họ hai đầu vào (năm và quý) dưới dạng các biến.

Tôi đang làm theo cách này, nhưng tôi thực sự không thích nó:

    declare @quarter int,
    @year int,
    @date date

    set @quarter = 4
    set @year = 2018


    set @date = cast(@year as varchar(4)) + '-01-01'
    set @date = dateadd(quarter, @quarter - 1, @date)


    print @date

Câu hỏi Cách tốt nhất để xây dựng lại một ngày từ đầu vào số nguyên là gì?

kết quả như ý:

    2018-10-01

Câu trả lời:


5

Bạn có thể Tạo thứ nguyên ngày hoặc bảng lịch trong SQL Server và truy vấn nó

--demo setup 
drop table if exists #dim
DECLARE @StartDate DATE = '20000101', @NumberOfYears INT = 30;

-- prevent set or regional settings from interfering with 
-- interpretation of dates / literals

SET DATEFIRST 7;
SET DATEFORMAT mdy;
SET LANGUAGE US_ENGLISH;

DECLARE @CutoffDate DATE = DATEADD(YEAR, @NumberOfYears, @StartDate);

-- this is just a holding table for intermediate calculations:

CREATE TABLE #dim
(
  [date]       DATE PRIMARY KEY, 
  [day]        AS DATEPART(DAY,      [date]),
  [month]      AS DATEPART(MONTH,    [date]),
  FirstOfMonth AS CONVERT(DATE, DATEADD(MONTH, DATEDIFF(MONTH, 0, [date]), 0)),
  [MonthName]  AS DATENAME(MONTH,    [date]),
  [week]       AS DATEPART(WEEK,     [date]),
  [ISOweek]    AS DATEPART(ISO_WEEK, [date]),
  [DayOfWeek]  AS DATEPART(WEEKDAY,  [date]),
  [quarter]    AS DATEPART(QUARTER,  [date]),
  [year]       AS DATEPART(YEAR,     [date]),
  FirstOfYear  AS CONVERT(DATE, DATEADD(YEAR,  DATEDIFF(YEAR,  0, [date]), 0)),
  Style112     AS CONVERT(CHAR(8),   [date], 112),
  Style101     AS CONVERT(CHAR(10),  [date], 101)
);

-- use the catalog views to generate as many rows as we need

INSERT #dim([date]) 
SELECT d
FROM
(
  SELECT d = DATEADD(DAY, rn - 1, @StartDate)
  FROM 
  (
    SELECT TOP (DATEDIFF(DAY, @StartDate, @CutoffDate)) 
      rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    -- on my system this would support > 5 million days
    ORDER BY s1.[object_id]
  ) AS x
) AS y;

drop table if exists dbo.DateDimension

CREATE TABLE dbo.DateDimension
(
  --DateKey           INT         NOT NULL PRIMARY KEY,
  [Date]              DATE        NOT NULL,
  [Day]               TINYINT     NOT NULL,
  DaySuffix           CHAR(2)     NOT NULL,
  [Weekday]           TINYINT     NOT NULL,
  WeekDayName         VARCHAR(10) NOT NULL,
  IsWeekend           BIT         NOT NULL,
  IsHoliday           BIT         NOT NULL,
  HolidayText         VARCHAR(64) SPARSE,
  DOWInMonth          TINYINT     NOT NULL,
  [DayOfYear]         SMALLINT    NOT NULL,
  WeekOfMonth         TINYINT     NOT NULL,
  WeekOfYear          TINYINT     NOT NULL,
  ISOWeekOfYear       TINYINT     NOT NULL,
  [Month]             TINYINT     NOT NULL,
  [MonthName]         VARCHAR(10) NOT NULL,
  [Quarter]           TINYINT     NOT NULL,
  QuarterName         VARCHAR(6)  NOT NULL,
  [Year]              INT         NOT NULL,
  MMYYYY              CHAR(6)     NOT NULL,
  MonthYear           CHAR(7)     NOT NULL,
  FirstDayOfMonth     DATE        NOT NULL,
  LastDayOfMonth      DATE        NOT NULL,
  FirstDayOfQuarter   DATE        NOT NULL,
  LastDayOfQuarter    DATE        NOT NULL,
  FirstDayOfYear      DATE        NOT NULL,
  LastDayOfYear       DATE        NOT NULL,
  FirstDayOfNextMonth DATE        NOT NULL,
  FirstDayOfNextYear  DATE        NOT NULL
);
INSERT dbo.DateDimension WITH (TABLOCKX)
SELECT
  --DateKey     = CONVERT(INT, Style112),
  [Date]        = [date],
  [Day]         = CONVERT(TINYINT, [day]),
  DaySuffix     = CONVERT(CHAR(2), CASE WHEN [day] / 10 = 1 THEN 'th' ELSE 
                  CASE RIGHT([day], 1) WHEN '1' THEN 'st' WHEN '2' THEN 'nd' 
                  WHEN '3' THEN 'rd' ELSE 'th' END END),
  [Weekday]     = CONVERT(TINYINT, [DayOfWeek]),
  [WeekDayName] = CONVERT(VARCHAR(10), DATENAME(WEEKDAY, [date])),
  [IsWeekend]   = CONVERT(BIT, CASE WHEN [DayOfWeek] IN (1,7) THEN 1 ELSE 0 END),
  [IsHoliday]   = CONVERT(BIT, 0),
  HolidayText   = CONVERT(VARCHAR(64), NULL),
  [DOWInMonth]  = CONVERT(TINYINT, ROW_NUMBER() OVER 
                  (PARTITION BY FirstOfMonth, [DayOfWeek] ORDER BY [date])),
  [DayOfYear]   = CONVERT(SMALLINT, DATEPART(DAYOFYEAR, [date])),
  WeekOfMonth   = CONVERT(TINYINT, DENSE_RANK() OVER 
                  (PARTITION BY [year], [month] ORDER BY [week])),
  WeekOfYear    = CONVERT(TINYINT, [week]),
  ISOWeekOfYear = CONVERT(TINYINT, ISOWeek),
  [Month]       = CONVERT(TINYINT, [month]),
  [MonthName]   = CONVERT(VARCHAR(10), [MonthName]),
  [Quarter]     = CONVERT(TINYINT, [quarter]),
  QuarterName   = CONVERT(VARCHAR(6), CASE [quarter] WHEN 1 THEN 'First' 
                  WHEN 2 THEN 'Second' WHEN 3 THEN 'Third' WHEN 4 THEN 'Fourth' END), 
  [Year]        = [year],
  MMYYYY        = CONVERT(CHAR(6), LEFT(Style101, 2)    + LEFT(Style112, 4)),
  MonthYear     = CONVERT(CHAR(7), LEFT([MonthName], 3) + LEFT(Style112, 4)),
  FirstDayOfMonth     = FirstOfMonth,
  LastDayOfMonth      = MAX([date]) OVER (PARTITION BY [year], [month]),
  FirstDayOfQuarter   = MIN([date]) OVER (PARTITION BY [year], [quarter]),
  LastDayOfQuarter    = MAX([date]) OVER (PARTITION BY [year], [quarter]),
  FirstDayOfYear      = FirstOfYear,
  LastDayOfYear       = MAX([date]) OVER (PARTITION BY [year]),
  FirstDayOfNextMonth = DATEADD(MONTH, 1, FirstOfMonth),
  FirstDayOfNextYear  = DATEADD(YEAR,  1, FirstOfYear)
FROM #dim
OPTION (MAXDOP 1);

--solution
SELECT min(Date)
  FROM [Test].[dbo].[DateDimension]
  where [year] = 2018 and [Quarter]=4

| Date       |
|------------|
| 2018-10-01 |

1
Đây là cách đúng đắn để thực hiện - theo cách này chúng ta có thể lập chỉ mục các phần ngày, do đó, việc tham gia vào DateDimension sẽ nhanh hơn nhiều lần so với việc đặt một hàm trên một cột trong mệnh đề where.
James

12

Làm thế nào về

declare @quarter int = 4
declare @year int = 2018

select datefromparts(@year,(@quarter-1)*3+1,1)

hoặc nếu bạn vẫn đang sử dụng SQL 2008:

select dateadd(month,(@quarter-1)*3,dateadd(year, @year-2018,'20180101'))

10

Hãy để tôi đề nghị KHÔNG sử dụng dấu tách ngày như '-' hoặc '/', tùy thuộc vào cài đặt khu vực, sử dụng YYYYMMDDđịnh dạng.

declare @quarter int,
    @year int,
    @date date

    set @quarter = 4
    set @year = 2018


    set @date = cast(@year as varchar(4)) + '0101'
    set @date = dateadd(quarter, 1 - 1, @date)
    print @date

    set @date = cast(@year as varchar(4)) + '0101'
    set @date = dateadd(quarter, 2 - 1, @date)
    print @date

    set @date = cast(@year as varchar(4)) + '0101'
    set @date = dateadd(quarter, 3 - 1, @date)
    print @date

    set @date = cast(@year as varchar(4)) + '0101'
    set @date = dateadd(quarter, 4 - 1, @date)
    print @date
2018-01-01
2018-04-01
2018-07-01
2018-10-01

db <> fiddle ở đây


3
Hiểu biết của tôi là, nó '2018-10-01'độc lập với các cài đặt khu vực và sẽ luôn được SQL Server phân tích cú pháp chính xác. Điều đó có đúng không?
a_horse_with_no_name

@a_horse_with_no_name nên là ISO 8601 , nhưng ít nhất trong công ty của tôi, nơi chúng tôi sử dụng cài đặt ngôn ngữ hỗn hợp các máy chủ SQL (tiếng Anh, tiếng Tây Ban Nha) Tôi đã kết thúc bằng cách sử dụng định dạng ít rõ ràng hơn YYYYMMDD HH:MM:SSđể tránh các vấn đề chuyển đổi ngày.
McNets

5
@a_horse_with_no_name - Điều này đúng với các kiểu dữ liệu tạm thời mới hơn (ví dụ datedatetime2) nhưng không phải cho datetimekiểu kế thừa . Vì vậy, khi người hỏi đang chuyển sang dateđịnh dạng là ổn trong trường hợp này
Martin Smith

1
@MartinSmith: cảm ơn bạn đã làm rõ
a_horse_with_no_name

"2001-08-12" không phải lúc nào cũng diễn giải theo cách bạn mong đợi. Tôi chưa bao giờ tìm hiểu làm thế nào nhưng trong môi trường phát triển của chúng tôi, nó sẽ hiểu nó là yyyy-dd-mm và đưa ra một lỗi ngoài phạm vi cho "2001-08-13". Tôi cố gắng tránh chuyển đổi chuỗi thành ngày, và khi cần thiết, tôi đảm bảo sử dụng tên tháng - "12 aug 2001" không rõ ràng.
IanF1

4

Tôi sẽ tránh sử dụng tất cả các chuỗi nhưng sử dụng số học ngày kết hợp với một epoch đã biết (hoặc thậm chí chưa biết!).

DECLARE @epoch DATE = CONVERT(DATETIME, 0);
/* for some reason SQL Server let's you cast int to datetime but not to date, the above casts via datetime (second cast implicit) */

SET @date = DATEADD(MONTH, (@quarter-1)*3, DATEADD(YEAR, @year - YEAR(@epoch), @epoch));

Điều này tránh chuỗi so sánh ngày nay là lộn xộn, phụ thuộc vào văn hóa và đắt tiền.

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.