填写SQL查询的空白

时间:2008-12-10 09:30:19

标签: sql mysql

对于模糊的主题感到抱歉,但我想不出要放什么。

这是我的问题,我正在对表格进行查询,该表格会返回与一天相关的项目计数。我想确保如果我对数据库进行查询,我总是得到一定数量的行。例如,假设我有下表,其中包含人们登录网站时的日志:

**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个。有什么方法可以更改我的查询,以便我获取包含我查询的所有日期的数据。 E.g。

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上做这个的好方法的任何线索?或者是以编程方式我唯一的选择?欢呼任何输入。

4 个答案:

答案 0 :(得分:2)

为此,您需要编写一个返回表结果的存储过程。

它将使用一个循环,每天都会循环并获取计数并将其存储在临时表的一行中,然后将该表作为结果集返回。

以下是循环的MS SQL服务器示例:

http://www.databasejournal.com/features/mssql/article.php/3100621/T-SQL-Programming-Part-2---Building-a-T-SQL-Loop.htm

答案 1 :(得分:1)

  

我想如果我设置一个包含一年中所有日期然后使用左/右连接的表格,那么我可以这样做。但是这样做非常麻烦。

不。这几乎就是这样做的。另一方面,您可以使用临时表并使用所需的日期范围填充它。

如果只有MS SQL有虚拟表,那么你提供了生成器函数......

答案 2 :(得分:1)

您不需要创建临时表或类似的,只需要一个包含足够行的源来构建缺少的日期:

我不知道mysql,但如果它支持“connect by”,那么你可以执行以下操作:

(这是在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

虽然不太整洁,显然你需要知道驱动表中有足够的行。

答案 3 :(得分: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