Подтвердить что ты не робот

SQL: проанализировать первое, среднее и фамильное имя из поля fullname

Как выполнить синтаксический анализ первого, среднего и последнего имени из поля fullname с помощью SQL?

Мне нужно попытаться совпадение имен, которые не соответствуют прямому имени. Я хотел бы иметь возможность заполнить полное имя и разбить его на первую, среднюю и фамилию.

Данные не содержат никаких префиксов или суффиксов. Второе имя является необязательным. Данные форматируются "First Middle Last".

Я интересуюсь некоторыми практическими решениями, чтобы получить от меня 90% пути. Как было сказано, это сложная проблема, поэтому я буду обрабатывать отдельные случаи индивидуально.

4b9b3361

Ответ 1

Вот самодостаточный пример, с легко обрабатываемыми тестовыми данными.

В этом примере, если у вас есть имя с более чем тремя частями, тогда все "лишние" вещи будут помещены в поле LAST_NAME. Исключение делается для определенных строк, которые обозначаются как "заголовки", такие как "DR", "MRS" и "MR".

Если среднее имя отсутствует, вы получите только FIRST_NAME и LAST_NAME (MIDDLE_NAME будет NULL).

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

Изменить - обрабатывать следующие специальные случаи:

1 - Поле NAME равно NULL

2 - Поле NAME содержит ведущие/конечные пробелы

3 - Поле NAME имеет > 1 последовательное пространство внутри имени

4 - Поле NAME содержит ТОЛЬКО первое имя

5 - включить исходное полное имя в конечный вывод в виде отдельного столбца, для удобства чтения

6 - Обращайтесь к определенному списку префиксов в виде отдельного столбца "title"

SELECT
  FIRST_NAME.ORIGINAL_INPUT_DATA
 ,FIRST_NAME.TITLE
 ,FIRST_NAME.FIRST_NAME
 ,CASE WHEN 0 = CHARINDEX(' ',FIRST_NAME.REST_OF_NAME)
       THEN NULL  --no more spaces?  assume rest is the last name
       ELSE SUBSTRING(
                       FIRST_NAME.REST_OF_NAME
                      ,1
                      ,CHARINDEX(' ',FIRST_NAME.REST_OF_NAME)-1
                     )
       END AS MIDDLE_NAME
 ,SUBSTRING(
             FIRST_NAME.REST_OF_NAME
            ,1 + CHARINDEX(' ',FIRST_NAME.REST_OF_NAME)
            ,LEN(FIRST_NAME.REST_OF_NAME)
           ) AS LAST_NAME
