SQL: анализировать имя, отчество и фамилию из поля полного имени - PullRequest
40 голосов
/ 02 октября 2008

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

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

Данные не включают префиксы или суффиксы. Отчество не обязательно. Данные отформатированы как «Первый Средний Последний».

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

Ответы [ 23 ]

1 голос
/ 01 ноября 2009

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

Копировать, если сайт отключается:

<?
/*
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's not my fault that there's 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;
    }


?>
0 голосов
/ 02 октября 2008

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

0 голосов
/ 02 октября 2008
  1. Получить функцию регулярного выражения sql. Образец: http://msdn.microsoft.com/en-us/magazine/cc163473.aspx
  2. Извлечение имен с использованием регулярных выражений.

Я рекомендую Expresso для изучения / построения / тестирования регулярных выражений. Старая бесплатная версия , Новая коммерческая версия

0 голосов
/ 02 октября 2008

Я не уверен насчет 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

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

0 голосов
/ 29 января 2011

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

0 голосов
/ 11 сентября 2009

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

В частности, довольно просто выйти за рамки простых разделителей пробелов, если у вас есть несколько списков общих префиксов (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";
0 голосов
/ 02 октября 2008

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

Рассмотрим эти примеры:

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

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

  • Гитарист "Эдди Ван Хален" (Первая Последняя Последняя)

  • И его мама, вероятно, называет его Эдвард Лодевийк Ван Хален (Первый Средняя Последняя Последняя)

  • Знаменитая каста "Мэри Энн Саммерс" (Первая Первая Последняя)

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

0 голосов
/ 08 июня 2017

Работа @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')
0 голосов
/ 04 апреля 2019

На основании вклада @ 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}
0 голосов
/ 19 января 2018

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

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

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

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