У меня есть таблица package_scans
с полями package_id
и scan_id
и еще одна таблица scan_content
с полями scan_id
и epc
.
Между таблицами существует связь 1-N, поэтому для каждой package_scan
имеется несколько записей в scan_content
.
Я пытаюсь найти все сканы пакетов, где есть еще один скан, но с другим содержимым.
Пример (для простоты все сканы имеют ровно 2 epc
, могут быть от 1 до *):
Таблица package_scans
:
Таблица scan_content
:
Единственным допустимым результатом будет 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.
Заранее спасибо за любые советы!
Какая это СУБД?
EPC не может повторяться для каждой упаковки, и это все?
Что касается ваших вопросов: - Нет, я пока не знаком с реляционным разделением - Это в PostgreSQL - правильно, epc уникален для каждого пакета.
Хорошо, просто чтобы прояснить новые требования: если есть package_id
, у которого есть хотя бы один scan_id
со всеми его epc
, не перекрывающимися ни с каким другим scan_id
набором epc
, то вы хотите, чтобы этот package_id
был включен? Что произойдет, если он полностью перекроет набор из другого package_id
? Например, что, если бы у вас был лишний ряд 443 | a1
?
Да, для меня важен только первый случай. Если в отсканированных epcs есть совпадение с разными идентификаторами package_ids (как в вашем примере 443 | a1), это другой вариант использования и не должен быть частью этого SQL.
Итак, если бы эта строка существовала, каков был бы результат?
package_id 4 все равно будет в наборе результатов, так как я хочу сравнивать epcs только с тем же package_id.
ОБНОВЛЕННЫЙ ОТВЕТ:
Перекрытия — это проблема, которая требует двойных соединений для получения результата.
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 Только что опубликовал обновление, проверьте его еще раз....
@Budenzauber Извините, все еще не в порядке... Работаю над этим...
@Budenzauber Теперь он должен выполнить свою работу, проверьте его и дайте мне знать ...
работает как шарм, спасибо!
Это тип реляционного деления без остатка, особенность которого состоит в том, что у вас есть несколько делителей и вам нужно, чтобы делимое было больше 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;
Scan_id из моего примера немного упрощен, так как на самом деле это поле является UUID. Ваш подход с MIN/MAX все еще работает?
Обновлено: на самом деле тип данных является «текстовым» и основан на документации Postgres, которая все равно должна работать. Попробую и дам вам знать!
Та же проблема, что и в первом предложенном решении: если есть перекрытие между двумя сканированиями пакета (как в 221 и 222), но есть третье сканирование (223) со всеми новыми epcs, тогда package_id 2 должен быть в наборе результатов. , но так как есть перекрытие хотя бы с одним epc(b2) это не работает. Я добавил примеры сканирования пакета 4 с этим сценарием в свой вопрос выше.
Знакомы ли вам с концепцией реляционного деления ? Там есть примеры на SQL.