【问题标题】:Retrieve initials from a SQL Server Table从 SQL Server 表中检索首字母
【发布时间】:2018-09-10 17:01:13
【问题描述】:

我一直致力于处理 sql 表并拆分数据。我已经开始从姓氏中拆分一些首字母。唯一的问题是,首字母被隔开。例如(我表中的数据)

  • Hanse J S P > J S P 是缩写
  • Gerson B D V > B D V 是缩写
  • J D Timberland > J D 是缩写

所以基本上,它最多有四个首字母,可以在字符串的开头、中间或结尾。我不知道应该如何导入这些。到一个单独的列中,结果将是:

COL A | COL B
J S P | Jansen
B D V | Gerson
J D   | Timberland

谁能指出我正确的方向?我正在使用 SQL Server。

【问题讨论】:

  • 什么版本的 SQL Server?
  • 你如何定义首字母是什么?
  • 这是 sql server 2012,首字母可以通过一个字母后跟一个空格来定义。
  • 并不总是后跟空格,因为 Hanse J S P 会以 P 结尾且没有尾随空格
  • 没错,我错了。因此,它可以是一个字母,后跟一个空格,后跟一个字母,或者是字符串中的最后一个字符。

标签: sql sql-server


【解决方案1】:

这是一种滥用Parsename 函数的相当笨拙的方法。这里最大的警告是 Parsename 仅限于 4 个令牌,因此 J S P Jansen 可以工作,但 J S P C JansenJohn J S P Jansen 不会。

With parsedname AS
(
  SELECT
      PARSENAME(replace(name, ' ', '.'), 1) name1,
      PARSENAME(replace(name, ' ', '.'), 2) name2,
      PARSENAME(replace(name, ' ', '.'), 3) name3,
      PARSENAME(replace(name, ' ', '.'), 4) name4
   FROM yourtable
)
SELECT 
   CASE WHEN LEN(name4) = 1 THEN name4 ELSE '' END +
      CASE WHEN LEN(name3) = 1 THEN name3 ELSE '' END +
      CASE WHEN LEN(name2) = 1 THEN name2 ELSE '' END +
      CASE WHEN LEN(name1) = 1 THEN name1 ELSE '' END as initials,
   CASE WHEN LEN(name1) > 1 THEN name1 
      WHEN LEN(name2) > 1 THEN name2
      WHEN LEN(name3) > 1 THEN name3
      WHEN LEN(name4) > 1 THEN name4
      END as surname
FROM parsedname

Here is a sqlfiddle of this in action

CREATE TABLE NAMES (name varchar(50));
INSERT INTO NAMES VALUES ('J S P Jansen');
INSERT INTO NAMES VALUES ('B D V Gerson');
INSERT INTO NAMES VALUES ('J D Timberland');

With parsedname AS
(
  SELECT
      PARSENAME(replace(name, ' ', '.'), 1) name1,
      PARSENAME(replace(name, ' ', '.'), 2) name2,
      PARSENAME(replace(name, ' ', '.'), 3) name3,
      PARSENAME(replace(name, ' ', '.'), 4) name4
   FROM names
)
SELECT 
   CASE WHEN LEN(name4) = 1 THEN name4 ELSE '' END +
      CASE WHEN LEN(name3) = 1 THEN name3 ELSE '' END +
      CASE WHEN LEN(name2) = 1 THEN name2 ELSE '' END +
      CASE WHEN LEN(name1) = 1 THEN name1 ELSE '' END as initials,
   CASE WHEN LEN(name1) > 1 THEN name1 
      WHEN LEN(name2) > 1 THEN name2
      WHEN LEN(name3) > 1 THEN name3
      WHEN LEN(name4) > 1 THEN name4
      END as surname
FROM parsedname

+----------+------------+
| initials |  surname   |
+----------+------------+
| JSP      | Jansen     |
| BDV      | Gerson     |
| JD       | Timberland |
+----------+------------+

如果在这些字母之间需要一个空格,您可以将 CASE 语句翻转为类似:

TRIM(CASE WHEN LEN(name4) = 1 THEN name4 + ' ' ELSE '' END +
      CASE WHEN LEN(name3) = 1 THEN name3 + ' ' ELSE '' END +
      CASE WHEN LEN(name2) = 1 THEN name2 + ' ' ELSE '' END +
      CASE WHEN LEN(name1) = 1 THEN name1 + ' ' ELSE '' END) as initials

SQLFiddle with the spaces

+----------+------------+
| initials |  surname   |
+----------+------------+
| J S P    | Jansen     |
| B D V    | Gerson     |
| J D      | Timberland |
+----------+------------+

