2008-10-01 86 views
39

如何使用SQL從全名字段中解析第一個,中間和最後一個名稱?SQL:從全名字段中解析第一個,中間和最後一個名稱

我需要嘗試匹配不是全名直接匹配的名稱。我希望能夠把全名字段分解成第一個,中間和最後一個名字。

數據不包含任何前綴或後綴。中間名是可選的。數據格式爲「第一中間」。

我對一些實際的解決方案感興趣,讓我有90%的方式。如前所述,這是一個複雜的問題,所以我會分別處理特殊情況。

回答

122

這是一個自包含的示例,可輕鬆操作測試數據。

在此示例中,如果您的名稱超過三個部分,則所有「額外」內容都將放入LAST_NAME字段中。對於標識爲「標題」的特定字符串(例如「DR」,「MRS」和「MR」)有例外。

如果中間名缺失,那麼您只需獲取FIRST_NAME和LAST_NAME(MIDDLE_NAME將爲NULL)。

你可以將它砸成一個巨大的嵌套blob的SUBSTRINGs,但可讀性已經夠難,因爲它是在SQL中這樣做的時候。

Edit--手柄以下特例:

1 - NAME字段是NULL

2 - 名稱字段包含前/後間隔

3 - 名稱字段在名稱內具有> 1個連續空格

4 - 名稱字段僅包含所述第一名稱

5 - 包括在作爲一個單獨的列中的最終輸出的原始的全名,以提高可讀性

6 - 處理的特定列表前綴作爲一個單獨的「標題」列

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 
5

除非你有非常非常好的數據,否則這是一個不小的挑戰。一個天真的方法是在空白處標記化,並假設三個標記的結果是[first,middle,last]和一個雙標記的結果是[first,last],但是你將不得不處理多個標記,單詞姓氏(例如「Van Buren」)和多箇中間名。

+0

我最終變得天真,最終都成功了。謝謝。 – 2008-12-30 16:16:14

+0

這不是一個答案。每個人都知道這個問題很難,但如果你能容忍一些錯誤,那麼90%的方法並不是不可能的。 – rjmunro 2011-11-29 11:36:41

2

您確定完整的法定名稱將始終包含第一,中間和最後?我知道只有一個名稱爲完整法定名稱的人,說實話,我不確定這是他們的姓或名。 :-)我也知道那些在法定名稱中有多個Fisrt名字的人,但沒有中間名。還有一些人擁有多箇中間名。

然後還有完整法定名稱中的名稱順序。據我所知,在一些亞洲文化中,「姓氏」以「完整的法律名稱」排在第一位。

更實際的一點是,您可以將全名分割爲空白,並將第一個標記作爲名字和最後一個標記(或者只存在一個名稱的唯一標記)威脅爲姓氏。雖然這假定訂單總是一樣的。

+0

也有人只有一個名字。不僅像麥當娜,雪兒和波諾這樣的名人,而且它在冰島的傳統僅僅是以名字命名。 – 2008-10-01 20:47:56

+0

這似乎是我需要使用的實用方法。中間名可以是任何沒有包含在名字或姓氏中的名字。 – 2008-10-01 20:50:50

+0

@ Bill Karwin - 是的,我提到如果你只有一個名字的人,不清楚它是他們的名字還是姓氏。 – 2008-10-01 22:01:09

6

如果不知道「全名」是如何格式化的,很難回答。

這可能是「姓,名中間名」或「名字中間名姓」等

基本上你將不得不使用SUBSTRING功能

SUBSTRING (expression , start , length) 

也可能是CHARINDEX功能

CHARINDEX (substr, expression) 

找出一個開始nd每部分你想要提取的長度。

所以我們說格式是「名字姓氏」,你可以(未經測試..但應接近):

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

我發佈後更新了數據格式。 – 2008-10-01 21:26:32

+1

請更新示例以使用SUBSTRING而不是SUBSTR。 – Shiroy 2017-05-02 20:11:43

6

反轉的問題,添加列舉行單獨的塊並且將它們結合起來,以獲得全名。

這將是最好的答案的原因是,有沒有保證的方法來找出一個人已經註冊爲他們的名字,他們的中間名是什麼。

