Объединение MySQL для двух таблиц, одна с отметкой времени и одна с датой

Я пытаюсь составить отчет об эффективности одного объявления, показывающий количество просмотров и кликов за один день. Просмотры и клики хранятся в отдельных таблицах с разной структурой, поэтому я думаю, что мне нужно выполнить объединение.

Я прочитал и понял эту фантастическую статью . Это помогло мне, но я думаю, что это на один уровень сложнее, чем примеры, описанные там. Хотелось бы помощи от сообщества.

Это моя таблица views, в которой хранится счетчик количества просмотров объявления за один день.

+-------------+--------------+
| COLUMN_NAME | COLUMN_TYPE  |
+-------------+--------------+
| ad_day_id   | bigint(13)   |
| advert_id   | bigint(20)   |
| date        | date         |
| views       | mediumint(6) |
+-------------+--------------+

Это моя таблица clicks, в которой каждый клик хранится отдельно. (некоторые столбцы опущены, так как они не имеют отношения к вопросу)

+-------------+---------------------+
| COLUMN_NAME | COLUMN_TYPE         |
+-------------+---------------------+
| id          | bigint(20) unsigned |
| advert_id   | bigint(20)          |
| timestamp   | timestamp           |
+-------------+---------------------+

Результат должен выглядеть так (настоящие числа не используются, просто чтобы показать формат):

+------------+-------+--------+
| event_date | views | clicks |
+------------+-------+--------+
| 2016-05-09 |    25 |      4 |
| 2016-05-10 |     2 |        |
| 2016-05-11 |   105 |     10 |
| 2016-05-13 |    96 |      7 |
| 2016-05-14 |       |      1 |
+------------+-------+--------+

Что касается результатов:

  • Не на каждой дате будут клики или просмотры
  • На некоторые даты могут быть просмотры и нет кликов
  • На некоторые даты могут быть клики и нет просмотров

Перейдем к коду... вот что у меня сейчас есть:

SELECT
    $views_table.date AS event_date,
    $views_table.views,
    '' AS clicks
FROM
    $views_table
WHERE
    ( $views_table.date BETWEEN '$from_date' AND '$to_date' )
    AND $views_table.advert_id=$advert_id
UNION
SELECT
    CAST($clicks_table.timestamp AS DATE) AS event_date,
    '' AS views,
    COUNT($clicks_table.advert_id) AS clicks
FROM
    $clicks_table
WHERE
    ( CAST($clicks_table.timestamp AS DATE) BETWEEN '$from_date' AND '$to_date' )
    AND $clicks_table.advert_id=$advert_id
GROUP BY
    event_date
ORDER BY
    event_date ASC;

Некоторые примечания к коду:

  • Клики сохраняются индивидуально в отметке времени и, следовательно, должны быть приведены к датам, а затем сгруппированы по дате (по крайней мере, так я получил достоверные результаты для другого отчета).
  • Отчет будет содержать диапазон дат и относится к одному конкретному объявлению. Это объясняет предложения where.

При составлении этого вопроса я немного лучше отформатировал код и для удобства чтения изменил порядок операторов выбора, что решило мою первоначальную проблему. По-видимому, оба выбора должны иметь одни и те же столбцы И быть в одном и том же порядке.

Я думаю, что почти у цели, потому что это мой текущий результат:

+------------+-------+--------+
| event_date | views | clicks |
+------------+-------+--------+
| 2016-05-09 | 1     |        |
| 2016-05-09 |       | 1      |
| 2016-05-10 | 2     |        |
| 2016-05-11 | 105   |        |
| 2016-05-11 |       | 7      |
| 2016-05-13 | 96    |        |
| 2016-05-13 |       | 16     |
| 2016-05-14 | 2     |        |
| 2016-05-14 |       | 1      |
| 2016-05-15 | 2     |        |
| 2016-05-15 |       | 2      |
+------------+-------+--------+

Моя оставшаяся проблема связана с повторяющимися датами. Как я могу решить эту проблему?
Большое спасибо тем, кто так любезен ответить!


