如何使用 SQL 从全名字段中解析名字、中间名和姓氏?
我需要尝试匹配与全名不直接匹配的名称。我希望能够获取全名字段并将其分解为名字、中间名和姓氏。
数据不包括任何前缀或后缀。中间名是可选的。数据格式为“First Middle Last”。
我对一些实用的解决方案感兴趣,可以让我完成 90% 的工作。如前所述,这是一个复杂的问题,因此我将单独处理特殊情况。
如何使用 SQL 从全名字段中解析名字、中间名和姓氏?
我需要尝试匹配与全名不直接匹配的名称。我希望能够获取全名字段并将其分解为名字、中间名和姓氏。
数据不包括任何前缀或后缀。中间名是可选的。数据格式为“First Middle Last”。
我对一些实用的解决方案感兴趣,可以让我完成 90% 的工作。如前所述,这是一个复杂的问题,因此我将单独处理特殊情况。
这是一个独立的示例,具有易于操作的测试数据。
在这个例子中,如果你的名字包含三个以上的部分,那么所有“额外”的东西都会放在 LAST_NAME 字段中。对于标识为“titles”的特定字符串(例如“DR”、“MRS”和“MR”)例外。
如果缺少中间名,那么您只会得到 FIRST_NAME 和 LAST_NAME(MIDDLE_NAME 将为 NULL)。
你可以将它粉碎成一个巨大的嵌套的 SUBSTRING 块,但是当你在 SQL 中这样做时,可读性已经够难了。
编辑——处理以下特殊情况:
1 - NAME 字段为 NULL
2 - NAME 字段包含前导/尾随空格
3 - NAME 字段的名称中有 > 1 个连续空格
4 - NAME 字段仅包含名字
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
不知道“全名”的格式很难回答。
它可以是“姓氏,名字中间名”或“名字中间名姓氏”等。
基本上你必须使用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
反转问题,添加列以保存各个部分并将它们组合起来以获得全名。
这将是最佳答案的原因是,无法保证确定一个人已注册为他们的名字,以及他们的中间名是什么。
例如,您将如何拆分它?
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
我想在大多数语言中都可以找到类似的情况。
因此,与其尝试解释没有足够信息以使其正确的数据,不如存储正确的解释,并结合起来得到全名。
另一种简单的方法是使用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
除非你有非常非常好的数据,否则这是一个不小的挑战。一种天真的方法是对空格进行标记,并假设三个标记的结果是 [first, middle, last],而两个标记的结果是 [first, last],但是您将不得不处理多个单词姓氏(例如“Van Buren”)和多个中间名。
此查询工作正常。
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
您确定完整的法定姓名将始终包含名字、中间名和姓氏吗?我认识的人只有一个名字作为法定全名,老实说,我不确定这是他们的名字还是姓氏。:-) 我也认识一些人,他们的法定姓名中有多个名字,但没有中间名。有些人有多个中间名。
然后还有完整法定名称中的名称顺序。据我所知,在某些亚洲文化中,姓氏排在法定全名的首位。
在更实际的说明中,您可以在空格上拆分全名,并将第一个标记作为名字,将最后一个标记(或只有一个名字的唯一标记)作为姓氏。尽管这假设顺序将始终相同。
这将在字符串是 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
就像#1 说的,这不是微不足道的。连字符的姓氏、姓名首字母、双名、逆名顺序和各种其他异常可能会破坏您精心设计的功能。
您可以使用 3rd 方库(插件/免责声明 - 我在此产品上工作):
我会将此作为一个迭代过程。
1)将表转储到一个平面文件中。
2)编写一个简单的程序来使用空格作为分隔符来分解您的姓名,其中 first 标记是名字,如果有 3 个标记,则标记 2 是中间名,标记 3 是姓氏。如果有 2 个标记,则第二个标记是姓氏。(Perl、Java 或 C/C++,语言无关紧要)
3) 观察结果。寻找不符合此规则的名称。
4)使用该示例,创建一个新规则来处理该异常......
5) 冲洗并重复
最终,您将获得一个修复所有数据的程序。
如果您尝试在 PHP 中解析人名,我推荐Keith Beckman 的 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;
}
?>
我不确定 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'),所以我会非常小心结果。
我曾经制作了一个 500 个字符的正则表达式来解析任意字符串中的名字、姓氏和中间名。即使使用那个鸣喇叭的正则表达式,由于输入的完全不一致,它也只能得到大约 97% 的准确率。不过,总比没有好。
根据已经提出的关于名称中的空格和其他异常的警告,以下代码将至少处理 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 值结尾的名称都是不合格的,需要以不同的方式处理。
这是一个存储过程,它将找到的第一个单词放入 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
正如其他人所说,你不能从简单的程序化方式。
考虑这些例子:
总统“乔治·赫伯特·沃克·布什”(First Middle Middle Last)
总统刺客“约翰·威尔克斯·布斯”(First Middle Last)
吉他手“Eddie Van Halen”(First Last Last)
他的妈妈可能称他为 Edward Lodewijk Van Halen (First Middle Last Last)
著名的漂流者“玛丽安萨默斯”(First First Last)
新墨西哥州共和党主席“Fernando C de Baca”(First Last Last Last)
我们当然都明白,没有完美的方法可以解决这个问题,但有些解决方案可以让你走得更远。
特别是,如果您只有一些常见前缀(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";
我遇到的最大问题是像“Bob R. Smith, Jr.”这样的案例。我使用的算法发布在http://www.blackbeltcoder.com/Articles/strings/splitting-a-name-into-first-and-last-names。我的代码在 C# 中,但如果你必须在 SQL 中,你可以移植它。
@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')
在 Athena 中检查这个查询是否只有一个空格分隔的字符串(例如名字和中间名组合):
SELECT name, REVERSE( SUBSTR( REVERSE(name), 1, STRPOS(REVERSE(name), ' ') ) ) AS middle_name
FROM name_table
如果您希望有两个或更多空格,则可以轻松扩展上述查询。
基于@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}
员工表有“姓名”列,我们必须将其拆分为名字、中间名和姓氏。如果 name 列的值是两个单词,例如“James Thomas”,则此查询将处理以将中间名保持为空。
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
我想发布对 hajili 建议的更新,但这个回复太长,无法评论该建议。
我们的问题是“姓,名中间名”,其中一些姓氏中有一个空格。
所以我们想出了:
,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)