Как проверить, является ли массив чисел подмножеством разделённых запятыми чисел в строке?

Пытаюсь проверить, является ли массив чисел подмножеством разделённых запятыми чисел в строке.

Например, я хочу получить ID записей в таблице:

ID STRING1 STRING2
1 100,101,201 1,2,3,4
2 100,103,201,202 1,4,9,10
3 101,102,200 1,3,4

которые удовлетворяют условию (псевдокод):

(100,201) is subset of STRING1 AND (1,4) is subset of STRING2

Ожидаемый результат: ID 1 и 2


Свободный перевод вопроса How to find numbers inside selected string? от участника @maxime mkhe


Ответы (2 шт):

Автор решения: 0xdb

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

Идея заключается в том, чтобы написать функцию, которая разбивает списки чисел и возвращает вместо них вложенную таблицу. Потребуется глобальный тип данных (на уровне схемы).

Подготовка схемы:

create or replace type tbl_of_num is table of number;
/
create or replace function str_to_tbl(s varchar2) return tbl_of_num deterministic is
    pragma udf;
    ton tbl_of_num;
begin
    select cast(collect(val) as tbl_of_num) into ton
    from json_table('[' || s || ']', '$[*]' columns val number path '$');
    return ton;
end;
/
create table t (id, string1, string2) as
    select 1, '100,101,201', '1,2,3,4'  from dual union all
    select 2, '100,103,201', '1,4,9,10' from dual union all
    select 3, '101,102,200', '1,3,4,10' from dual;

Сначала определяю две переменные связывания, инициализирую их и использую в запросе:

variable string1 varchar2(100)
variable string2 varchar2(100)

exec :string1 := '100,201'; :string2 := '1,4'

select id
from   t
where str_to_tbl(:string1) submultiset of str_to_tbl(string1)
  and str_to_tbl(:string2) submultiset of str_to_tbl(string2);

        ID
----------
         1
         2

Возможны другие способы передачи значений в переменных связывания, это зависит от приложения.


Свободный перевод ответа от участника @mathguy

→ Ссылка
Автор решения: 0xdb

Ешё два пути решения задачи без создания дополнительных обьектов БД.

Для удобства создал только одну вложенную таблиицу numtab, но если и это нежелательно, для непривилегированных пользователей доступнa mdsys.sdo_numtab.

Наиболее эффективно, с обявлением функции в запросе:

with 
function toNumTab (str varchar2) return numtab is
    pos int := 1; len int := 0; 
    r numtab := numtab ();
begin <<split>> loop
        len := instr (str||',', ',', pos);
        exit split when len = 0;
        r.extend; r(r.last) := to_number (substr (str, pos, len-pos));
        pos := len+1; 
    end loop;
    return r;
end;
args (arg1, arg2) as (
    select numtab (100, 201), numtab (1, 4) 
    from dual 
)  
select id
from t, args
where args.arg1 submultiset of toNumTab (string1) and
      args.arg2 submultiset of toNumTab (string2);

На чистом SQL можно расспарсить список чисел из строки стандартной функцией XMLTable, но в этом случае, надо будет ее результат сначало конвертировать во вложеную таблицу, чтобы воспользоваться фунццией submultiset:

with 
t1 (id, nt1, nt2) as (
    select id, 
        (select cast (collect (to_number (column_value)) as numtab) from xmlTable (string1)),
        (select cast (collect (to_number (column_value)) as numtab) from xmlTable (string2))
    from t
    ),
args (arg1, arg2) as (
    select numtab (100, 201), numtab (1, 4)
    from dual 
)  
select id
from t1, args
where arg1 submultiset of t1.nt1 and
      arg2 submultiset of t1.nt2

Оба запроса выше вернут ожидаемый результат (см. db<>fiddle).

→ Ссылка