2008-12-10 144 views
2

對於模糊的主題很抱歉,但我無法考慮要放什麼。在SQL查詢中填充空白

這是我的問題,我在一張桌子上做了一個查詢,這個查詢返回了與一天相關的項目數。我想確保如果我在數據庫上執行查詢,我總會得到一定數量的行。例如,假設我有以下表包含日誌,當人們登錄到網站:

**WebsiteLogin** 
id: Integer 
login_date: Datetime 

然後我可以做類似的登錄信息,每個日期的計數:

SELECT DATE(login_date), COUNT(*) FROM WebsiteLogin GROUP BY DATE(login_date) 

這很好,並會返回我想要的數據。但想象一下,我的網站在週末並不受歡迎。返回的數據會看起來像:

2008-12-10, 100 
2008-12-11, 124 
2008-12-12, 151 
2008-12-15, 141 
2008-12-16, 111 

第13 & 14日丟失,因爲沒有在那段時間沒有數據。有什麼方法可以更改我的查詢,以便獲取包含查詢日期的所有日期的數據。例如。

2008-12-10, 100 
2008-12-11, 124 
2008-12-12, 151 
2008-12-13, 0 
2008-12-14, 0 
2008-12-15, 141 
2008-12-16, 111 

我想我能做到這一點,如果我設置了包含在一年的所有日期的表,然後用左/右連接但這是真的做的混亂的方式。

因此,一個很好的方式來做到這一點在SQL中的任何線索?或者在編程上我是唯一的選擇?歡呼任何輸入。

回答

1

我想我可以做到這一點,如果我建立一個表,包含一年中的所有日期,然後使用左/右連接,但這是非常麻煩的做法。

都能跟得上。這幾乎是如何做到這一點。另一方面,您可以使用臨時表並僅填寫所需的日期範圍。

如果只是MS SQL有虛表,在那裏你提供一臺發電機的功能...

1

你不應該需要創建一個臨時表,或類似的,你只需要足夠的行構建缺少源日期:

我不知道MySQL的,但如果支持,那麼你可以做下面的「通過連接」:

(這是Oracle)

select d login_date, count(login_date) count 
from 
    websitelogin wsl 
    right outer join (
     select start_date+l-1 d from (select start_date, level l 
     from (select min(login_date) start_date, max(login_date)-min(login_date)+1 num_days 
     from websitelogin) connect by level <= num_days)) v on d=login_date 
group by d 
/

如果MySQL沒有連接的,你可以只參加與足夠的行一些任意表中它來代替,結果限制爲所需的行數:

select d login_date, count(login_date) count 
from 
    websitelogin wsl 
    right outer join (select start_date+rownum-1 d from 
(
select 
    min(login_date) start_date, 
    max(login_date)-min(login_date)+1 num_days 
from websitelogin)v,all_objects 
where rownum<=num_days 
) v on d=login_date 
group by d 

不是很整齊,雖然,顯然你需要知道駕駛臺上有足夠的行。

+0

做的很巧妙的方法。我喜歡。 – Jonathan 2008-12-10 14:02:32

0

我知道這是不是MySQL,但是我用MSSQL以下功能(請參閱下面的MySQL版本):

CREATE FUNCTION dbo.DatesBetween (@start_date datetime, @end_date datetime) 
RETURNS @DateTable TABLE (gen_date datetime) 
AS 
BEGIN 
    DECLARE @num_dates int 
    DECLARE @tmpVal TABLE (a_count int identity(0,1)) 

    SELECT @num_dates = datediff(day, @start_date, @end_date) 
    WHILE (select isnull(max(a_count), 0) from @tmpVal) < @num_dates 
     INSERT @tmpVal DEFAULT VALUES 

    INSERT @DateTable (gen_date) 
    SELECT dateadd(day, a_count, @start_date) FROM @tmpVal 

    RETURN 
END 

所以,在你的榜樣使用它,我會嘗試這樣的:

DECLARE @min_date datetime, @max_date datetime 
SELECT @min_date = min(login_date), @max_date = max(login_date) 
FROM WebsiteLogin 

SELECT m.gen_date 'login_date', isnull(l.num_visits, 0) 'num_visits' 
FROM dbo.DatesBetween(@min_date, @max_date) as d 
LEFT OUTER JOIN (SELECT DATE(login_date) 'login_date', COUNT(*) 'num_visits' 
      FROM WebsiteLogin 
      GROUP BY DATE(login_date)) AS l ON d.gen_date = l.login_date 

或者,並與我的查詢一個巨大的速度提升,你可以調查此blog entry,它做什麼我上面的代碼做,但將整個SQL的所有版本。

他解釋它更在那裏,但SQL是:

DECLARE @LowDate DATETIME 
SET @LowDate = '01-01-2006' 

DECLARE @HighDate DATETIME 
SET @HighDate = '12-31-2016' 

SELECT DISTINCT DATEADD(dd, Days.Row, DATEADD(mm, Months.Row, DATEADD(yy, Years.Row, @LowDate))) AS Date 
FROM 
(SELECT 0 AS Row UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 
UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 
UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24 
UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29 
UNION ALL SELECT 30 -- add more years here... 
) AS Years 
INNER JOIN 
(SELECT 0 AS Row UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 
UNION ALL SELECT 10 UNION ALL SELECT 11 
) AS Months 
ON DATEADD(mm, Months.Row, DATEADD(yy, Years.Row, @LowDate)) <= @HighDate 
INNER JOIN 
(SELECT 0 AS Row UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 
UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 
UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24 
UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29 
UNION ALL SELECT 30 
) AS Days 
ON DATEADD(dd, Days.Row, DATEADD(mm, Months.Row, DATEADD(yy, Years.Row, @LowDate))) <= @HighDate 
WHERE DATEADD(yy, Years.Row, @LowDate) <= @HighDate 
ORDER BY 1