【讨论】:

  • 如果字符串是 "A ! ? B 不管什么",你的结果将是 "A!?B" 而不是 "A B"。
  • 嗯......我们只是希望这不会发生。
  • 我认为首字母应该是字母。他的要求之一是在首字母之间添加一个空格。
  • @NicVerAZ 不错。我在底部添加了一点,以从所有已解析和损坏的单词中取回空格。这就像弗兰肯斯坦的怪物。
【解决方案2】:

这个使用 CHARINDEX 和递归 CTE 从名称中提取空格分隔的子字符串:

  • 查找第一个空格前的子字符串
  • 将剩余的子字符串提供给相同的 CTE

一旦有了子字符串,只需将它们粘回去:

WITH yourdata(FullName) AS (
    SELECT 'Hanse J S P' UNION
    SELECT 'Gerson B D V' UNION
    SELECT 'J D Timberland' UNION
    SELECT 'TEST 1 TEST 2 TEST 3'
), cte AS (
    SELECT
        FullName,
        CASE WHEN Pos1 = 0 THEN FullName ELSE SUBSTRING(FullName, 1, Pos1 - 1) END AS LeftPart,
        CASE WHEN Pos1 = 0 THEN Null     ELSE SUBSTRING(FullName, Pos1 + 1, Pos2 - Pos1) END AS NextPart,
        1 AS PartSort
    FROM yourdata
    CROSS APPLY (SELECT CHARINDEX(' ', FullName) AS Pos1, LEN(FullName) AS Pos2) AS CA
    UNION ALL
    SELECT
        FullName,
        CASE WHEN Pos1 = 0 THEN NextPart ELSE SUBSTRING(NextPart, 1, Pos1 - 1) END,
        CASE WHEN Pos1 = 0 THEN Null     ELSE SUBSTRING(NextPart, Pos1 + 1, Pos2 - Pos1) END,
        PartSort + 1
    FROM cte
    CROSS APPLY (SELECT CHARINDEX(' ', NextPart) AS Pos1, LEN(NextPart) AS Pos2) AS CA
    WHERE NextPart IS NOT NULL
)
SELECT yourdata.FullName, STUFF(CA1.XMLStr, 1, 1, '') AS Initials, STUFF(CA2.XMLStr, 1, 1, '') AS Names
FROM yourdata
CROSS APPLY (
    SELECT CONCAT(' ', LeftPart)
    FROM cte
    WHERE FullName = yourdata.FullName AND LEN(LeftPart) = 1
    ORDER BY PartSort
    FOR XML PATH('')
) AS CA1(XMLStr)
CROSS APPLY (
    SELECT CONCAT(' ', LeftPart)
    FROM cte
    WHERE FullName = yourdata.FullName AND LEN(LeftPart) > 1
    ORDER BY PartSort
    FOR XML PATH('')
) AS CA2(XMLStr)

结果:

| FullName             | Initials | Names          |
|----------------------|----------|----------------|
| Gerson@B@D@V         | B D V    | Gerson         |
| Hanse@J@S@P          | J S P    | Hanse          |
| J@D@Timberland       | J D      | Timberland     |
| TEST@1@TEST@2@TEST@3 | 1 2 3    | TEST TEST TEST |

