2012-06-21 35 views
7

Tiêu đề không hoàn toàn nắm bắt được ý tôi và điều này có thể trùng lặp.Cách tạo một phạm vi ngày trong SQL Server

Đây là phiên bản dài: được cung cấp tên khách, ngày đăng ký và ngày thanh toán của họ, làm cách nào để tạo một hàng cho mỗi ngày mà họ là khách?

Ví dụ: Bob kiểm tra trong 7/14 và rời khỏi 7/17. Tôi muốn

('Bob', 7/14), ('Bob', 7/15), ('Bob', 7/16), ('Bob', 7/17) 

là kết quả của tôi.

Cảm ơn!

+0

Có một cái nhìn ở đây http://stackoverflow.com/questions/1478951/tsql-generate-a-resultset-of-incrementing-dates [1]: http: // stackoverflow .com/questions/1478951/tsql-generate-a-resultset-of-incrementing-date – StoicFnord

+3

Nói chung, bạn không. Bạn có một bảng tra cứu và chọn chúng ra khỏi đó. 'WHERE calendar.date> = user.start_date AND calendar.date <= user.leave_date' Bạn * CAN * tạo tập hợp bằng cách sử dụng vòng lặp hoặc truy vấn đệ quy, nhưng chúng không bao giờ nhanh bằng cách sử dụng bảng tra cứu. – MatBailie

+0

Tôi hỏi một câu hỏi rất giống, nhưng tôi là giờ, không phải ngày. Bạn có thể thay đổi để phù hợp với nhu cầu của bạn khá dễ dàng. http://stackoverflow.com/questions/10986344/get-every-hour-for-a-time-range – Limey

Trả lời

23

Tôi cho rằng đối với mục đích cụ thể này, truy vấn dưới đây hiệu quả như sử dụng bảng tra cứu chuyên dụng.

DECLARE @start DATE, @end DATE; 
SELECT @start = '20110714', @end = '20110717'; 

;WITH n AS 
(
    SELECT TOP (DATEDIFF(DAY, @start, @end) + 1) 
    n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
    FROM sys.all_objects 
) 
SELECT 'Bob', DATEADD(DAY, n-1, @start) 
FROM n; 

Kết quả:

Bob  2011-07-14 
Bob  2011-07-15 
Bob  2011-07-16 
Bob  2011-07-17 

Có lẽ bạn sẽ cần điều này như là một tập, chứ không phải cho một thành viên duy nhất, vì vậy đây là một cách để thích nghi với kỹ thuật này:

DECLARE @t TABLE 
(
    Member NVARCHAR(32), 
    RegistrationDate DATE, 
    CheckoutDate DATE 
); 

INSERT @t SELECT N'Bob', '20110714', '20110717' 
UNION ALL SELECT N'Sam', '20110712', '20110715' 
UNION ALL SELECT N'Jim', '20110716', '20110719'; 

;WITH [range](d,s) AS 
(
    SELECT DATEDIFF(DAY, MIN(RegistrationDate), MAX(CheckoutDate))+1, 
    MIN(RegistrationDate) 
    FROM @t -- WHERE ? 
), 
n(d) AS 
(
    SELECT DATEADD(DAY, n-1, (SELECT MIN(s) FROM [range])) 
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) 
    FROM sys.all_objects) AS s(n) 
    WHERE n <= (SELECT MAX(d) FROM [range]) 
) 
SELECT t.Member, n.d 
FROM n CROSS JOIN @t AS t 
WHERE n.d BETWEEN t.RegistrationDate AND t.CheckoutDate; 
----------^^^^^^^ not many cases where I'd advocate between! 

Kết quả:

Member d 
-------- ---------- 
Bob  2011-07-14 
Bob  2011-07-15 
Bob  2011-07-16 
Bob  2011-07-17 
Sam  2011-07-12 
Sam  2011-07-13 
Sam  2011-07-14 
Sam  2011-07-15 
Jim  2011-07-16 
Jim  2011-07-17 
Jim  2011-07-18 
Jim  2011-07-19 

Như @Dems chỉ ra, điều này có thể được đơn giản hóa để:

;WITH natural AS 
(
    SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 AS val 
    FROM sys.all_objects 
) 
SELECT t.Member, d = DATEADD(DAY, natural.val, t.RegistrationDate) 
    FROM @t AS t INNER JOIN natural 
    ON natural.val <= DATEDIFF(DAY, t.RegistrationDate, t.CheckoutDate); 
+0

