SQL как: Как рассчитать пересечение и объединениеданные - PullRequest
0 голосов
/ 18 мая 2018

Нужна помощь в SQL:

У меня есть данные со следующими столбцами:

  • ItemId
  • UserId

Каждыйстрока указывает, что какой-то элемент был куплен каким-либо пользователем.Пример:

ItemId UserId

   200    user1

   200    user3

   200    user4

   300    user5

   300    user3

для каждого я хотел бы рассчитать следующую таблицу вывода:

  • users (i): количество купленных пользователей i
  • users (j): количество купленных пользователей j
  • пользователей (i, j): количество пользователей, купленных i и j
  • пользователей (i, ~ j): количество пользователей, купленных i, но неj
  • users (~ i, j): количество пользователей, купленных j, но не i

Пример вывода (из приведенного выше примера):

i_itemId  j_itemId  users(i)  users(j)  users(i,j)  users(i,~j)  users(~i, j)

200  200  3  3  3  0  0

200  300  3  2  1  2  1

300  300  2  2  2  0  0

300  200  3  2  1  1  2

Примечание :

  1. Таблица данных огромного размера (11 ГБ) расположена в облаке.У меня есть рамки SQL для работы.Поэтому я не могу загрузить файл и запустить python (например). Поэтому решение должно быть эффективно написано на SQL
  2. Решение не должно быть одним единственным оператором SQL.
  3. Я ищу эффективное решение
  4. Мы можем предположить, что это ключ
  5. Если у кого-то есть лучшая альтернатива для заголовка Вопроса здесь, я буду рад обновить его:)

Ответы [ 3 ]

0 голосов
/ 18 мая 2018

Вот рецепт

1) Создайте временную таблицу для сбора итогов I и J.

Отказ от ответственности:
В этом примере используется тип данных сервера MS SQL: INT.
Так что измените его на числовой тип, поддерживаемый вашей СУБД.
Кстати, в MS SQL Server временные таблицы начинаются с #

create table TempTotals (iItemId int, jItemId int, TotalUsers int); 

2) Заполните итоги

delete from TempTotals;
insert into TempTotals (iItemId, jItemId, TotalUsers)
select 
    t1.ItemId as iItemId, 
    t2.ItemId as jItemId, 
    count(distinct t1.UserId) as TotalUsers
from YourTable t1
full join YourTable t2 on (t1.UserId = t2.UserId)
group by t1.ItemId, t2.ItemId;

3) Самостоятельно присоединиться к временной таблице, чтобы получить все итоги

select 
 ij.iItemId, 
 ij.jItemId,
 i.TotalUsers as Users_I,
 j.TotalUsers as Users_J,
 ij.TotalUsers as Users_I_and_J, 
 (i.TotalUsers - ij.TotalUsers) as Users_I_no_J,
 (j.TotalUsers - ij.TotalUsers) as Users_J_no_I
from TempTotals ij
left join TempTotals i on (i.iItemId = ij.iItemId and i.iItemId = i.jItemId)
left join TempTotals j on (j.jItemId = ij.jItemId and j.iItemId = j.jItemId)
0 голосов
/ 18 мая 2018

Если вы используете Oracle Database, вы можете сравнить вложенные таблицы (коллекции) с операторами мультимножества.И получите количество элементов в коллекции с количеством элементов.

Итак, что вы можете сделать:

  • Группировать по itemid, собирая всех пользователей во вложенную таблицу
  • Кросс-соединение этого выхода с самим собой
  • Используйте мультисетевые операторы пересечения / исключения, чтобы получить необходимое количество элементов в наборах

Что выглядит примерно так:

create table t (
  ItemId int, UserId varchar2(10)
);
insert into t values (   200  ,  'user1');
insert into t values (   200  ,  'user3');
insert into t values (   200  ,  'user4');
insert into t values (   300  ,  'user5');
insert into t values (   300  ,  'user3');

commit;

create or replace type users_t as table of varchar2(10);
/

with grps as (
  select itemid, cast ( collect ( userid ) as users_t ) users
  from   t
  group  by itemid
)
  select g1.itemid i, g2.itemid j,
         cardinality ( g1.users ) num_i,
         cardinality ( g2.users ) num_j,
         cardinality ( g1.users multiset intersect g2.users ) i_and_j,
         cardinality ( g1.users multiset except g2.users ) i_not_j,
         cardinality ( g2.users multiset except g1.users ) j_not_i
  from   grps g1
  cross  join grps g2;

I     J     NUM_I   NUM_J   I_AND_J   I_NOT_J   J_NOT_I   
  200   200       3       3         3         0         0 
  200   300       3       2         1         2         1 
  300   200       2       3         1         1         2 
  300   300       2       2         2         0         0 

При необходимости вы можете повысить производительность, пропустив операторы кроме, когда i = j, например:

case 
  when g1.itemid = g2.itemid then 0 
  else cardinality ( g1.users multiset intersect g2.users )
end
0 голосов
/ 18 мая 2018

Я не уверен, есть ли «легкий» способ сделать это.Один из методов - грубая сила: используйте cross join для генерации всех строк.Затем используйте подзапросы для каждого отдельного счета:

select i1.itemid, i2.itemid, i1.num as cnt1, i2.num as cnt2,
       (select count(*)
        from t u1 join
             t u2
             on u1.userid = u2.userid
        where u1.itemid = i1.itemid and u2.itemid = i2.itemid
       ) as cnt_1_2,
       (select count(*)
        from t u1 left join
             t u2
             on u1.userid = u2.userid and u2.itemid = i2.itemid
        where u1.itemid = i1.itemid and u2.itemid is null
       ) as cnt_1_not2,
       (select count(*)
        from t u1 left join
             t u2
             on u1.userid = u2.userid and u1.itemid = i1.itemid
        where u2.itemid = i2.itemid and u1.itemid is null
       ) as cnt_not1_2
from (select itemid, count(*) as num from t group by itemid) i1 cross join
     (select itemid, count(*) as num from t group by itemid) i2;
...