【问题标题】:SQL: parse the first, middle and last name from a fullname fieldSQL:从全名字段解析名字、中间名和姓氏
【发布时间】:2022-12-09 05:06:31
【问题描述】:

如何使用 SQL 从全名字段中解析名字、中间名和姓氏?

我需要尝试匹配与全名不直接匹配的名称。我希望能够获取全名字段并将其分解为名字、中间名和姓氏。

数据不包含任何前缀或后缀。中间名是可选的。数据格式为“First Middle Last”。

我对一些实用的解决方案很感兴趣,以帮助我完成 90% 的工作。如前所述,这是一个复杂的问题,因此我将单独处理特殊情况。

【问题讨论】:

    标签: sql sql-server tsql parsing


    【解决方案1】:

    这是一个独立的示例,具有易于操作的测试数据。

    在这个例子中,如果你有一个包含三个以上部分的名字,那么所有“额外”的东西都会被放在 LAST_NAME 字段中。被标识为“标题”的特定字符串除外,例如“DR”、“MRS”和“MR”。

    如果缺少中间名,那么您只会得到 FIRST_NAME 和 LAST_NAME(MIDDLE_NAME 将为 NULL)。

    您可以将它粉碎成一个巨大的嵌套 SUBSTRING 块,但是可读性已经够难了,就像您在 SQL 中执行此操作一样。

    编辑——处理以下特殊情况:

    1 - NAME 字段为 NULL

    2 - NAME 字段包含前导/尾随空格

    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
    

    【讨论】:

    • 很好的答案,但如果全名包含前缀(博士、先生、女士),则效果不佳
    • @EfficionDave:好的,所以在我解决这个问题之前我无法忘记它。查看修改后的版本;不过,您必须手动提供要考虑“标题”的字符串列表。
    • 我稍微修改了代码,使其接受更多头衔(先生、夫人、小姐、fr、dr、sir、prof、lady、lord),并在称呼后接受一个点,例如:“约翰博士马修斯”。 pastebin.com/xfX9YFdN
    • 在这里它是一个独立的函数,可以像这样使用:SELECT * FROM NameSplitter('Mr. George Washington') pastebin.com/d91VBRe9
    • 做得很好!这非常有用,我通过声明一个带有标题前缀列表的变量来让它更干。
    【解决方案2】:

    不知道“全名”的格式如何,很难回答。

    它可以是“姓氏,名字中间名”或“名字中间名姓氏”等。

    基本上你必须使用子串功能

    SUBSTRING ( expression , start , length )
    

    可能是查理索引功能

    CHARINDEX (substr, expression)
    

    找出要提取的每个部分的开始和长度。

    所以假设格式是“名字姓氏”你可以(未经测试..但应该接近):

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

    【讨论】:

      【解决方案3】:

      另一种简单的方法是使用 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

      【讨论】:

      • 当有复合名称时,这不起作用,例如 van buren 的姓氏。
      【解决方案4】:

      反转问题,添加列来保存各个部分并将它们组合起来以获得全名。

      这将是最好的答案是,没有保证的方法可以确定一个人已经注册为他们的名字,以及他们的中间名是什么。

      例如,你会如何拆分这个?

      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
      

      我想在大多数语言中都能找到类似的情况。

      因此,与其尝试解释没有足够信息的数据以使其正确,不如存储正确的解释,并结合起来获得全名。

      【讨论】:

      • 不幸的是,这是数据转换。就是这样。
      • 然后你将不得不构建一个简单的算法,然后在你意识到错误时处理错误。
      • 我不认为每个名字都有意义地分解为 first-middle-last。组合起来也很困难,因为对于许多亚洲名字来说,姓氏排在名字之前。
      【解决方案5】:

      除非你有非常非常好的数据,否则这是一个不小的挑战。一种天真的方法是在空格上进行标记,并假设三标记结果是 [first, middle, last] 而二标记结果是 [first, last],但你将不得不处理多-单词姓氏(例如“Van Buren”)和多个中间名。

      【讨论】:

      • 我最终变得天真了,最后一切都解决了。谢谢。
      • 这不是答案。人人都知道这道题很难,但如果你能容忍一些错误,也不是不可能做到 90%。
      【解决方案6】:

      此查询工作正常。

      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
      

      【讨论】:

        【解决方案7】:

        您确定法定全名始终包括名字、中间名和姓氏吗?我认识只有一个名字作为法定全名的人,老实说,我不确定那是他们的名字还是姓氏。 :-) 我也认识一些人,他们的法定姓名中有多个第一名,但没有中间名。有些人有多个中间名。

        然后还有全名中的姓名顺序。据我所知,在某些亚洲文化中,姓氏在法定全名中排在第一位。

        从更实际的角度来看,您可以将全名拆分为空格,并将第一个标记作为名字,将最后一个标记(或者在只有一个名字的情况下的唯一标记)作为姓氏。尽管这假定顺序将始终相同。

        【讨论】:

        • 也有只有名字的人。不仅像麦当娜、雪儿和波诺这样的名人,而且在冰岛也有传统,例如只用您的名字。
        • 这似乎是我需要使用的实用方法。中间名可以是名字或姓氏中未包含的任何内容。
        • @Bill Karwin - 是的,我提到过如果你有一个只有一个名字的人,那么不清楚这是他们的名字还是姓氏。
        【解决方案8】:

        这将适用于字符串为 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
        

        【讨论】:

        • 我在这里遇到了一个案例,它抛出 Msg 537,Level 16,State 3,Line 6 Invalid length parameter passed to the LEFT or SUBSTRING function
        【解决方案9】:

        就像#1 说的,这不是微不足道的。带连字符的姓氏、首字母、双名、倒序的姓名和其他各种异常情况可能会破坏您精心设计的功能。

        您可以使用 3rd 方库(插件/免责声明 - 我在这个产品上工作):

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

        【讨论】:

        • 嘿,我们使用 Melissa 数据作为邮政编码。我不知道你有名字的东西,需要检查一下。
        【解决方案10】:

        我会把它作为一个迭代过程来做。

        1)将表格转储到平面文件中以供使用。

        2) 编写一个简单的程序来使用空格作为分隔符来分解您的姓名,其中第一个标记是名字,如果有 3 个标记,则标记 2 是中间名,标记 3 是姓氏。如果有 2 个标记,则第二个标记是姓氏。 (Perl、Java 或 C/C++,语言无关紧要)

        3)目测结果。查找不符合此规则的名称。

        4)使用该示例,创建一个新规则来处理该异常......

        5)冲洗并重复

        最终你会得到一个修复所有数据的程序。

        【讨论】:

          【解决方案11】:

          下面是一个存储过程,它将把找到的第一个单词放入 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
          

          【讨论】:

          • 我喜欢这个解决方案。它简单、优雅、易于阅读,适用于大多数情况。在我的用例中,我最感兴趣的是查找名字和姓氏。这做得很好。
          【解决方案12】:

          如果你想用 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;
              }
          
          
          ?>
          

          【讨论】:

            【解决方案13】:
            1. 获取 sql regex 函数。样本:http://msdn.microsoft.com/en-us/magazine/cc163473.aspx
            2. 使用正则表达式提取名称。

              我推荐使用 Espresso 来学习/构建/测试正则表达式。 Old free version, new commercial version

            【讨论】:

              【解决方案14】:

              我曾经制作了一个 500 个字符的正则表达式来解析任意字符串中的名字、姓氏和中间名。即使使用那个鸣喇叭的正则表达式,由于输入的完全不一致,它的准确率也只有 97% 左右。不过,聊胜于无。

              【讨论】:

                【解决方案15】:

                根据已经提出的有关名称中的空格和其他异常的警告,以下代码将至少处理 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 中的值结尾的名称都是不合格的,需要以不同方式处理。

                【讨论】:

                  【解决方案16】:

                  正如其他人所说,您不能通过简单的编程方式。

                  考虑这些例子:

                  • 总统“乔治·赫伯特·沃克·布什”(First Middle Middle Last)

                  • 总统刺客“约翰·威尔克斯·布斯”(第一中 最后的)

                  • 吉他手“Eddie Van Halen”(First Last Last)

                  • 他的妈妈可能称他为 Edward Lodewijk Van Halen(First 中间最后最后)

                  • 著名的漂流者“Mary Ann Summers”(名次)

                  • New Mexico GOP chairman“Fernando C de Baca”(名字最后最后最后)

                  【讨论】:

                    【解决方案17】:

                    我不确定 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”)所以我会非常小心结果。

                    【讨论】:

                      【解决方案18】:

                      我们当然都明白,没有完美的方法可以解决这个问题,但有些解决方案可以让您比其他解决方案走得更远。

                      特别是,如果您只有一些常见前缀(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("	", $dr, map "@{[ $n->part($_) ]}", qw(first von last jr)), "
                      ";
                      

                      【讨论】:

                        【解决方案19】:

                        我在做这件事时遇到的最大问题是像“Bob R. Smith, Jr.”这样的案例。我使用的算法发布在http://www.blackbeltcoder.com/Articles/strings/splitting-a-name-into-first-and-last-names。我的代码是在 C# 中,但如果你必须在 SQL 中,你可以移植它。

                        【讨论】:

                          【解决方案20】:

                          @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')
                          

                          【讨论】:

                            【解决方案21】:

                            在 Athena 中检查此查询是否只有一个空格分隔的字符串(例如名字和中间名组合):

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

                            如果您希望有两个或更多空格,您可以轻松地扩展上面的查询。

                            【讨论】:

                              【解决方案22】:

                              基于@hajili 的贡献(这是对 parsename 函数的创造性使用,旨在解析以句点分隔的对象的名称),我对其进行了修改,以便它可以处理数据不包含中间名或当名字是“John and Jane Doe”时。它不是 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}
                              

                              【讨论】:

                                【解决方案23】:

                                Employee 表有“姓名”列,我们必须将其拆分为名字、中间名和姓氏。如果 name 列具有两个单词的值,如“James Thomas”,则此查询将处理以将中间名保留为 null。

                                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
                                

                                【讨论】:

                                  【解决方案24】:

                                  我想发布对 hajili 建议的更新,但这个回复太长了,无法对该建议发表评论。

                                  我们的问题是“Lastname, Firstname Middlename”,其中一些姓氏中有一个空格。

                                  所以我们想出了:

                                  ,FullName           = CUST.FULLNAME
                                  ,LastName           = PARSENAME(REPLACE(CUST.FULLNAME, ',', '.'),2)
                                  ,FirstName          = (CASE WHEN PARSENAME(REPLACE(CUST.FULLNAME, ',', '.'),1) LIKE '% %' THEN PARSENAME(REPLACE(PARSENAME(REPLACE(CUST.FULLNAME, ',', '.'),1), ' ', '.'),2) ELSE PARSENAME(REPLACE(CUST.FULLNAME, ',', '.'),1) END)
                                  ,MiddleName         = (CASE WHEN PARSENAME(REPLACE(CUST.FULLNAME, ' ', '.'),1) LIKE '%,%' THEN NULL ELSE PARSENAME(REPLACE(CUST.FULLNAME, ' ', '.'),1) END)
                                  

                                  【讨论】:

                                    【解决方案25】:

                                    选择 SUBSTRING_INDEX(name, ' ', 1) 作为 fname,SUBSTRING_INDEX(SUBSTRING_INDEX(name, ' ', 2), ' ', -1) 作为 mname,SUBSTRING_INDEX(name, ' ', -1) 作为 lname FROM Person

                                    【讨论】:

                                    • 将其放入块代码中并添加更多解释。仅仅共享代码是不够的。没有格式化等
                                    【解决方案26】:

                                    如果“fullname”列是“Last, First - Middle”格式(通常不是,但让我们假设它是),那么这行得通。在我的 SQL 中完成。在第一行中,“内部”SUBSTRING_INDEX() 获取从左到‘-’的所有内容,即“Last, First”;然后“外部”SUBSTRING_INDEX() 从这个新的“Last, First”字符串(即“First”)中获取从右到‘,’的所有内容。 第二行取从右到‘-’的棋子,即“中”。 第三行获取从左到‘,’的第一个字符串。

                                        SUBSTRING_INDEX(SUBSTRING_INDEX(fullname, ' - ', 1), ', ', -1) AS First,
                                        SUBSTRING_INDEX(fullname, ' - ', -1), AS Middle,
                                        SUBSTRING_INDEX(fullname, ', ', 1) AS Last,
                                    

                                    【讨论】:

                                      猜你喜欢
                                      • 2010-09-14
                                      • 1970-01-01
                                      • 2015-01-07
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2020-02-21
                                      • 1970-01-01
                                      • 1970-01-01
                                      相关资源
                                      最近更新 更多