Trình tối ưu hóa của SQL Server AFAIK có nghĩa là bạn không thực sự cần 'WHERE n <= (SELECT MAX())' có nghĩa là điều này có thể đơn giản hơn nữa ... 'WITH AS tự nhiên (SELECT ROW_NUMBER() OVER (ORDER BY id) - 1 AS val FROM sys.objects) CHỌN t.Member, DATEADD (DAY, natural.val, t.start) TỪ @t AS t ĐỐI TÁC THAM GIA tự nhiên ON natural.val <= DATEDIFF (DAY, t.start , t.end) '* [Nhưng, ngay cả khi đó, một bảng tra cứu thẳng vẫn sẽ sử dụng ít chu kỳ CPU ít nhất.] * – MatBailie

+0

@Do khi tôi bắt đầu viết mục tiêu của mình là sử dụng phạm vi cao nhất trong 'TOP' chống lại' sys.all_objects'. Bạn nói đúng là nó có thể được đơn giản hóa. –

+0

Cảm ơn, truy vấn của bạn thực hiện chính xác những gì tôi đang tìm kiếm. Một câu hỏi - là cần thiết để sử dụng MAX và MIN trên bảng 'phạm vi'? Trong ví dụ này, tôi chỉ thấy một hàng được tạo cho 'dải ô', vì vậy chỉ có một ứng cử viên cho một trong hai hoặc tối đa (trong trường hợp này tôi sẽ chỉ đặt phạm vi và ngày bắt đầu trong các biến thông thường). Tôi khá ấn tượng với SQL của bạn và tò mò nếu có một số tinh tế có tôi là mất tích. –

0

Điều này có thể làm việc cho ya:

with mycte as 
(
    select cast('2000-01-01' as datetime) DateValue, 'Bob' as Name 
    union all 
    select DateValue + 1 ,'Bob' as Name 
    from mycte 
    where DateValue + 1 < '2000-12-31' 
) 
select * 
from mycte 
OPTION (MAXRECURSION 0) 
+2

Điều đó chứa "Đếm CTE đệ quy". Xem bài viết sau đây về lý do tại sao họ rất xấu ngay cả khi đếm số lượng nhỏ. http://www.sqlservercentral.com/articles/T-SQL/74118/ –

-4

tôi sẽ tạo ra một kích hoạt để tạo hồ sơ bổ sung và chạy nó khi thanh toán. Ngoài ra, bạn có thể có một công việc nửa đêm hàng ngày làm như vậy (nếu bạn cần thông tin cập nhật trong cơ sở dữ liệu của bạn).

+1

Đây không thực sự là câu trả lời - trình kích hoạt tạo ra các bản ghi bổ sung như thế nào? –

+0

@AaronBertrand đây là nhiệm vụ lập trình tầm thường bằng bất kỳ ngôn ngữ nào. – Andy

+3

Nếu nó quá tầm thường, OP sẽ không hỏi, đúng không? Và nó không khó để thực sự sao lưu câu trả lời của bạn với một số mã cho * ngôn ngữ * này? –

6

Tôi thường làm điều này với một thủ thuật sử dụng row_number() trên một số bảng. Vì vậy:

select t.name, dateadd(d, seq.seqnum, t.start_date) 
from t left outer join 
    (select row_number() over (order by (select NULL)) as seqnum 
     from t 
    ) seq 
    on seqnum <= datediff(d, t.start_date, t.end_date) 

Tính toán cho seq diễn ra khá nhanh, vì không cần tính toán hoặc đặt hàng. Tuy nhiên, bạn cần đảm bảo rằng bảng đủ lớn cho mọi khoảng thời gian.

1

Nếu bạn có bảng "Tally" hoặc "Numbers", thì cuộc sống thực sự đơn giản với những thứ như thế này.

SELECT Member, DatePresent = DATEADD(dd,t.N,RegistrationDate) 
    FROM @t 
    CROSS JOIN dbo.Tally t 
    WHERE t.N BETWEEN 0 AND DATEDIFF(dd,RegistrationDate,CheckoutDate) 
; 

Dưới đây là cách tạo bảng "Kiểm đếm".

--=================================================================== 
--  Create a Tally table from 0 to 11000 
--=================================================================== 
--===== Create and populate the Tally table on the fly. 
SELECT TOP 11001 
     IDENTITY(INT,0,1) AS N 
    INTO dbo.Tally 
    FROM Master.sys.ALL_Columns ac1 
    CROSS JOIN Master.sys.ALL_Columns ac2 
; 
--===== Add a CLUSTERED Primary Key to maximize performance 
    ALTER TABLE dbo.Tally 
    ADD CONSTRAINT PK_Tally_N 
     PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100 
; 
--===== Allow the general public to use it 
    GRANT SELECT ON dbo.Tally TO PUBLIC 
; 
GO 

Để biết thêm thông tin về những gì một "Tally" bảng là trong SQL và làm thế nào nó có thể được sử dụng để thay thế khi vòng và "Hidden RBAR" của CTEs reursive mà đếm, hãy xem bài viết sau đây.

http://www.sqlservercentral.com/articles/T-SQL/62867/

Các vấn đề liên quan