улей sql агрегат - PullRequest
       12

улей sql агрегат

4 голосов
/ 29 сентября 2011

У меня есть две таблицы в Hive, t1 и t2

>describe t1;
>date_id    string

>describe t2;
>messageid string,
 createddate string,
 userid int

> select * from t1 limit 3;        
> 2011-01-01 00:00:00 
  2011-01-02 00:00:00 
  2011-01-03 00:00:00 

> select * from t2 limit 3;
87211389    2011-01-03 23:57:01 13864753
87211656    2011-01-03 23:57:59 13864769
87211746    2011-01-03 23:58:25 13864785

То, что я хочу, это подсчитать предыдущий трехдневный отдельный ИД пользователя для данной даты.
Например, для даты 2011-01-03 я хочу посчитать отличный идентификатор пользователя от 2011-01-01 до 2011-01-03.
для даты 2011-01-04, я хочу посчитать отличный идентификатор пользователя от 2011-01-02 до 2011-01-04

Я написал следующий запрос. Но это не возвращает трехдневный результат. Вместо этого он возвращает отдельный идентификатор пользователя в день.

SELECT to_date(t1.date_id), count(distinct t2.userid) FROM t1 JOIN t2 
ON (to_date(t2.createddate) = to_date(t1.date_id))  
WHERE date_sub(to_date(t2.createddate),0) > date_sub(to_date(t1.date_id), 3) 
AND to_date(t2.createddate) <= to_date(t1.date_id) 
GROUP by to_date(t1.date_id);

`to_date()` and `date_sub()` are date function in Hive. 

Тем не менее, следующая часть не вступает в силу.

WHERE date_sub(to_date(t2.createddate),0) > date_sub(to_date(t1.date_id), 3) 
AND to_date(t2.createddate) <= to_date(t1.date_id) 

РЕДАКТИРОВАТЬ: Одно решение может быть (но это очень медленно):

SELECT to_date(t3.date_id), count(distinct t3.userid) FROM
(
 SELECT * FROM t1  LEFT OUTER JOIN t2
 WHERE 
 (date_sub(to_date(t2.createddate),0) > date_sub(to_date(t1.date_id), 3)
  AND to_date(t2.createddate) <= to_date(t1.date_id)
 )
) t3 
GROUP by to_date(t3.date_id);

