Как посчитать все объединенные вхождения в SQL? - PullRequest
6 голосов
/ 08 ноября 2010

Можно ли получить подсчитанные комбинации всех элементов в одном запросе SQL без использования временных таблиц или процедур?

Рассмотрим эти три таблицы:

  • продуктов (id, имя_продукта)

  • транзакции (идентификатор, дата)

  • транзакция_has_product (идентификатор, идентификатор продукта, транзакция_id)

Пример данных

  • продукты

    1   AAA
    2   BBB
    3   CCC
    
  • транзакций

    1   some_date
    2   some_date
    
  • transaction_has_products

    1   1   1
    2   2   1
    3   3   1
    4   1   2
    5   2   2
    

Результат должен быть:

AAA, BBB = 2   
AAA, CCC = 1   
BBB, CCC = 1   
AAA, BBB, CCC = 1

Ответы [ 4 ]

1 голос
/ 14 декабря 2010

Если вы знаете, что все продукты будут впереди, вы могли бы сделать это, поворачивая данные следующим образом.

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

<code>select
    product_combination, 
    case product_combination
        when 'AAA, BBB' then aaa_bbb
        when 'AAA, CCC' then aaa_ccc
        when 'BBB, CCC' then bbb_ccc
        when 'AAA, BBB, CCC' then aaa_bbb_ccc
    end as number_of_transactions
from
(
    select 'AAA, BBB' as product_combination union all
    select 'AAA, CCC' union all
    select 'BBB, CCC' union all
    select 'AAA, BBB, CCC'
) as combination_list
cross join
(
    select
        sum(case when aaa = 1 and bbb = 1 then 1 else 0 end) as aaa_bbb,
        sum(case when aaa = 1 and ccc = 1 then 1 else 0 end) as aaa_ccc,
        sum(case when bbb = 1 and ccc = 1 then 1 else 0 end) as bbb_ccc,
        sum(case when aaa = 1 and bbb = 1 and ccc = 1 then 1 else 0 end) as aaa_bbb_ccc
    from
    (
        select
            count(case when a.product_name = 'AAA' then 1 else null end) as aaa,
            count(case when a.product_name = 'BBB' then 1 else null end) as bbb,
            count(case when a.product_name = 'CCC' then 1 else null end) as ccc,
            b.transaction_id
        from
            products a
        inner join
            transaction_has_products b
        on
            a.id = b.product_id
        group by
            b.transaction_id
    ) as product_matrix
) as combination_counts

Результат:

<code>product_combination  number_of_transactions
AAA, BBB             2
AAA, CCC             1
BBB, CCC             1
AAA, BBB, CCC        1
1 голос
/ 08 ноября 2010

Не легко, потому что у вас есть другое количество подходящих продуктов в последнем ряду по сравнению с другими рядами. Возможно, вы сможете сделать это с помощью какого-то оператора GROUP_CONCAT () (доступен в MySQL; реализуется в других СУБД, таких как Informix и, возможно, PostgreSQL), но я не уверен в этом.

Парное соответствие

SELECT p1.product_name AS name1, p2.product_name AS name2, COUNT(*)
  FROM (SELECT p.product_name, h.transaction_id
          FROM products AS p
          JOIN transactions_has_products AS h ON h.product_id = p.product_id
       ) AS p1
  JOIN (SELECT p.product_name, h.transaction_id
          FROM products AS p
          JOIN transactions_has_products AS h ON h.product_id = p.product_id
       ) AS p2
    ON p1.transaction_id = p2.transaction_id
   AND p1.product_name   < p2.product_name
 GROUP BY p1.name, p2.name;

Обработка тройного совпадения нетривиальна; продлить его дальше, чем это, безусловно, довольно сложно.

0 голосов
/ 06 мая 2015
  1. генерирует все возможные комбинации. Я поддержал себя этим: https://stackoverflow.com/a/9135162/2244766 (это немного сложно, я не до конца понимаю логику ... но это работает!)
  2. создать подзапрос, в котором вы агрегируете products_in_transactions в массивы продуктов по транзакции_id
  3. Соедините их обоих, используя операторы включения массива

После описанных выше шагов вы можете получить что-то вроде:

with all_combis as (
    with RECURSIVE y1 as (
            with x1 as (
                --select id from products
                select distinct product_id as a from transaction_has_products 
            )
            select array[a] as b ,a as c ,1 as d 
            from x1
            union all
            select b||a,a,d+1
            from x1
            join y1 on (a < c)
    )
    select *
    from y1
)
, grouped_transactions as (
  SELECT 
    array_agg(product_id) as products
  FROM transaction_has_products
  GROUP BY transaction_id
)
SELECT all_combis.b, count(*)
from all_combis
left JOIN grouped_transactions ON grouped_transactions.products @> all_combis.b 
--WHERE array_upper(b, 1) > 1 -- or whatever
GROUP BY all_combis.b
order by array_upper(b, 1) desc, count(*) desc

Вы можете присоединиться к своей таблице продуктов, чтобы заменить идентификаторы продуктов их названиями, но я думаю, вы получите их отсюда. вот скрипка (сегодня у sqlfiddle плохой день - так проверьте это на своей базе данных на случай, если он выдаст какую-то странную ошибку, такую ​​как таймаут или что-то в этом роде)

GL, HF: D

0 голосов
/ 08 ноября 2010

В зависимости от степени контроля над запросом, который вы можете сделать (это может потребоваться изменить в TSQL для postgresql)

SELECT COUNT(*) FROM transactions t WHERE
(
     SELECT COUNT(DISTINCT tp.product) 
     FROM transaction_has_products tp 
     WHERE tp.[transaction_id] = t.id and tp.product IN (1, 2, 3)
) = 3

, где (1,2,3) - это список идентификаторов, которые вы хотите проверитьа = 3 равно количеству записей в списке.

...