【讨论】:

    【解决方案3】:

    类似于 JNevil 的回答 (+1),但不限于 4 个令牌。

    示例

    Declare @YourTable table (SomeCol varchar(50))
    Insert Into @YourTable values
     ('Hanse J S P')
    ,('Gerson B D V')
    ,('J D Timberland')
    ,('J D Timberland / J R R Tolkien')
    
    
    Select A.SomeCol
          ,ColA = ltrim(
                  concat(IIF(len(Pos1)=1,' '+Pos1,null)
                        ,IIF(len(Pos2)=1,' '+Pos2,null)
                        ,IIF(len(Pos3)=1,' '+Pos3,null)
                        ,IIF(len(Pos4)=1,' '+Pos4,null)
                        ,IIF(len(Pos5)=1,' '+Pos5,null)
                        ,IIF(len(Pos6)=1,' '+Pos6,null)
                        ,IIF(len(Pos7)=1,' '+Pos7,null)
                        ,IIF(len(Pos8)=1,' '+Pos8,null)
                        ,IIF(len(Pos9)=1,' '+Pos9,null)
                        )
                  )
          ,ColB = ltrim(
                  concat(IIF(Pos1 not Like '[a-z]',' '+Pos1,null)
                        ,IIF(Pos2 not Like '[a-z]',' '+Pos2,null)
                        ,IIF(Pos3 not Like '[a-z]',' '+Pos3,null)
                        ,IIF(Pos4 not Like '[a-z]',' '+Pos4,null)
                        ,IIF(Pos5 not Like '[a-z]',' '+Pos5,null)
                        ,IIF(Pos6 not Like '[a-z]',' '+Pos6,null)
                        ,IIF(Pos7 not Like '[a-z]',' '+Pos7,null)
                        ,IIF(Pos8 not Like '[a-z]',' '+Pos8,null)
                        ,IIF(Pos9 not Like '[a-z]',' '+Pos9,null)
                        )
                  )
     From  @YourTable A
     Cross Apply (
                    Select Pos1 = xDim.value('/x[1]','varchar(max)')
                          ,Pos2 = xDim.value('/x[2]','varchar(max)')
                          ,Pos3 = xDim.value('/x[3]','varchar(max)')
                          ,Pos4 = xDim.value('/x[4]','varchar(max)')
                          ,Pos5 = xDim.value('/x[5]','varchar(max)')
                          ,Pos6 = xDim.value('/x[6]','varchar(max)')
                          ,Pos7 = xDim.value('/x[7]','varchar(max)')
                          ,Pos8 = xDim.value('/x[8]','varchar(max)')
                          ,Pos9 = xDim.value('/x[9]','varchar(max)')
                    From  (Select Cast('<x>' + replace(SomeCol,' ','</x><x>')+'</x>' as xml) as xDim) as A 
                 ) B
    

    退货

    SomeCol                           ColA            ColB
    Hanse J S P                       J S P           Hanse
    Gerson B D V                      B D V           Gerson
    J D Timberland                    J D             Timberland
    J D Timberland / J R R Tolkien    J D / J R R     Timberland / Tolkien
    

    【讨论】:

    • 这是一个非常漂亮的 XML 字符串拆分方法,可以返回到这个答案。我喜欢这个。
    • 另外,对于遇到这个问题的其他人,我相信如果 iif() 被替换为 CASE WHEN &lt;condition&gt; THEN &lt;trueValue&gt; ELSE &lt;falseValue&gt; END,这个答案可以一直追溯到 SQL 2008。
    【解决方案4】:

    我为此使用了一些内置函数。大致思路是使用string_split将字符串拆分成行,使用ROW_NUMBER根据长度和字符串中的char(s)位置保存顺序,然后使用FOR XML PATH()将行连接成单个列。

    --Assume your data structure
    DECLARE @temp TABLE (thestring varchar(1000))
    INSERT INTO @temp VALUES
     ('Hanse J S P'), ('Gerson B D V'), ('J D Timberland')
    
    ;WITH CTE AS
    (
        SELECT *
            ,ROW_NUMBER() OVER (PARTITION BY thestring ORDER BY thestring, LEN(value) ASC, pos ASC) [order]
            FROM (
                    SELECT *      
                        , value AS [theval]
                        , CHARINDEX(CASE WHEN len(value) = 1 THEN ' ' + value ELSE value END, thestring) AS [pos]
                    FROM @temp CROSS APPLY string_split(thestring, ' ')
                )  AS dT
    )
    SELECT ( SELECT value + ' ' AS [text()]
                     FROM cte 
                    WHERE cte.thestring = T.thestring
                      AND LEN(theval) = 1
                    FOR XML PATH('')
           ) AS [COL A]
          ,( SELECT value + ' ' AS [text()]
                     FROM cte 
                    WHERE cte.thestring = T.thestring
                      AND LEN(theval) > 1
                    FOR XML PATH('')
           ) AS [COL B]
      FROM @temp T 
    GROUP BY thestring
    

    产生输出:

    COL A   COL B
    -----   -----
    B D V   Gerson 
    J S P   Hanse 
    J D     Timberland 
    

    【讨论】:

    • 很遗憾,string_split() 在 SQL 2012 中不可用。它是在 SQL 2016 中添加的。
    • @Shawn ahh 没有看到那条评论,这是问题所在。抱歉,它对 OP 没有帮助。
    【解决方案5】:

    您拥有哪个版本的 SQL Server? STRING_SPLIT() 可用吗?

    如果是,则使用空格作为分隔符进行拆分,遍历结果字符串,评估它们的长度,并在所述字符串长度为一个字符且为字母时将结果字符串与该字符串连接。

    除非结果字符串到目前为止为空,否则在前面添加一个空格。

    如果 STRING_SPLIT() 不可用...嗯...这里有一些解决方案:

    T-SQL split string based on delimiter

    -- 附录

    对于问题的第二部分(我最初发布回复时最初不存在),您想将非首字母部分隔离到第二列中,我基本上会用两个结果分隔两个逻辑块基于每个元素长度的字符串。

    注意:这在 2016 年之前的 SQL Server 中不会很优雅,甚至可能需要 CURSOR(叹气)

    我知道我会因为提到光标而被否决。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 2010-10-19
      • 2012-03-03
      • 1970-01-01
      相关资源
      最近更新 更多