FROM
  (  
  SELECT
    TITLE.TITLE
   ,CASE WHEN 0 = CHARINDEX(' ',TITLE.REST_OF_NAME)
         THEN TITLE.REST_OF_NAME --No space? return the whole thing
         ELSE SUBSTRING(
                         TITLE.REST_OF_NAME
                        ,1
                        ,CHARINDEX(' ',TITLE.REST_OF_NAME)-1
                       )
    END AS FIRST_NAME
   ,CASE WHEN 0 = CHARINDEX(' ',TITLE.REST_OF_NAME)  
         THEN NULL  --no spaces @ all?  then 1st name is all we have
         ELSE SUBSTRING(
                         TITLE.REST_OF_NAME
                        ,CHARINDEX(' ',TITLE.REST_OF_NAME)+1
                        ,LEN(TITLE.REST_OF_NAME)
                       )
    END AS REST_OF_NAME
   ,TITLE.ORIGINAL_INPUT_DATA
  FROM
    (   
    SELECT
      --if the first three characters are in this list,
      --then pull it as a "title".  otherwise return NULL for title.
      CASE WHEN SUBSTRING(TEST_DATA.FULL_NAME,1,3) IN ('MR ','MS ','DR ','MRS')
           THEN LTRIM(RTRIM(SUBSTRING(TEST_DATA.FULL_NAME,1,3)))
           ELSE NULL
           END AS TITLE
      --if you change the list, don't forget to change it here, too.
      --so much for the DRY prinicple...
     ,CASE WHEN SUBSTRING(TEST_DATA.FULL_NAME,1,3) IN ('MR ','MS ','DR ','MRS')
           THEN LTRIM(RTRIM(SUBSTRING(TEST_DATA.FULL_NAME,4,LEN(TEST_DATA.FULL_NAME))))
           ELSE LTRIM(RTRIM(TEST_DATA.FULL_NAME))
           END AS REST_OF_NAME
     ,TEST_DATA.ORIGINAL_INPUT_DATA
    FROM
      (
      SELECT
        --trim leading & trailing spaces before trying to process
        --disallow extra spaces *within* the name
        REPLACE(REPLACE(LTRIM(RTRIM(FULL_NAME)),'  ',' '),'  ',' ') AS FULL_NAME
       ,FULL_NAME AS ORIGINAL_INPUT_DATA
      FROM
        (
        --if you use this, then replace the following
        --block with your actual table
              SELECT 'GEORGE W BUSH' AS FULL_NAME
        UNION SELECT 'SUSAN B ANTHONY' AS FULL_NAME
        UNION SELECT 'ALEXANDER HAMILTON' AS FULL_NAME
        UNION SELECT 'OSAMA BIN LADEN JR' AS FULL_NAME
        UNION SELECT 'MARTIN J VAN BUREN SENIOR III' AS FULL_NAME
        UNION SELECT 'TOMMY' AS FULL_NAME
        UNION SELECT 'BILLY' AS FULL_NAME
        UNION SELECT NULL AS FULL_NAME
        UNION SELECT ' ' AS FULL_NAME
        UNION SELECT '    JOHN  JACOB     SMITH' AS FULL_NAME
        UNION SELECT ' DR  SANJAY       GUPTA' AS FULL_NAME
        UNION SELECT 'DR JOHN S HOPKINS' AS FULL_NAME
        UNION SELECT ' MRS  SUSAN ADAMS' AS FULL_NAME
        UNION SELECT ' MS AUGUSTA  ADA   KING ' AS FULL_NAME      
        ) RAW_DATA
      ) TEST_DATA
    ) TITLE
  ) FIRST_NAME

Ответ 2

Трудно ответить, не зная, как отформатировано "полное имя".

Это может быть "Фамилия, Имя, Отчество" или "Имя, Фамилия" и т.д.

В основном вам придется использовать функцию SUBSTRING

SUBSTRING ( expression , start , length )

И, вероятно, функция CHARINDEX

CHARINDEX (substr, expression)

Чтобы определить начало и длину для каждой части, которую вы хотите извлечь.

Итак, допустим, что формат "Имя Фамилия" вы могли бы (не проверено.. но должно быть близко):

SELECT 
SUBSTRING(fullname, 1, CHARINDEX(' ', fullname) - 1) AS FirstName, 
SUBSTRING(fullname, CHARINDEX(' ', fullname) + 1, len(fullname)) AS LastName
FROM YourTable

Ответ 3

Отмените проблему, добавьте столбцы, чтобы удерживать отдельные части и объединить их для получения полного имени.

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

Например, как бы вы разделили это?

Jan Olav Olsen Heggelien

Это, будучи фиктивным, является юридическим именем в Норвегии и может, но не обязательно, разбиваться так:

First name: Jan Olav
Middle name: Olsen
Last name: Heggelien

или, например:

First name: Jan Olav
Last name: Olsen Heggelien

или, например:

First name: Jan
Middle name: Olav
Last name: Olsen Heggelien

Я бы предположил, что подобные события встречаются на большинстве языков.

Поэтому вместо того, чтобы пытаться интерпретировать данные, которые не имеют достаточной информации для правильной работы, сохраните правильную интерпретацию и объединитесь, чтобы получить полное имя.

Ответ 4

Если у вас есть очень, очень хорошие данные, это нетривиальная задача. Наивный подход заключался бы в том, чтобы токенизировать по пробелам и предположить, что результат с тремя маркерами - это [первый, средний, последний], а результат с двумя токенами - [первый, последний], но вам придется иметь дело с мульти- (например, "Van Buren" ) и несколько средних имен.

