2012-04-12 84 views
0

我有以下表的信息:如何将包含零消息的日期反正包含到结果集中?

+---------+---------+------------+----------+ 
| msg_id | user_id | m_date  | m_time | 
+-------------------+------------+----------+ 
| 1  | 1  | 2011-01-22 | 06:23:11 | 
| 2  | 1  | 2011-01-23 | 16:17:03 | 
| 3  | 1  | 2011-01-23 | 17:05:45 | 
| 4  | 2  | 2011-01-22 | 23:58:13 | 
| 5  | 2  | 2011-01-23 | 23:59:32 | 
| 6  | 2  | 2011-01-24 | 21:02:41 | 
| 7  | 3  | 2011-01-22 | 13:45:00 | 
| 8  | 3  | 2011-01-23 | 13:22:34 | 
| 9  | 3  | 2011-01-23 | 18:22:34 | 
| 10  | 3  | 2011-01-24 | 02:22:22 | 
| 11  | 3  | 2011-01-24 | 13:12:00 | 
+---------+---------+------------+----------+ 

我要的是每一天,看看有多少邮件每个用户都发送之前和16:00后:

SELECT 
    user_id, 
    m_date, 
    SUM(m_time <= '16:00') AS before16, 
    SUM(m_time > '16:00') AS after16 
FROM messages 
GROUP BY user_id, m_date 
ORDER BY user_id, m_date ASC 

这将产生:

user_id m_date  before16 after16 
------------------------------------- 
1  2011-01-22 1   0 
1  2011-01-23 0   2 
2  2011-01-22 0   1 
2  2011-01-23 0   1 
2  2011-01-24 0   1 
3  2011-01-22 1   0 
3  2011-01-23 1   1 
3  2011-01-24 2   0 

因为用户1在2011-01-24没有写任何消息,所以此日期不在结果集中。但是,这是不可取的。我有第二个表在我的数据库,名为“DATE_RANGE”:

+---------+------------+ 
| date_id | d_date  | 
+---------+------------+ 
| 1  | 2011-01-21 | 
| 1  | 2011-01-22 | 
| 1  | 2011-01-23 | 
| 1  | 2011-01-24 | 
+---------+------------+ 

我要检查的“信息”对这个表。对于每个用户,所有这些日期必须位于结果集中。正如你所看到的,没有一个用户在2011年1月21日写过消息,并且如上所述,用户1在2011年1月24日没有消息。查询所需的输出是:

user_id d_date  before16 after16 
------------------------------------- 
1  2011-01-21 0   0 
1  2011-01-22 1   0 
1  2011-01-23 0   2 
1  2011-01-24 0   0 
2  2011-01-21 0   0 
2  2011-01-22 0   1 
2  2011-01-23 0   1 
2  2011-01-24 0   1 
3  2011-01-21 0   0 
3  2011-01-22 1   0 
3  2011-01-23 1   1 
3  2011-01-24 2   0 

如何链接两个表,以便查询结果也持有与零个值的行为before16和after16?

编辑:是的,我有一个 “用户” 表:

+---------+------------+ 
| user_id | user_date | 
+---------+------------+ 
| 1  | foo  | 
| 2  | bar  | 
| 3  | foobar  | 
+---------+------------+ 
+0

