SQL для поиска родителей, у которых ВСЕ дети разные

У меня есть таблица package_scans с полями package_id и scan_id и еще одна таблица scan_content с полями scan_id и epc.

Между таблицами существует связь 1-N, поэтому для каждой package_scan имеется несколько записей в scan_content.

Я пытаюсь найти все сканы пакетов, где есть еще один скан, но с другим содержимым.

Пример (для простоты все сканы имеют ровно 2 epc, могут быть от 1 до *):

Таблица package_scans:

package_id скан_ид 1 111 1 112 2 221 2 222 3 331 4 441 4 442 4 443

Таблица scan_content:

scan_id ЭПК 111 а1 111 а2 112 а3 112 а4 221 б1 221 б2 222 б2 222 б3 331 с1 331 с2 441 d1 441 d2 442 d1 442 d2 443 д3 443 d4

Единственным допустимым результатом будет package_id 1, поскольку в этом пакете есть два сканирования (111 и 112) с совершенно разными epc.

Пакет 2 для меня не актуален, в нем тоже есть два скана (221 и 222), но есть перекрытие в epc (оба имеют b2). Пакет 3 меня не касается, так как он содержит только одно сканирование.

Пакет 4 действительно перекрывает сканирование 441 и 442, но сканирование 443 является совершенно новым, поэтому пакет 4 актуален и должен быть частью набора результатов.

Я пробовал использовать разные версии EXISTS и NOT EXISTS, но дошел только до того, что в результате получил как package_id 1, так и 2, а не только 1.

Вот мой лучший подход:

SELECT package_id FROM package_scans ps1 
JOIN scan_content sc1 ON ps1.scan_id = sc1.scan_id
WHERE EXISTS (
      SELECT 1 FROM package_scans ps2
      WHERE ps1.package_id = ps2.package_id
      AND ps1.scan_id != ps2.scan_id
      AND NOT EXISTS (
            SELECT 1 FROM scan_content sc2
            WHERE ps2.scan_id = sc2.scan_id
            AND sc1.epc = sc2.epc
            )
      )

Вот мои мысли о моем SQL: Существует package_scan, где существует другой package_scan для того же package_id, но с другим scan_id. Это второе сканирование не имеет ни одного EPC, соответствующего EPC из первого сканирования.

К сожалению, этот SQL приводит как к package_id 1, так и к 2.

Заранее спасибо за любые советы!

Знакомы ли вам с концепцией реляционного деления ? Там есть примеры на SQL.

Dai 06.08.2024 10:34

Какая это СУБД?

Charlieface 06.08.2024 13:17

EPC не может повторяться для каждой упаковки, и это все?

Salman Arshad 06.08.2024 13:35

Что касается ваших вопросов: - Нет, я пока не знаком с реляционным разделением - Это в PostgreSQL - правильно, epc уникален для каждого пакета.

Budenzauber 07.08.2024 08:17

Хорошо, просто чтобы прояснить новые требования: если есть package_id, у которого есть хотя бы один scan_id со всеми его epc, не перекрывающимися ни с каким другим scan_id набором epc, то вы хотите, чтобы этот package_id был включен? Что произойдет, если он полностью перекроет набор из другого package_id? Например, что, если бы у вас был лишний ряд 443 | a1?

Charlieface 07.08.2024 11:49

Да, для меня важен только первый случай. Если в отсканированных epcs есть совпадение с разными идентификаторами package_ids (как в вашем примере 443 | a1), это другой вариант использования и не должен быть частью этого SQL.

Budenzauber 07.08.2024 13:02

Итак, если бы эта строка существовала, каков был бы результат?

Charlieface 07.08.2024 13:16

package_id 4 все равно будет в наборе результатов, так как я хочу сравнивать epcs только с тем же package_id.

Budenzauber 08.08.2024 08:10
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
8
124
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

ОБНОВЛЕННЫЙ ОТВЕТ:
Перекрытия — это проблема, которая требует двойных соединений для получения результата.

WITH    /*  S a m p l e    D a t a  */
  package_scans ( package_id, scan_id ) AS 
    ( Select 1, 111 Union All 
      Select 1, 112 Union All 
      Select 2, 221 Union All 
      Select 2, 222 Union All 
    Select 2, 223 Union All      /* added row  */
      Select 3, 331 
    ),
  scan_content ( scan_id, epc ) AS 
    ( Select 111, 'a1' Union All 
      Select 111, 'a2' Union All 
      Select 112, 'a3' Union All 
      Select 112, 'a4' Union All 
      Select 221, 'b1' Union All
      Select 221, 'b2' Union All 
      Select 222, 'b2' Union All
      Select 222, 'b3' Union All 
  Select 223, 'b4' Union All      /* added row  */
  Select 223, 'b5' Union All      /* added row  */
      Select 331, 'c1' Union All
      Select 331, 'c2' 
    ), 