Ответ 5

Альтернативным простым способом является использование parsename:

select full_name,
   parsename(replace(full_name, ' ', '.'), 3) as FirstName,
   parsename(replace(full_name, ' ', '.'), 2) as MiddleName,
   parsename(replace(full_name, ' ', '.'), 1) as LastName 
from YourTableName

источник

Ответ 6

Для бесплатного решения на базе SQL CLR обязательно проверьте SqlName из Ambient Concepts, который может быть большой помощью при анализе имен на уровне базы данных.

http://ambientconcepts.com/sqlname

Ответ 7

Вы уверены, что полное юридическое имя всегда будет включать первый, средний и последний? Я знаю людей, которые имеют только одно имя как полное юридическое имя, и, честно говоря, я не уверен, что это их первое или последнее имя.:-) Я также знаю людей, которые имеют более одного имени Fisrt в своем юридическом названии, но не имеют среднего имени. И есть некоторые люди, которые имеют несколько средних имен.

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

В более практичной заметке вы можете разделить полное имя на пробел и угрожать первому токену как Имя и последний токен (или единственный токен в случае только одного имени) как Фамилия. Хотя это предполагает, что порядок будет всегда одним и тем же.

Ответ 8

Это будет работать в строке case. Имя FirstName/MiddleName/LastName

Select 

DISTINCT NAMES ,

   SUBSTRING(NAMES , 1, CHARINDEX(' ', NAMES) - 1) as FirstName,

   RTRIM(LTRIM(REPLACE(REPLACE(NAMES,SUBSTRING(NAMES , 1, CHARINDEX(' ', NAMES) - 1),''),REVERSE( LEFT( REVERSE(NAMES), CHARINDEX(' ', REVERSE(NAMES))-1 ) ),'')))as MiddleName,

   REVERSE( LEFT( REVERSE(NAMES), CHARINDEX(' ', REVERSE(NAMES))-1 ) ) as LastName

From TABLENAME

Ответ 9

Как и # 1, это не тривиально. Игнорируемые фамилии, инициалы, двойные имена, обратная последовательность имен и множество других аномалий могут испортить вашу тщательно обработанную функцию.

Вы можете использовать стороннюю библиотеку (plug/disclaimer - я работал над этим продуктом):

http://www.melissadata.com/nameobject/nameobject.htm

Ответ 10

Я бы сделал это как итеративный процесс.

1) Дамп таблицы в плоский файл для работы.

2) Напишите простую программу для разбиения своих имен с помощью пробела как разделителя, где первый токен - это первое имя, если есть 3 токена, то токен 2 - это среднее имя, а токен 3 - фамилия. Если есть 2 жетона, то второй токен - это фамилия. (Perl, Java или C/С++, язык не имеет значения)

3) Взгляните на результаты. Ищите имена, которые не соответствуют этому правилу.

4) Используя этот пример, создайте новое правило для обработки этого исключения...

5) Промыть и повторить

В конце концов вы получите программу, которая исправляет все ваши данные.

Ответ 11

Если вы пытаетесь разобрать человеческое имя на PHP, я рекомендую Keith Beckman nameparse.php script.

Скопировать в случае, если сайт не работает:

<?
/*
Name:   nameparse.php
Version: 0.2a
Date:   030507
First:  030407
License:    GNU General Public License v2
Bugs:   If one of the words in the middle name is Ben (or St., for that matter),
        or any other possible last-name prefix, the name MUST be entered in
        last-name-first format. If the last-name parsing routines get ahold
        of any prefix, they tie up the rest of the name up to the suffix. i.e.:

        William Ben Carey   would yield 'Ben Carey' as the last name, while,
        Carey, William Ben  would yield 'Carey' as last and 'Ben' as middle.

        This is a problem inherent in the prefix-parsing routines algorithm,
        and probably will not be fixed. It not my fault that there some
        odd overlap between various languages. Just don't name your kids
        'Something Ben Something', and you should be alright.

*/

function    norm_str($string) {
    return  trim(strtolower(
        str_replace('.','',$string)));
    }