person FreshSnow    schedule 15.05.2016    source источник
comment
Я думаю, что я почти там - вы. Поместите свой код в подзапрос и используйте GROUP BY event_date. Вам нужно будет настроить SELECT.   -  person Paul Spiegel    schedule 15.05.2016


Ответы (2)


Я немного изменил ваш запрос (см. встроенные комментарии) и обернул его в подзапрос, чтобы использовать GROUP BY event_date во внешнем запросе.

SELECT event_date, MAX(views) AS views, MAX(clicks) AS clicks
FROM (
    SELECT
        views.date AS event_date,
        views.views,
        0 AS clicks -- '' causes strange results on sqlfiddle
    FROM
        views
    WHERE
        ( views.date BETWEEN '2016-05-09' AND '2016-05-15' )
        AND views.advert_id=1
    UNION
    SELECT
        CAST(clicks.timestamp AS DATE) AS event_date,
        0 AS views, -- '' causes strange results on sqlfiddle
        COUNT(clicks.advert_id) AS clicks
    FROM
        clicks
    WHERE
        ( CAST(clicks.timestamp AS DATE) BETWEEN '2016-05-09' AND '2016-05-15' )
        AND clicks.advert_id=1
    GROUP BY
        event_date
    -- ORDER BY is useless here
) sub
GROUP BY event_date
ORDER BY event_date

Демо

Вместо CAST(clicks.timestamp AS DATE) вы также можете использовать DATE(clicks.timestamp) и надеяться, что MySQL будет использовать индексы в будущем.

person Paul Spiegel    schedule 15.05.2016
comment
Большое спасибо, Пол, это работает как шарм, и я использовал все ваши предложения в своем коде. Команда DATE() работает нормально. Уууу, очень рада!! - person FreshSnow; 16.05.2016

Вам нужен не простой союз, а союз и присоединения. Итак, вам нужен союз, чтобы получить объединенный список дат как из просмотров, так и из таблицы кликов. Затем вам нужно лево присоединиться к таблицам просмотров и кликов в списке дат:

select ds.event_date, max(v.views) views, count(c.clicks) clicks
from
    (select distinct date as event_date from views
     union distinct
     select distinct date(timestamp) from clicks) ds
left join views v on ds.event_date=v.date
left join clicks c on ds.event_date=date(c.timestamp)
where ...
group by ds.event_date
person Shadow    schedule 15.05.2016
comment
Вам нужно отфильтровать подзапросы по advert_id. И (я не уверен, но) не сделает ли union distinct select distinct излишним? - person Paul Spiegel; 15.05.2016
comment
@PaulSpiegel Я тоже не уверен, поэтому я сохранил избранные отличия. Это, вероятно, избыточно в 1-м запросе. - person Shadow; 15.05.2016
comment
Документ не очень точен: повторяющиеся строки удаляются из результат. Но я проверил на sqlfiddle. Однако, если views.date уже не отличается, вы получите неправильное количество кликов из-за двойного соединения. Но это проблема ОП :-) - person Paul Spiegel; 15.05.2016
comment
Спасибо Shadow и @PaulSpiegel, я обновил свой код и после нескольких попыток получил результаты. Но результаты не правильные. Что касается просмотров, кажется, что они исходят от случайных объявлений (возможно, первого, кто зарегистрировал просмотр в этот день?), а клики - это цифры, которые я не могу отследить. Не могли бы вы взглянуть на мой обновленный код? Я создал этот Pastie, потому что поле для комментариев слишком маленькое: pastie.org/private/uvobpjedjvgrciqlfg - person FreshSnow; 15.05.2016
comment
@Frank - Похоже, вы смешали мое предложение из комментариев и решение Shadows. Это не будет работать. Попробуйте один из них. Лучше - попробуйте оба, но по отдельности. - person Paul Spiegel; 15.05.2016
comment
Извините за это... Стараюсь изо всех сил здесь :) Я также пытался получить все из подвыборки, и это дает действительные результаты для просмотров, но нет данных о кликах: pastie.org/private/dnpuyjcnaa1can0wprjka - person FreshSnow; 15.05.2016
comment
Это решение отлично работает с некоторыми изменениями: sqlfiddle.com/#!9/796b8/1 - person Paul Spiegel; 15.05.2016