Попытка найти элегантный способ фильтровать запрос, используя BITAND
, где значения, которые должны быть and
ed, предоставляются параметром с несколькими значениями.
Данные тестирования:
WITH
patient as
(
select 1 patient_id, 'foo' patient_name from dual
union all
select 2 patient_id, 'bar' patient_name from dual
union all
select 3 patient_id, 'baz' patient_name from dual
union all
select 4 patient_id, 'zoo' patient_name from dual
)
,
-- each organ is a power of 2
organ as
(
select 2 organ_id, 'Lung' organ_name from dual
union all
select 4 organ_id, 'Pancreas' organ_name from dual
union all
select 8 organ_id, 'Liver' organ_name from dual
union all
select 16 organ_id, 'Kidney' organ_name from dual
)
,
patient_organ as
(
-- patient with a multi-organ transplant
select 1 patient_id, 4 organ_id from dual
union all
select 1 patient_id, 16 organ_id from dual
union all
-- patient with a single-organ transplant
select 2 patient_id, 4 organ_id from dual
union all
-- patient with a multi-organ transplant
select 3 patient_id, 8 organ_id from dual
union all
select 3 patient_id, 16 organ_id from dual
union all
-- patient with a single-organ transplant
select 4 patient_id, 2 organ_id from dual
)
Этот запрос:
select p.patient_id, p.patient_name
,po.bits,po.organs
from patient p
inner join (
SELECT patient_id, sum(organ_id) AS BITS
,listagg(organ, '; ') within group (order by organ_id) ORGANS
FROM (
SELECT patient_id, po.organ_id, o.organ_name || ' [' || o.organ_id || ']' organ
FROM patient_organ po
INNER JOIN organ o ON po.organ_id = o.organ_id
)
GROUP BY patient_id
) po on p.patient_id=po.patient_id
Формирует желаемый набор данных; отображаются несколько органов (например, Pancreas [4]; Kidney [16]
):
PATIENT_ID, PATIENT_NAME, BITS, ORGANS
1 foo 20 Pancreas [4]; Kidney [16]
2 bar 4 Pancreas [4]
3 baz 24 Liver [8]; Kidney [16]
4 zoo 2 Lung [2]
Я хочу иметь возможность предоставить значение параметра 4,8
и получить следующие результаты:
PATIENT_ID, PATIENT_NAME, BITS, ORGANS
1 foo 20 Pancreas [4]; Kidney [16]
2 bar 4 Pancreas [4]
3 baz 24 Liver [8]; Kidney [16]
Если у меня есть одно значение (смоделировано с помощью :organ
= 4), я могу использовать BITAND
и получить значения для нескольких органов:
select p.patient_id, p.patient_name
,po.bits,po.organs
from patient p
inner join (
SELECT patient_id, sum(organ_id) AS BITS
,listagg(organ, '; ') within group (order by organ_id) ORGANS
FROM (
SELECT patient_id, po.organ_id, o.organ_name || ' [' || o.organ_id || ']' organ
FROM patient_organ po
INNER JOIN organ o ON po.organ_id = o.organ_id
)
GROUP BY patient_id
) po on p.patient_id=po.patient_id
WHERE bitand(bits,:organ)=:organ
Сохранение нескольких органов:
PATIENT_ID, PATIENT_NAME, BITS, ORGANS
1 foo 20 Pancreas [4]; Kidney [16]
2 bar 4 Pancreas [4]
Я могу использовать параметр с несколькими значениями (смоделированный &organs
= 4,8):
select p.patient_id, p.patient_name
,po.bits,po.organs
from patient p
inner join (
SELECT patient_id, sum(organ_id) AS BITS
,listagg(organ, '; ') within group (order by organ_id) ORGANS
FROM (
SELECT patient_id, po.organ_id, o.organ_name || ' [' || o.organ_id || ']' organ
FROM patient_organ po
INNER JOIN organ o ON po.organ_id = o.organ_id
WHERE po.organ_id IN (&organs)
)
GROUP BY patient_id
) po on p.patient_id=po.patient_id
Но при этом теряются мультиорганные результаты:
PATIENT_ID, PATIENT_NAME, BITS, ORGANS
1 foo 4 Pancreas [4]
2 bar 4 Pancreas [4]
3 baz 8 Liver [8]
В идеале я мог бы использовать функцию BITAND
с оператором IN
, но это синтаксически неверно.
Есть ли другой непроцедурный подход?
** редактировать **
Чтобы уточнить, я ссылаюсь на этот SQL в инструменте создания отчетов (Crystal Reports). Инструмент позволяет вам выбрать одно или несколько значений параметров: вы видели organ_name
, но organ_id
входит в комплект. Более того, значения параметра предоставляются в виде массива или строки с разделителями-запятыми (трудно сказать, какая именно), а не суммируются до одного значения (как предлагается в ответе и комментариях). Эта архитектура делает это трудным.
Если я правильно понял ваш вопрос, вы хотите запросить ваши сводные данные для пациентов, которым была проведена хотя бы одна трансплантация органов из списка. Вы должны иметь возможность сделать это, просто изменив предикат с первой попытки:
WHERE bitand(bits,:organ)=:organ
к
WHERE bitand(bits,:organ) != 0
Затем вы можете указать битовую маску интересующих вас органов (например, битовая маска для Pancreas [4]
и Kidney [16]
будет 20
, а битовая маска для Pancreas [4]
и Liver [8]
будет 12
). Это будет работать, поскольку до тех пор, пока один бит в маске совпадает с битом, и результат будет отличным от нуля, если ни один бит не соответствует биту, и результат будет равен нулю.
Чтобы использовать параметр с несколькими значениями, вам просто нужно преобразовать его в битовую маску и заменить значение привязки: organ производной битовой маской следующим образом:
WHERE bitand(bits,(select sum(distinct organ_id) from organ where organ_id in (&organs))) !=0
В этом случае я моделирую многозначный параметр, как вы, и конвертирую его в битовую маску в подзапросе со скалярными значениями к таблице органов.
Спасибо за вотум доверия @mathguy. Я тоже изначально использовал > 0
, но решил переключиться на != 0
, так как функция bitand работает с двоичными целыми числами с дополнением n-bit и двумя. Таким образом, они могут принимать отрицательные значения в дополнение к положительным. Использование !=
вместо этого позволяет использовать знаковый бит в дополнение ко всем остальным битам. В Oracle 12c n = 128 бит, поэтому вы можете иметь не более 128 различных элементов, идентифицируемых одной битовой маской.
Думаю, из-за дополнительного числа до двух BITAND
работает только до 127 бит. Если вам нужно работать с более чем 127 битами, используйте функцию UTL_RAW.BIT_AND
.
@craig - я согласен - Sentinel опередил меня. Для дальнейшего уточнения: когда вы сравниваете два многобитовых кода (например, значения
BITS
для отдельных пациентов с кодом, например, 20 для проверки поджелудочной железы или почек), состояниеbitand(bits, :organ) = :organ
означает, что пациенту были трансплантаты для ВСЕХ кодов в:organ
. . Напротив, условиеbitand(bits, :organ) > 0
(как я бы предпочел его записать) означает, что эти два пересечения имеют непустое значение - пациенту была проведена по крайней мере ОДНА из трансплантатов органов, представленных в битовом коде:organ
.