function    in_array_norm($needle,$haystack) {
    return  in_array(norm_str($needle),$haystack);
    }

function    parse_name($fullname) {
    $titles         =   array('dr','miss','mr','mrs','ms','judge');
    $prefices       =   array('ben','bin','da','dal','de','del','der','de','e',
                            'la','le','san','st','ste','van','vel','von');
    $suffices       =   array('esq','esquire','jr','sr','2','ii','iii','iv');

    $pieces         =   explode(',',preg_replace('/\s+/',' ',trim($fullname)));
    $n_pieces       =   count($pieces);

    switch($n_pieces) {
        case    1:  // array(title first middles last suffix)
            $subp   =   explode(' ',trim($pieces[0]));
            $n_subp =   count($subp);
            for($i = 0; $i < $n_subp; $i++) {
                $curr               =   trim($subp[$i]);
                $next               =   trim($subp[$i+1]);

                if($i == 0 && in_array_norm($curr,$titles)) {
                    $out['title']   =   $curr;
                    continue;
                    }

                if(!$out['first']) {
                    $out['first']   =   $curr;
                    continue;
                    }

                if($i == $n_subp-2 && $next && in_array_norm($next,$suffices)) {
                    if($out['last']) {
                        $out['last']    .=  " $curr";
                        }
                    else {
                        $out['last']    =   $curr;
                        }
                    $out['suffix']      =   $next;
                    break;
                    }

                if($i == $n_subp-1) {
                    if($out['last']) {
                        $out['last']    .=  " $curr";
                        }
                    else {
                        $out['last']    =   $curr;
                        }
                    continue;
                    }

                if(in_array_norm($curr,$prefices)) {
                    if($out['last']) {
                        $out['last']    .=  " $curr";
                        }
                    else {
                        $out['last']    =   $curr;
                        }
                    continue;
                    }

                if($next == 'y' || $next == 'Y') {
                    if($out['last']) {
                        $out['last']    .=  " $curr";
                        }
                    else {
                        $out['last']    =   $curr;
                        }
                    continue;
                    }

                if($out['last']) {
                    $out['last']    .=  " $curr";
                    continue;
                    }

                if($out['middle']) {
                    $out['middle']      .=  " $curr";
                    }
                else {
                    $out['middle']      =   $curr;
                    }
                }
            break;
        case    2:
                switch(in_array_norm($pieces[1],$suffices)) {
                    case    TRUE: // array(title first middles last,suffix)
                        $subp   =   explode(' ',trim($pieces[0]));
                        $n_subp =   count($subp);
                        for($i = 0; $i < $n_subp; $i++) {
                            $curr               =   trim($subp[$i]);
                            $next               =   trim($subp[$i+1]);

                            if($i == 0 && in_array_norm($curr,$titles)) {
                                $out['title']   =   $curr;
                                continue;
                                }

                            if(!$out['first']) {
                                $out['first']   =   $curr;
                                continue;
                                }

                            if($i == $n_subp-1) {
                                if($out['last']) {
                                    $out['last']    .=  " $curr";
                                    }
                                else {
                                    $out['last']    =   $curr;
                                    }
                                continue;
                                }

                            if(in_array_norm($curr,$prefices)) {
                                if($out['last']) {
                                    $out['last']    .=  " $curr";
                                    }
                                else {
                                    $out['last']    =   $curr;
                                    }
                                continue;
                                }

                            if($next == 'y' || $next == 'Y') {
                                if($out['last']) {
                                    $out['last']    .=  " $curr";
                                    }
                                else {
                                    $out['last']    =   $curr;
                                    }
                                continue;
                                }

                            if($out['last']) {
                                $out['last']    .=  " $curr";
                                continue;
                                }

                            if($out['middle']) {
                                $out['middle']      .=  " $curr";
                                }
                            else {
                                $out['middle']      =   $curr;
                                }
                            }                       
                        $out['suffix']  =   trim($pieces[1]);
                        break;
                    case    FALSE: // array(last,title first middles suffix)
                        $subp   =   explode(' ',trim($pieces[1]));
                        $n_subp =   count($subp);
                        for($i = 0; $i < $n_subp; $i++) {
                            $curr               =   trim($subp[$i]);
                            $next               =   trim($subp[$i+1]);

                            if($i == 0 && in_array_norm($curr,$titles)) {
                                $out['title']   =   $curr;
                                continue;
                                }

                            if(!$out['first']) {
                                $out['first']   =   $curr;
                                continue;
                                }

                        if($i == $n_subp-2 && $next &&
                            in_array_norm($next,$suffices)) {
                            if($out['middle']) {
                                $out['middle']  .=  " $curr";
                                }
                            else {
                                $out['middle']  =   $curr;
                                }
                            $out['suffix']      =   $next;
                            break;
                            }

                        if($i == $n_subp-1 && in_array_norm($curr,$suffices)) {
                            $out['suffix']      =   $curr;
                            continue;
                            }

                        if($out['middle']) {
                            $out['middle']      .=  " $curr";
                            }
                        else {
                            $out['middle']      =   $curr;
                            }
                        }
                        $out['last']    =   $pieces[0];
                        break;
                    }
            unset($pieces);
            break;
        case    3:  // array(last,title first middles,suffix)
            $subp   =   explode(' ',trim($pieces[1]));
            $n_subp =   count($subp);
            for($i = 0; $i < $n_subp; $i++) {
                $curr               =   trim($subp[$i]);
                $next               =   trim($subp[$i+1]);
                if($i == 0 && in_array_norm($curr,$titles)) {
                    $out['title']   =   $curr;
                    continue;
                    }

                if(!$out['first']) {
                    $out['first']   =   $curr;
                    continue;
                    }

                if($out['middle']) {
                    $out['middle']      .=  " $curr";
                    }
                else {
                    $out['middle']      =   $curr;
                    }
                }

            $out['last']                =   trim($pieces[0]);
            $out['suffix']              =   trim($pieces[2]);
            break;
        default:    // unparseable
            unset($pieces);
            break;
        }

    return $out;
    }