例如,你會如何分裂這?

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 

我會想象在大多數語言中可以找到類似的事件。

因此,不是試圖解釋沒有足夠信息來正確解釋數據,而是存儲正確的解釋,併合並得到全名。

+0

不幸的是,這是數據轉換。就是這樣。 – 2008-10-01 20:57:51

0

我不知道有關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 

正則表達式的表達或許可以更簡潔一點;但你明白了。這對於有兩個雙重名字的人來說並不適用(在荷蘭我們有很多'Jan van der Ploeg'),所以我會非常小心的結果。

1

就像#1說的那樣,這不是小事。姓名,首字母縮寫,雙重名字,反名字序列和各種其他異常都可能毀掉您精心設計的功能。

你可以使用一個第三方庫(插頭/免責聲明 - 我從事這個產品):

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

1

我會做這是一個反覆的過程。

1)將錶轉儲到一個平面文件來處理。

2)編寫一個簡單的程序來打破你的名字,使用空格作爲分隔符,其中firsts token是第一個名字,如果有3個token,則token 2是中間名,token 3是last name。如果有2個令牌,則第二個令牌是最後一個名稱。 (Perl,Java或C/C++,語言無所謂)

3)眼球結果。尋找不符合此規則的名稱。

4)用這個例子中,創建一個新的規則來處理該異常......

5)沖洗和重複

最終你會得到解決您所有的數據的程序。

0

我曾經做了一個500字正則表達式來分析第一,最後和中間名從任意的字符串。即使使用這種聲音的正則表達式,由於輸入的完全不一致性,它的準確率也只有97%左右。儘管如此,總比沒有好。

0

根據已經提出的有關名稱和其他異常空間的警告,以下代碼至少可以處理98%的名稱。 (注:凌亂的SQL,因爲我沒有在我使用數據庫正則表達式選項。)

**警告:凌亂的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中的值結尾的名稱都是不合格的,需要以不同的方式處理。

0

這裏有一個存儲過程,它會將第一個字找到First Name中,最後一個字放入Last Name中,並將其中的所有內容放入Middle Name中。

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 
0

正如其他人所說,你不能從簡單的編程方式。

考慮這些例子:

  • 總統 「喬治·赫伯特·沃克·布什」(第一中學中間名姓)

  • 總統刺客 「約翰·威爾克斯·布斯」(第一中學 姓)

  • 吉他手「Eddie Van Halen」(最後一張)

  • 而他媽媽大概叫他愛德華Lodewijk範海倫(第一 中最後最後)

  • 著名棄兒「瑪麗·安的夏天」(第一名姓)

  • New Mexico GOP chairman「費爾南多Ç德巴卡」(第一個最後最後最後)

0

我們當然都明白,沒有完美的方法可以解決這個問題,但有些解決方案可以讓您比其他解決方案更遠。

特別是,如果您只有一些常用前綴(先生,博士,夫人等),中綴(von,de,del等),後綴等的列表,則很容易超越簡單的空格分隔符(小,三,鍶等)等。如果你有一些共同的名字(如果你的名字是多樣的話,在不同的語言/文化中),這樣你就可以猜測中間的單詞是否可能成爲姓氏的一部分,這也很有幫助。

BibTeX還實施了一些啓發式方法,可以幫助您實現這一目標。它們封裝在perl模塊的Text::BibTeX::Name中。這裏有一個簡單的代碼示例,可以做一份合理的工作。

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"; 
1

如果您試圖在PHP中解析人名,我建議您使用Keith Beckman's 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'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; 
    } 


?> 
3

對於免費的基於SQL CLR的解決方案,請確保從Ambient Concepts中檢出SqlName,這對於在數據庫級別解析名稱有很大的幫助。

http://ambientconcepts.com/sqlname

2

替代簡單的方法是使用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 

source

1

這將案件串的工作是名字/中間名/姓

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 
0

工作由@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

入住此查詢雅典娜只有一個空間分隔的字符串(如名字和中間名的組合):

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

如果您希望有兩個或更多的空間,你可以輕鬆擴展上面的查詢。

相關問題