ОБНОВЛЕНИЕ: Спасибо за все ответы. Они хороши.
Но Hive немного отличается от SQL. К сожалению, их нельзя использовать при ВИЧ. Мое текущее решение заключается в использовании UNION ALL.

 SELECT * FROM t1 JOIN t2 ON (to_date(t1.date_id) = to_date(t2.createddate))
 UNION ALL
 SELECT * FROM t1 JOIN t2 ON (to_date(t1.date_id) = date_add(to_date(t2.createddate), 1)
 UNION ALL 
 SELECT * FROM t1 JOIN t2 ON (to_date(t1.date_id) = date_add(to_date(t2.createddate), 2)

Затем я делаю group by и count. Таким образом, я могу получить то, что хочу.
Хотя это не элегантно, но гораздо эффективнее, чем cross join.

Ответы [ 4 ]

11 голосов
/ 06 октября 2011

Следующее должно работать в стандартном SQL ...

SELECT
  to_date(t1.date_id),
  count(distinct t2.userid)
FROM
  t1
LEFT JOIN
  t2
    ON  to_date(t2.createddate) >= date_sub(to_date(t1.date_id), 2)
    AND to_date(t2.createddate) <  date_add(to_date(t1.date_id), 1)
GROUP BY
  to_date(t1.date_id)

Это будет , однако будет медленным. Поскольку вы храните даты в виде строк, используйте to_date () для их преобразования в даты. Это означает, что индексы не могут быть использованы, и механизм SQL не может сделать что-нибудь умное, чтобы уменьшить затрачиваемое усилие.

В результате каждая возможная комбинация строк должна сравниваться. Если у вас есть 100 записей в T1 и 10000 записей в T2, ваш механизм SQL обрабатывает миллион комбинаций.

Если вы храните эти значения как даты, вам не нужно to_date(). И если вы индексируете даты, механизм SQL может быстро найти указанный диапазон дат.

ПРИМЕЧАНИЕ. Формат предложения ON означает, что вам не нужно округлять t2.createddate до дневного значения.


РЕДАКТИРОВАТЬ Почему ваш код не работает ...

SELECT to_date(t1.date_id), count(distinct t2.userid) FROM t1 JOIN t2 
ON (to_date(t2.createddate) = to_date(t1.date_id))  
WHERE date_sub(to_date(t2.createddate),0) > date_sub(to_date(t1.date_id), 3) 
AND to_date(t2.createddate) <= to_date(t1.date_id) 
GROUP by to_date(t1.date_id);

Это соединяет t1 с t2 с предложением ON, равным (to_date(t2.createddate) = to_date(t1.date_id)). Так как объединение является LEFT OUTER JOIN, значения в t2.createddate ДОЛЖНЫ теперь либо быть NULL (нет совпадений), либо совпадать с t1.date_id.

Предложение WHERE допускает гораздо более широкий диапазон (3 дня). Но пункт ON в JOIN уже ограничил ваши данные одним днем.

Пример, который я привел выше, просто берет ваше предложение WHERE и помещает его вместо старого предложения ON.

EDIT

Hive не допускает <= и >= в предложении ON? Вы действительно настроены на использование HIVE ???

Если вы действительно, как насчет МЕЖДУ?

SELECT
  to_date(t1.date_id),
  count(distinct t2.userid)
FROM
  t1
LEFT JOIN
  t2
    ON to_date(t2.createddate) BETWEEN date_sub(to_date(t1.date_id), 2) AND date_add(to_date(t1.date_id), 1)
GROUP BY
  to_date(t1.date_id)


В качестве альтернативы, можно реорганизовать таблицу дат, чтобы перечислить даты, которые вы хотите включить ...

TABLE t1 (calendar_date, inclusive_date) =
{ 2011-01-03, 2011-01-01
  2011-01-03, 2011-01-02
  2011-01-03, 2011-01-03

  2011-01-04, 2011-01-02
  2011-01-04, 2011-01-03
  2011-01-04, 2011-01-04

  2011-01-05, 2011-01-03
  2011-01-05, 2011-01-04
  2011-01-05, 2011-01-05 }

SELECT
  to_date(t1.calendar_date),
  count(distinct t2.userid)
FROM
  t1
LEFT JOIN
  t2
    ON to_date(t2.createddate) = to_date(t1.inclusive_date)
GROUP BY
  to_date(t1.calendar_date)
3 голосов
/ 29 сентября 2011

Вам нужен подзапрос:

попробуйте что-то вроде этого (я не могу проверить, потому что у меня нет улья)

SELECT to_date(t1.date_id), count(distinct t2.userid) FROM t1 JOIN t2 
ON (to_date(t2.createddate) = to_date(t1.date_id))  
WHERE t2.messageid in 
    (
    select t2.messageid from t2 where 
    date_sub(to_date(t2.createddate),0) > date_sub(to_date(t1.date_id), 3) 
    AND 
    to_date(t2.createddate) <= to_date(t1.date_id) 
   )
GROUP by to_date(t1.date_id);

ключ в том, что с подзапросом FOR EACH date в t1, правильные записи выбираются в t2.

EDIT:

Запустив подзапрос в предложении from, вы можете попробовать это:

SELECT to_date(t1.date_id), count(distinct t2.userid) FROM t1 JOIN 

(select userid, createddate  from t2 where 

    date_sub(to_date(t2.createddate),0) > date_sub(to_date(t1.date_id), 3) 
    AND 
    to_date(t2.createddate) <= to_date(t1.date_id) 
) as t2

ON (to_date(t2.createddate) = to_date(t1.date_id))  

GROUP by to_date(t1.date_id);

но не знаю, может ли это сработать.

2 голосов
/ 06 октября 2011

Я предполагаю, что t1 используется для определения 3-дневного периода.Я подозреваю, что загадочный подход из-за недостатков Улья.Это позволяет вам иметь произвольное количество 3-дневных периодов.Попробуйте выполнить следующие 2 запроса

SELECT substring(t1.date_id,1,10), count(distinct t2.userid) 
FROM t1 
JOIN t2 
ON substring(t2.createddate,1,10) >= date_sub(substring(t1.date_id,1,10), 2) 
AND substring(t2.createddate,1,10) <=  substring(t1.date_id,1,10) 
GROUP BY t1.date_id 

- или -

SELECT substring(t1.date_id,1,10), count(distinct t2.userid) 
FROM t1 
JOIN t2 
ON t2.createddate like substring(t1.date_id ,1,10) + '%' 
OR t2.createddate like substring(date_sub(t1.date_id, 1) ,1,10) + '%' 
OR t2.createddate like substring(date_sub(t1.date_id, 2) ,1,10) + '%' 
GROUP BY t1.date_id 

Последний сводит к минимуму вызовы функций в таблице t2.Я также предполагаю, что t1 является меньшей из 2. подстрока должна возвращать тот же результат, что и to_date.Согласно документации, https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#LanguageManualUDF-DateFunctions, to_date возвращает строковый тип данных.Поддержка типов данных даты кажется минимальной, но я не знаком с ульем.

1 голос
/ 06 октября 2011

1.Я не знаком с Hive.

2.Вы можете попробовать использовать подзапрос в FROM clase:

SELECT  T1.date_id, COUNT(x.userid) AS UserCount
FROM    T1
LEFT OUTER JOIN
(
    SELECT  TO_DATE(createddate) AS date_id, userid
    FROM    T2
    GROUP BY TO_DATE(createddate), userid
) X ON DATE_SUB(TO_DATE(T1.date_id),3) <= X.date_id AND X.date_id <= TO_DATE(T1.date_id)
GROUP BY T1.date_id;
...