而不是一个日期范围(这是固定的),你应该考虑我在这个[问题](http://stackoverflow.com/questions/10034668/missing-days-from-sql-call-where-there -is-无数据)。 – 2012-04-12 15:48:05

回答

2

测试台:

create table messages (msg_id integer, user_id integer, _date date, _time time); 
create table date_range (date_id integer, _date date); 
insert into messages values 
     (1,1,'2011-01-22','06:23:11'), 
     (2,1,'2011-01-23','16:17:03'), 
     (3,1,'2011-01-23','17:05:05'); 
insert into date_range values 
     (1, '2011-01-21'), 
     (1, '2011-01-22'), 
     (1, '2011-01-23'), 
     (1, '2011-01-24'); 

查询:

SELECT p._date, p.user_id, 
     coalesce(m.before16, 0) b16, coalesce(m.after16, 0) a16 
    FROM 
     (SELECT DISTINCT user_id, dr._date FROM messages m, date_range dr) p 
    LEFT JOIN 
     (SELECT user_id, _date, 
       SUM(_time <= '16:00') AS before16, 
       SUM(_time > '16:00') AS after16 
     FROM messages 
     GROUP BY user_id, _date 
     ORDER BY user_id, _date ASC) m 
    ON p.user_id = m.user_id AND p._date = m._date; 

编辑:

  1. 你的初始查询被保留原样,我h操作它不需要任何解释;

  2. SELECT DISTINCT user_id, dr._date FROM messages m, date_range dr将返回两个表的笛卡尔或CROSS JOIN,这将给我主题中每个用户的所有必需的日期范围。由于我只对每一对感兴趣,我使用DISTINCT条款。尝试使用和不使用此查询;

  3. 然后我在两个子选择上使用LEFT JOIN

    此连接表示:首先,执行INNER连接,即返回ON条件中具有匹配字段的所有行。然后,对于右侧没有匹配的连接的左侧关系中的每一行,返回NULL s(因此名称为LEFT JOIN,即左边的关系始终存在,右侧预计有NULL s)。此连接将执行您所期望的操作 - 即使给定用户的给定日期中没有消息,也会返回user_id + date组合。请注意,我使用user_id + date子选择第一(左边)和messages查询第二(右边);

  4. coalesce()用于用零代替NULL

我希望这可以澄清这个查询如何工作。

+0

我尝试了解您的查询,但感到困惑。我重命名了一些列,以便它们是不同的(例如,m_date和d_date)。你能否更新你的查询,以便我可以更好地遵循它? – Pr0no 2012-04-12 18:57:50

+0

抱歉再次打扰您,并感谢您的解释...但现在我得到一个错误:#1052 - 列'user_id'在字段列表中是不明确的。这我不明白。另外:错误不指向哪个user_id是模棱两可的,为什么...啊! – Pr0no 2012-04-12 20:18:21

+1

这实际上是MySQL的情况。您有2个(或更多)关系在'FROM'列表中提供'user_id'列。在这种情况下,您必须使用关系别名(或全表名称,如果关系是表格)前缀列。请注意,在我的示例中,'SELECT','FROM'和'ON'子句中的所有列都有前缀。这是一个很好的做法,总是为关系提供别名,并始终为列添加前缀。 – vyegorov 2012-04-12 20:23:14

0

chezy525的解决方案的伟大工程,我把它移植到PostgreSQL和删除/重命名一些别名:

select users_and_dates.user_id, users_and_dates._date, 
    SUM(case when _time <= '16:00' then 1 else 0 end) as before16, 
    SUM(case when _time > '16:00' then 1 else 0 end) as after16 
from (
    select messages.user_id, date_range._date 
    from messages 
     cross join date_range 
    group by messages.user_id, date_range._date 
    ) users_and_dates 
    left join messages on users_and_dates.user_id=messages.user_id 
        and users_and_dates._date=messages._date 
group by users_and_dates.user_id, users_and_dates._date; 

和跑在我的机器上,完美地工作

+0

不幸的是,虽然这可能会解决“缺失”日期的问题,但它不会为这些日期生成'user's'missing'(无消息)。不过,你正走在正确的轨道上。 – 2012-04-12 15:37:04

+0

感谢您的努力,但值得赞赏。 – Pr0no 2012-04-12 18:59:23

+0

那么没有看到那个要求。如果没有任何解决方案可以工作,我会尽量在早上找到一个完整的解决方案 – 2012-04-12 19:36:32

1

它并不整齐。但是如果你有一个user表。那么,也许是这样的:

SELECT 
    user_id, 
    _date, 
    SUM(_time <= '16:00') AS before16, 
    SUM(_time > '16:00') AS after16 
FROM messages 
GROUP BY user_id, _date 
UNION 
SELECT 
    user_id, 
    date_range, 
    0 AS before16, 
    0 AS after16 
FROM 
    users, 
    date_range 
ORDER BY user_id, _date ASC 
+2

虽然这个查询会起作用(并且OP更好地拥有一个“用户”表),但它并不是完全最优的(特别是考虑到'UNION ')。我更喜欢带有显式连接的版本,并且在一个'SELECT'语句中。而且我知道'ORDER BY'会如何反应,但这让我很痒,看到它就像那样。 – 2012-04-12 15:42:20

+0

我得到一个错误:'#1052 - 列'user_id'在字段列表中是不明确的'我不知道什么suer_id被引用。任何帮助? – Pr0no 2012-04-12 18:40:44

+0

这可能不是最好的解决方案。我只是给Op另一个选择。当我看到输出时,这正是我第一次来到这里。 – Arion 2012-04-12 19:28:44

2

这给一个镜头:

select u.user_id, u._date, 
    sum(_time <= '16:00') as before16, 
    sum(_time > '16:00') as after16 
from (
    select m.user_id, d._date 
    from messages m 
     cross join date_range d 
    group by m.user_id, d._date 
    ) u 
    left join messages m on u.user_id=m.user_id 
         and u._date=m._date 
group by u.user_id, u._date 

内部查询只是建设一个集所有可能的/所需的用户最新的对。使用用户表会更有效率,但你没有提到你有一个,所以我不会假设。否则,你只需要left join不删除未加入的记录。

编辑 - 更详细的解释:分开查询。

从最里面的查询开始;目标是获取每个用户所需的所有日期的列表。由于有用户的表和日期的表也可以是这样的:

select distinct u.user_id, d.d_date 
from users u 
    cross join date_range d 

这里的关键是cross join,采取一切行的users表,并使用在date_range表的每一行关联起来。 distinct关键字实际上只是所有列上的group by的简写,并且在这里是为了防止重复数据。

请注意,还有其他几种获取相同结果集的方法(就像在我的原始查询中一样),但从逻辑和计算的角度来看,这可能是最简单的方法。

真的,唯一的步骤是添加left join(所有上面我们得到了所有可用的数据行的关联,并没有删除任何不具有任何数据)和group byselect组件它们基本上和以前一样。因此,把一切融合在一起,它看起来像这样:

select t.user_id, t.d_date, 
    sum(m.m_time <= '16:00') as before16, 
    sum(m.m_time > '16:00') as after16 
from (
    select distinct u.user_id, d.d_date 
    from users u 
     cross join date_range d 
) t 
    left join messages m on t.user_id = m.user_id 
         and t.d_date = m.m_date 
group by t.user_id, t.d_date 

基于一些其他的意见/问题,请注意明确使用前缀的所有表和子查询的所有用途(这是非常简单的,因为我们”不再使用任何表格):uusers表格,ddate_range表格,t为包含每个用户使用日期的子查询,mmessage表格。这可能是我的第一个解释有点短的地方,因为我使用了两次消息表,两次都使用相同的前缀。它在那里工作是因为两种用法的环境(一种用于子查询),但它可能不是最佳做法。

+0

请参阅更新的OP。我有一个用户表,并重新命名了一些列(例如m_date和d_date),因为我很困惑,试图理解你的查询。请您更新您的查询,以便我可以理解? – Pr0no 2012-04-12 18:59:02

+0

@ Pr0no,编辑是否有助于解释事情? – chezy525 2012-04-13 15:47:37

相关问题