... создайте cte для нескольких объединений в основном sql...

  pcte AS
    ( Select     ps.package_id,  ps.scan_id, s.epc, 
                    Count(ps.package_id) Over(Partition By ps.package_id) /
                    Count(ps.package_id) Over(Partition By ps.scan_id) as ps_count, 
                    Count(s.epc) Over(Partition By ps.package_id, s.epc) as s_count
        From       package_scans ps
        Inner Join scan_content s ON( s.scan_id = ps.scan_id )
      )

... вам нужно самостоятельно присоединиться к cte с помощью агрегации ДВАЖДЫ: один раз для epc и один раз для уникальности scan_id...

/*    M a i n    S Q L :    */
SELECT p.package_id 
FROM   pcte p
INNER JOIN (Select package_id, epc, Max(s_count) as max_count
            From pcte 
            Where ps_count > 1
            Group By package_id, epc
            Having Max(s_count) = 1 ) s ON(s.package_id = p.package_id And 
                                          s.epc = p.epc)
INNER JOIN (Select package_id, scan_id, Max(s_count) as max_count
            From pcte 
            Where ps_count > 1
            Group By package_id, scan_id
            Having Max(s_count) = 1 ) s2 ON(s2.package_id = p.package_id And 
                                            s2.scan_id = p.scan_id)
WHERE     p.ps_count > 1 
GROUP BY  p.package_id
/*  R e s u l t : 
package_id 
----------
         1    
         2    */

См. скрипку здесь.

Привет! К сожалению, этот подход работает не во всех случаях. Представьте себе, что у вас есть дополнительное сканирование для package_id 2, scan_id 223 со всеми новыми epcs (b4, b5). Поскольку это сканирование является новым epcs по сравнению со сканированием 221 и 222, мне нужно, чтобы package_id 2 был частью набора результатов.

Budenzauber 07.08.2024 09:44

@Budenzauber Только что опубликовал обновление, проверьте его еще раз....

d r 07.08.2024 11:20

@Budenzauber Извините, все еще не в порядке... Работаю над этим...

d r 07.08.2024 11:49

@Budenzauber Теперь он должен выполнить свою работу, проверьте его и дайте мне знать ...

d r 07.08.2024 12:03

работает как шарм, спасибо!

Budenzauber 09.08.2024 10:45

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

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

Сгруппируйте по package_id и epc, учитывая количество epc, а также минимальное и максимальное scan_id.

Затем снова сгруппируйте по package_id, проверяя, что min и max scan_id различны (и, следовательно, как минимум два сканирования), и что счетчик для каждого epc равен 1 (и, следовательно, нигде в package_id нет перекрытий.

SELECT
  ps.package_id
FROM (
    SELECT
      ps.package_id,
      COUNT(*) AS count_per_epc,
      MIN(sc.scan_id) AS min_scan,
      MAX(sc.scan_id) AS max_scan
    FROM package_scans ps
    JOIN scan_content sc ON sc.scan_id = ps.scan_id
    GROUP BY
      ps.package_id,
      sc.epc
) ps
GROUP BY
  ps.package_id
HAVING MIN(ps.min_scan) <> MAX(ps.max_scan)  -- at least two scans
   AND COUNT(CASE WHEN ps.count_per_epc > 1 THEN 1 END) = 0  -- none;

Альтернативный, более краткий, но более неясный синтаксис конечного условия:

   AND COUNT(NULLIF(ps.count_per_epc, 1)) = 0;

db<>рабочий пример

Scan_id из моего примера немного упрощен, так как на самом деле это поле является UUID. Ваш подход с MIN/MAX все еще работает?

Budenzauber 07.08.2024 08:22

Обновлено: на самом деле тип данных является «текстовым» и основан на документации Postgres, которая все равно должна работать. Попробую и дам вам знать!

Budenzauber 07.08.2024 08:31

Та же проблема, что и в первом предложенном решении: если есть перекрытие между двумя сканированиями пакета (как в 221 и 222), но есть третье сканирование (223) со всеми новыми epcs, тогда package_id 2 должен быть в наборе результатов. , но так как есть перекрытие хотя бы с одним epc(b2) это не работает. Я добавил примеры сканирования пакета 4 с этим сценарием в свой вопрос выше.

Budenzauber 07.08.2024 09:53

Другие вопросы по теме