?>

Ответ 12

Этот запрос работает нормально.

SELECT name
    ,Ltrim(SubString(name, 1, Isnull(Nullif(CHARINDEX(' ', name), 0), 1000))) AS FirstName
    ,Ltrim(SUBSTRING(name, CharIndex(' ', name), CASE 
                WHEN (CHARINDEX(' ', name, CHARINDEX(' ', name) + 1) - CHARINDEX(' ', name)) <= 0
                    THEN 0
                ELSE CHARINDEX(' ', name, CHARINDEX(' ', name) + 1) - CHARINDEX(' ', name)
                END)) AS MiddleName
    ,Ltrim(SUBSTRING(name, Isnull(Nullif(CHARINDEX(' ', name, Charindex(' ', name) + 1), 0), CHARINDEX(' ', name)), CASE 
                WHEN Charindex(' ', name) = 0
                    THEN 0
                ELSE LEN(name)
                END)) AS LastName
FROM yourtableName

Ответ 14

Я не уверен в SQL-сервере, но в postgres вы можете сделать что-то вроде этого:

SELECT 
  SUBSTRING(fullname, '(\\w+)') as firstname,
  SUBSTRING(fullname, '\\w+\\s(\\w+)\\s\\w+') as middle,
  COALESCE(SUBSTRING(fullname, '\\w+\\s\\w+\\s(\\w+)'), SUBSTRING(fullname, '\\w+\\s(\\w+)')) as lastname
FROM 
public.person

Выражения регулярных выражений могут быть немного более краткими; но вы понимаете. Это, кстати, не работает для людей, имеющих два двойных имени (в Нидерландах у нас это много "Ян ван дер Плойг" ), поэтому я был бы очень осторожен с результатами.

Ответ 15

Я однажды сделал регулярное выражение на 500 символов для анализа первых, последних и средних имен из произвольной строки. Даже с этим регулярным выражением, он только получил около 97% точности из-за полной несогласованности ввода. Тем не менее, лучше, чем ничего.

Ответ 16

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

** Предупреждение: беспорядочный SQL следует:

create table parsname (fullname char(50), name1 char(30), name2 char(30), name3 char(30), name4 char(40));
insert into parsname (fullname) select fullname from ImportTable;
update parsname set name1 = substring(fullname, 1, locate(' ', fullname)),
 fullname = ltrim(substring(fullname, locate(' ', fullname), length(fullname)))
 where locate(' ', rtrim(fullname)) > 0;
update parsname set name2 = substring(fullname, 1, locate(' ', fullname)),
 fullname = ltrim(substring(fullname, locate(' ', fullname), length(fullname)))
 where locate(' ', rtrim(fullname)) > 0;
update parsname set name3 = substring(fullname, 1, locate(' ', fullname)),
 fullname = ltrim(substring(fullname, locate(' ', fullname), length(fullname)))
 where locate(' ', rtrim(fullname)) > 0;
update parsname set name4 = substring(fullname, 1, locate(' ', fullname)),
 fullname = ltrim(substring(fullname, locate(' ', fullname), length(fullname)))
 where locate(' ', rtrim(fullname)) > 0;
// fullname now contains the last word in the string.
select fullname as FirstName, '' as MiddleName, '' as LastName from parsname where fullname is not null and name1 is null and name2 is null
union all
select name1 as FirstName, name2 as MiddleName, fullname as LastName from parsname where name1 is not null and name3 is null

Код работает, создавая временную таблицу (parsname) и токенизируя полное имя пробелами. Любые имена, заканчивающиеся значениями в name3 или name4, являются несоответствующими и должны обрабатываться по-разному.

Ответ 17

Здесь хранится процедура, которая будет помещать первое слово в Имя, последнее слово в Фамилию и все между ними в среднее имя.

create procedure [dbo].[import_ParseName]
(            
    @FullName nvarchar(max),
    @FirstName nvarchar(255) output,
    @MiddleName nvarchar(255) output,
    @LastName nvarchar(255)  output
)
as
begin

set @FirstName = ''
set @MiddleName = ''
set @LastName = ''  
set @FullName = ltrim(rtrim(@FullName))

declare @ReverseFullName nvarchar(max)
set @ReverseFullName = reverse(@FullName)

declare @lengthOfFullName int
declare @endOfFirstName int
declare @beginningOfLastName int

set @lengthOfFullName = len(@FullName)
set @endOfFirstName = charindex(' ', @FullName)
set @beginningOfLastName = @lengthOfFullName - charindex(' ', @ReverseFullName) + 1

set @FirstName = case when @endOfFirstName <> 0 
                      then substring(@FullName, 1, @endOfFirstName - 1) 
                      else ''
                 end

set @MiddleName = case when (@endOfFirstName <> 0 and @beginningOfLastName <> 0 and @beginningOfLastName > @endOfFirstName)
                       then ltrim(rtrim(substring(@FullName, @endOfFirstName , @beginningOfLastName - @endOfFirstName))) 
                       else ''
                  end

set @LastName = case when @beginningOfLastName <> 0 
                     then substring(@FullName, @beginningOfLastName + 1 , @lengthOfFullName - @beginningOfLastName)
                     else ''
                end

return

end 

И здесь я называю это.

DECLARE @FirstName nvarchar(255),
        @MiddleName nvarchar(255),
        @LastName nvarchar(255)

EXEC    [dbo].[import_ParseName]
        @FullName = N'Scott The Other Scott Kowalczyk',
        @FirstName = @FirstName OUTPUT,
        @MiddleName = @MiddleName OUTPUT,
        @LastName = @LastName OUTPUT

print   @FirstName 
print   @MiddleName
print   @LastName 

output:

Scott
The Other Scott
Kowalczyk

Ответ 18

Как все говорят, вы не можете простыми программными способами.

Рассмотрим следующие примеры:

  • Президент "Джордж Герберт Уокер Буш" (первый средний средний последний)

  • Президентский убийца "Джон Уилкс Бут" (первый средний Последняя)

  • Гитарист "Эдди Ван Хален" (первый последний)

  • И его мама, вероятно, называет его Эдвардом Лодевейком Ван Халеном (первый Middle Last Last)

  • Famed castaway "Мэри Энн Саммерс" (Первый Первый Последний)

  • Председатель Республиканской партии в Нью-Мексико "Фернандо С де Бака" (первый последний последний)

