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」という2番目のテーブルがあります。

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

この表に対して「メッセージ」を確認したい。ユーザーごとに、これらすべての日付を結果セットに含める必要があります。ご覧のとおり、2011-01-21にはどのユーザーもメッセージを書き込んでおらず、前述のように、ユーザー1には2011-01-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

2つのテーブルをリンクして、クエリ結果にbefore16とafter16の値がゼロの行も含まれるようにするにはどうすればよいですか?

編集:はい、「ユーザー」テーブルがあります:

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

4 に答える 4

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. 最初のクエリはそのままです。説明が必要ないことを願っています。

  2. SELECT DISTINCT user_id, dr._date FROM messages m, date_range drデカルトまたはCROSS JOIN2つのテーブルが返されます。これにより、対象の各ユーザーに必要なすべての日付範囲がわかります。各ペアに一度だけ興味があるので、DISTINCT節を使用します。このクエリを使用して、または使用せずに試してください。

  3. 次にLEFT JOIN、2つのサブ選択で使用します。

    この結合とは、最初にINNER結合が実行されることを意味します。つまり、条件内のフィールドが一致するすべての行ONが返されます。次に、右側に一致するものがない結合の左側の関係の各行について、NULLsを返します(したがって、名前、LEFT JOINつまり、左側の関係は常に存在し、右側にはNULLsが必要です)。この結合は、期待どおりにuser_id + date機能します。特定のユーザーの特定の日付にメッセージがなかった場合でも、組み合わせを返します。user_id + date最初に副選択(左側)をmessages使用し、次にクエリ(右側)を使用することに注意してください。

  4. coalesce()ゼロに置き換えるためNULLに使用されます。

これにより、このクエリがどのように機能するかが明確になることを願っています。

于 2012-04-12T15:42:11.073 に答える
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です。キーワードは実際にはすべての列のdistinctaの省略形であり、group byデータが重複している場合に備えてここにあります。

これと同じ結果セットを取得する方法は他にもいくつかあることに注意してください(私の元のクエリのように)が、これは論理的および計算的観点からおそらく最も簡単です。

実際、他の唯一の手順は、left join(上記で取得したすべての行を使用可能なすべてのデータに関連付け、データがないものは削除しない)と、基本的に以前と同じコンポーネントをgroup by追加することです。selectしたがって、すべてをまとめると、次のようになります。

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

他のいくつかのコメント/質問に基づいて、すべてのテーブルとサブクエリのすべての使用にプレフィックスを明示的に使用していることに注意してください(これは、テーブルを2回以上使用していないため、非常に簡単ですu)。テーブル、各ユーザーに使用する日付を含むサブクエリ、およびテーブル。これはおそらく、メッセージテーブルを2回使用したため、最初の説明が少し不足した場所です。どちらも同じプレフィックスを使用しています。両方の使用法(1つはサブクエリ内にあった)のコンテキストのためにそこで機能しますが、おそらくベストプラクティスではありません。usersddate_rangetmmessage

于 2012-04-12T16:00:07.160 に答える
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
于 2012-04-12T15:27:36.517 に答える
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;

私のマシンで実行し、完璧に動作しました

于 2012-04-12T15:22:22.120 に答える