Нахождение имен и фамилий в другом порядке в SQL - PullRequest
2 голосов
/ 22 марта 2020

Я пытаюсь закодировать запрос SQL, который может найти одно и то же значение «выставленным» по-разному. Теперь я пытаюсь объяснить лучше.

У меня есть столбец, содержащий имена и фамилии (также, если у вас их больше одного), все вместе, как это:

--------------------------------------
|              TABLE_1               |
--------------------------------------
|                NAME                |
--------------------------------------
|           John Frusciante          |
--------------------------------------                   
|            Gilmour David           |
--------------------------------------
|            Sinatra Frank           |
--------------------------------------
|             David Bowie            |
--------------------------------------
|           Frusciante John          |
--------------------------------------
|     Wilhelm Friedrich Nietzsche    |
--------------------------------------

Проблема в том, что имена и фамилии не всегда в порядке.

Как мне сделать запрос типа

SELECT * FROM TABLE_1 WHERE NAME='JOHN FRUSCIANTE'

и найти результаты findind 2?

Ответы [ 4 ]

2 голосов
/ 22 марта 2020

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

Входные данные, такие как 'John Frusciante', задаются как переменная связывания :i_name. Имя может быть одним, двумя, тремя или любым другим числом «токенов» - и они могут присутствовать в любом порядке, включая бессмысленные порядки, такие как Hussein Obama Barack (где Обама - фамилия, а Барак Хуссейн - данные имена имя и отчество в американской терминологии). Для теста я использовал 'John Frusciante' для переменной bind.

Регулярные выражения удобны, но не быстры. Запрос может быть выполнен быстрее различными способами (с использованием стандартных строковых функций, но также в Oracle 12.1 или выше с использованием предложения lateral или cross apply, et c.) Одной из проблем будет listagg(), если ваша Oracle версия базы данных - 11.1 или ниже, поскольку эта функция была введена только в 11.2.

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

with
  table_1 (id, name) as (
    select 1, 'John Frusciante'             from dual union all
    select 2, 'Gilmour David'               from dual union all  
    select 3, 'Sinatra Frank'               from dual union all
    select 4, 'David Bowie'                 from dual union all
    select 5, 'Frusciante John'             from dual union all
    select 6, 'Wilhelm Friedrich Nietzsche' from dual
  )
, prep (id, name, ordered_name) as (
    select  id, name, 
            listagg(regexp_substr(name,'\S+', 1, level), ' ') 
              within group
                (order by regexp_substr(name,'\S+', 1, level))
    from    table_1
    connect by  level <= regexp_count(name, '\S+')
            and prior id = id
            and prior sys_guid() is not null
    group   by id, name
  )
select name
from   prep
where  lower(ordered_name) = 
         (select  lower(listagg(regexp_substr(:i_name,'\S+', 1, level), ' ') 
                  within group 
                    (order by regexp_substr(:i_name,'\S+', 1, level)))
          from    dual
          connect by level <= regexp_count(:i_name, '\S+')
         )
;

Выход (для ввода 'John Frusciante'):

NAME
---------------
John Frusciante
Frusciante John
0 голосов
/ 24 марта 2020

Что-то настолько простое, как это может работать для вас:

SELECT *
FROM TABLE_1
WHERE NAME LIKE '%JOHN%'
AND NAME LIKE '%FRUSCIANTE%'
0 голосов
/ 23 марта 2020

Вы можете использовать ниже для вашей цели

1) Это основано на заданной строке поиска, которая должна иметь имя и фамилию в любом порядке.

2) Кроме того, первый имя и фамилия не должны совпадать.

        WITH table_1 (id, name)
             AS (SELECT 1,
                        'John george Frusciante'
                 FROM   dual
                 UNION ALL
                 SELECT 2,
                        'Gilmour David'
                 FROM   dual
                 UNION ALL
                 SELECT 3,
                        'Sinatra Frank'
                 FROM   dual
                 UNION ALL
                 SELECT 4,
                        'JOHN Frusciante'
                 FROM   dual
                 UNION ALL
                 SELECT 5,
                        'Friedrich Nietzsche Wilhelm'
                 FROM   dual
                 UNION ALL
                 SELECT 6,
                        'Wilhelm Friedrich Nietzsche'
                 FROM   dual),
                 input1 as(select replace('Wilhelm Friedrich Nietzsche',' ','|') string1 from dual)
        SELECT a.*
        FROM   table_1 a,input1
        WHERE regexp_like(name, '^'||string1, 'i')
        AND regexp_like(name, string1||'$', 'i')
        AND upper(REGEXP_substr(name,'^(\S*)'))<>upper(REGEXP_substr(name,'(\S*)$'));
0 голосов
/ 22 марта 2020

Обновленное решение - попробуйте следующее:

with patterns as -- split each word in input search_string as a match-able pattern
(
    select regexp_substr(:search_string, '\w+', 1, level) pattern -- '\w+' matches one or more word characters
    from dual
    connect by regexp_substr(:search_string, '\w+', 1, level) is not null
) ,
table_1 as 
(
    select 'John Frusciante' as name from dual union all
    select 'John Frusciante John' as name from dual union all
    select 'Gilmour David' from dual union all 
    select 'Sinatra Frank' from dual union all 
    select 'David Bowie' from dual union all 
    select 'Frusciante John' from dual union all 
    select 'Wilhelm Friedrich Nietzsche' from dual union all  
    select 'John Smith'  from dual union all
    select 'Sarah Frusciante'  from dual
)
select name from (
select t.name,
       case sum( -- sum of all matches
                case instr(upper(t.name), upper(p.pattern)) -- match occurrence of each pattern in name
                    when 0 then 0   -- 0 when pattern is not found in name
                    else 1          -- 1 when each pattern is found in name
                end
                )
        when regexp_count(:search_string, '\w+', 1) -- count number of words in search string
        then 'matched'      -- match found for each word in search string
        else 'not matched'  -- not all words in search string is matched in the name
    end match_result
from table_1 t, patterns p
group by t.name
) where match_result = 'matched'
;

Для search_string = john frusciante результат будет следующим:

NAME
---------------
John Frusciante
John John Frusciante
Frusciante John



Мой первоначальный подход состоял в том, чтобы сопоставить несколько шаблонов в строке следующим образом:

select * from table_1 where regexp_like(name, '^(JOHN|FRUSCIANTE)', 'i');

Однако это также вернет «Джона Бекета» и «Сару Фрусчанте», как справедливо указано @mathguy

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...