Ответ 19

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

В частности, довольно легко выйти за рамки простых разделителей пробелов, если у вас есть только некоторые списки общих префиксов (Mr, Dr, Mrs и т.д.), инфикс (von, de, del и т.д.), суффиксы ( Jr, III, Sr и т.д.) И т.д. Это также полезно, если у вас есть список общих имен (на разных языках/культурах, если ваши имена разнообразны), чтобы вы могли догадаться, может ли слово посередине быть частью фамилии или нет.

BibTeX также реализует некоторые эвристики, которые позволяют вам участвовать в этом пути; они заключены в модуль Text::BibTeX::Name perl. Вот пример быстрого кода, который делает разумную работу.

use Text::BibTeX;
use Text::BibTeX::Name;
$name = "Dr. Mario Luis de Luigi Jr.";
$name =~ s/^\s*([dm]rs?.?|miss)\s+//i;
$dr=$1;
$n=Text::BibTeX::Name->new($name);
print join("\t", $dr, map "@{[ $n->part($_) ]}", qw(first von last jr)), "\n";

Ответ 20

Самая большая проблема, с которой я столкнулась, заключалась в таких случаях, как "Боб Р. Смит-младший". Используемый алгоритм размещен на http://www.blackbeltcoder.com/Articles/strings/splitting-a-name-into-first-and-last-names. Мой код находится в С#, но вы можете его перенести, если вы должны иметь в SQL.

Ответ 21

Работа @JosephStyons и @Digs велик! Я использовал часть своей работы для создания новой функции для SQL Server 2016 и более новой. Это также обрабатывает суффиксы, а также префиксы.

CREATE FUNCTION [dbo].[NameParser]
(
    @name nvarchar(100)
)
RETURNS TABLE
AS
RETURN (

WITH prep AS (
    SELECT 
        original = @name,
        cleanName = REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(@name)),'  ',' '),'  ',' '), '.', ''), ',', '')
)
SELECT
    prep.original,
    aux.prefix,
    firstName.firstName,
    middleName.middleName,
    lastName.lastName,
    aux.suffix
FROM
    prep
    CROSS APPLY (
        SELECT 
            prefix =
                CASE 
                    WHEN LEFT(prep.cleanName, 3) IN ('MR ', 'MS ', 'DR ', 'FR ')
                        THEN LEFT(prep.cleanName, 2)
                    WHEN LEFT(prep.cleanName, 4) IN ('MRS ', 'LRD ', 'SIR ')
                        THEN LEFT(prep.cleanName, 3)
                    WHEN LEFT(prep.cleanName, 5) IN ('LORD ', 'LADY ', 'MISS ', 'PROF ')
                        THEN LEFT(prep.cleanName, 4)
                    ELSE ''
                END,
            suffix =
                CASE 
                    WHEN RIGHT(prep.cleanName, 3) IN (' JR', ' SR', ' II', ' IV')
                        THEN RIGHT(prep.cleanName, 2)
                    WHEN RIGHT(prep.cleanName, 4) IN (' III', ' ESQ')
                        THEN RIGHT(prep.cleanName, 3)
                    ELSE ''
                END
    ) aux
    CROSS APPLY (
        SELECT
            baseName = LTRIM(RTRIM(SUBSTRING(prep.cleanName, LEN(aux.prefix) + 1, LEN(prep.cleanName) - LEN(aux.prefix) - LEN(aux.suffix)))),
            numParts = (SELECT COUNT(1) FROM STRING_SPLIT(LTRIM(RTRIM(SUBSTRING(prep.cleanName, LEN(aux.prefix) + 1, LEN(prep.cleanName) - LEN(aux.prefix) - LEN(aux.suffix)))), ' '))
    ) core
    CROSS APPLY (
        SELECT
            firstName = 
                CASE
                    WHEN core.numParts <= 1 THEN core.baseName
                    ELSE LEFT(core.baseName, CHARINDEX(' ', core.baseName, 1) - 1) 
                END

    ) firstName
    CROSS APPLY (
        SELECT
            remainder = 
                CASE
                    WHEN core.numParts <= 1 THEN ''
                    ELSE LTRIM(SUBSTRING(core.baseName, LEN(firstName.firstName) + 1, 999999))
                END
    ) work1
    CROSS APPLY (
        SELECT
            middleName = 
                CASE
                    WHEN core.numParts <= 2 THEN ''
                    ELSE LEFT(work1.remainder, CHARINDEX(' ', work1.remainder, 1) - 1) 
                END
    ) middleName
    CROSS APPLY (
        SELECT
            lastName = 
                CASE
                    WHEN core.numParts <= 1 THEN ''
                    ELSE LTRIM(SUBSTRING(work1.remainder, LEN(middleName.middleName) + 1, 999999))
                END
    ) lastName
)

GO

SELECT * FROM dbo.NameParser('Madonna')
SELECT * FROM dbo.NameParser('Will Smith')
SELECT * FROM dbo.NameParser('Neil Degrasse Tyson')
SELECT * FROM dbo.NameParser('Dr. Neil Degrasse Tyson')
SELECT * FROM dbo.NameParser('Mr. Hyde')
SELECT * FROM dbo.NameParser('Mrs. Thurston Howell, III')

Ответ 22

Проверьте этот запрос в Афине на наличие только строки, разделенной одним пробелом (например, комбинация имени и отчества):

SELECT name, REVERSE( SUBSTR( REVERSE(name), 1, STRPOS(REVERSE(name), ' ') ) ) AS middle_name FROM name_table

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

Ответ 23

Основываясь на вкладе @hajili (который представляет собой творческое использование функции parsename, предназначенной для анализа имени объекта, разделенного точкой), я изменил его, чтобы он мог обрабатывать случаи, когда данные не содержат отчество или когда зовут "Джон и Джейн Доу". Он не идеален на 100%, но компактен и может добиться цели в зависимости от бизнес-ситуации.

SELECT NAME,
CASE WHEN parsename(replace(NAME, ' ', '.'), 4) IS NOT NULL THEN 
   parsename(replace(NAME, ' ', '.'), 4) ELSE
    CASE WHEN parsename(replace(NAME, ' ', '.'), 3) IS NOT NULL THEN 
    parsename(replace(NAME, ' ', '.'), 3) ELSE
   parsename(replace(NAME, ' ', '.'), 2) end END as FirstName
   ,
CASE WHEN parsename(replace(NAME, ' ', '.'), 3) IS NOT NULL THEN 
   parsename(replace(NAME, ' ', '.'), 2) ELSE NULL END as MiddleName,
   parsename(replace(NAME, ' ', '.'), 1) as LastName
from  {@YourTableName}

Ответ 24

В таблице сотрудников есть столбец "Имя", и нам пришлось разделить его на Имя, Отчество и Фамилия. Этот запрос обрабатывает сохранение среднего имени как нулевого, если столбец имени имеет значение двух слов, таких как "Джеймс Томас".

UPDATE Employees
SET [First Name] = CASE 
        WHEN (len(name) - len(Replace(name, '.', ''))) = 2
            THEN PARSENAME(Name, 3)
        WHEN (len(name) - len(Replace(name, '.', ''))) = 1
            THEN PARSENAME(Name, 2)
        ELSE PARSENAME(Name, 1)
        END
    ,[Middle Name] = CASE 
        WHEN (len(name) - len(Replace(name, '.', ''))) = 2
            THEN PARSENAME(Name, 2)
        ELSE NULL
        END
    ,[Last Name] = CASE 
        WHEN (len(name) - len(Replace(name, '.', ''))) = 2
            THEN PARSENAME(Name, 1)
        WHEN (len(name) - len(Replace(name, '.', ''))) = 1
            THEN PARSENAME(Name, 1)
        ELSE NULL
        END GO

UPDATE Employee
SET [Name] = Replace([Name], '.', ' ') GO