【问题标题】:Extract email address from string using tsql使用 tsql 从字符串中提取电子邮件地址
【发布时间】:2015-04-13 01:47:54
【问题描述】:

我正在尝试从现有 cmets 字段中提取电子邮件地址并将其放入自己的列中。字符串可能是这样的“this is an example comment with an email address of someemail@domain.org”,或者只是电子邮件本身“someemail@domain.org”。

我认为最好的办法是找到'@'符号的索引并在两个方向上搜索,直到字符串的末尾被命中或有空格。谁能帮我完成这个实现?

【问题讨论】:

  • 我会使用 PATINDEX 来查找电子邮件地址的起始位置。在线搜索电子邮件地址的模式:你会发现从最简单的模式到最复杂的模式,SQL-Server 甚至可能无法识别。然后我将使用 CHARINDEX 来定位下一个空格或字符串的结尾(如果 CHARINDEX 不会返回任何内容)

标签: sql sql-server regex tsql


【解决方案1】:

我知道 wewestthemenace 已经回答了这个问题,但他/她的解决方案似乎过于复杂。为什么要将电子邮件地址的左侧和右侧连接在一起?我宁愿只找到电子邮件地址的开头和结尾,然后使用子字符串返回电子邮件地址,如下所示:

我的桌子

DECLARE @Table TABLE (comment NVARCHAR(50));
INSERT INTO @Table
VALUES ('blah MyEmailAddress@domain.org'),            --At the end
        ('blah MyEmailAddress@domain.org blah blah'), --In the middle
        ('MyEmailAddress@domain.org blah'),           --At the beginning
        ('no email');

实际查询:

SELECT  comment,        
        CASE
            WHEN CHARINDEX('@',comment) = 0 THEN NULL
            ELSE SUBSTRING(comment,beginningOfEmail,endOfEmail-beginningOfEmail)
        END email
FROM @Table
CROSS APPLY (SELECT CHARINDEX(' ',comment + ' ',CHARINDEX('@',comment))) AS A(endOfEmail)
CROSS APPLY (SELECT DATALENGTH(comment)/2 - CHARINDEX(' ',REVERSE(' ' + comment),CHARINDEX('@',REVERSE(' ' + comment))) + 2) AS B(beginningOfEmail)

结果:

comment                                            email
-------------------------------------------------- --------------------------------------------------
blah MyEmailAddress@domain.org                     MyEmailAddress@domain.org
blah MyEmailAddress@domain.org blah blah           MyEmailAddress@domain.org
MyEmailAddress@domain.org blah                     MyEmailAddress@domain.org
no email                                           NULL

【讨论】:

  • 这似乎在我使用时抛出了“传递给左侧或子字符串函数的长度参数无效”异常。
  • 我的表中的数据类型是 nvarchar,所以我将 DATALENGTH 更改为 LEN,一切顺利。谢谢。
  • LEN() 忽略末尾的空白。 DATALENGTH() 不会忽略空白,但它的工作方式略有不同。它列出了字节。所以 VARCHAR(non-Unicode) 字节 = 字符串的长度。对于 NVARCHAR(Unicode),您需要使用 DATALENGTH() 除以 2
  • 对我来说效果很好,而且比公认的解决方案要简洁得多,后者也会为我的数据生成不正确的结果。另外为了了解 LEN 在字符串末尾不包含空格这一事实,我从来不知道!
【解决方案2】:

您可以在字符串中搜索'@'。然后在'@'LEFTRIGHT 一侧获得字符串。然后你想REVERSELEFT 一侧并获得' ' 的第一次出现,然后从那里获得SUBSTRING。然后REVERSE就可以得到原始表格了。相同的原则适用于RIGHT 侧,无需执行REVERSE

示例字符串:'some text someemail@domain.org some text'

  1. LEFT = '一些文本 someemail'
  2. RIGHT = '@domain.org 一些文字'
  3. 反向 LEFT = 'liameemos txet emos'
  4. SUBSTRING 直到第一个空格 = 'liameemos'
  5. REVERSE(4) = 一些电子邮件
  6. SUBSTRING (2) 直到第一个空格 = '@domain.org'
  7. 组合 5 和 6 = 'someemail@domain.org'

您的查询将是:

;WITH CteEmail(email) AS(
    SELECT 'someemail@domain.org' UNION ALL
    SELECT 'some text someemail@domain.org some text' UNION ALL
    SELECT 'no email'
)
,CteStrings AS(
    SELECT
        [Left] = LEFT(email, CHARINDEX('@', email, 0) - 1),
        Reverse_Left = REVERSE(LEFT(email, CHARINDEX('@', email, 0) - 1)),
        [Right] = RIGHT(email, CHARINDEX('@', email, 0) + 1)
    FROM CteEmail
    WHERE email LIKE '%@%'
)
SELECT *,
    REVERSE(
        SUBSTRING(Reverse_Left, 0, 
            CASE
                WHEN CHARINDEX(' ', Reverse_Left, 0) = 0 THEN LEN(Reverse_Left) + 1
                ELSE CHARINDEX(' ', Reverse_Left, 0)
            END
        )
    )
    +
    SUBSTRING([Right], 0,
        CASE
            WHEN CHARINDEX(' ', [Right], 0) = 0 THEN LEN([Right]) + 1
            ELSE CHARINDEX(' ', [Right], 0)
        END
    )
FROM CteStrings

样本数据:

email
----------------------------------------
someemail@domain.org
some text someemail@domain.org some text
no email

结果

---------------------
someemail@domain.org
someemail@domain.org

【讨论】:

  • 请务必阅读SUBSTRINGLEFTRIGHT 函数。
  • RIGTH 列有问题。我应该包括 LEN [Right] = RIGHT(email, LEN(email) - CHARINDEX('@', email, 0) + 1)
【解决方案3】:

Stephan 的答案非常适合在每一行中查找单个电子邮件地址。

但是,当我尝试在每一行中获取多个电子邮件地址时遇到了这个错误:

传递给 LEFT 或 SUBSTRING 函数的长度参数无效

我使用this answer from DBA Stack Exchange 来获取字符串中@ 的所有位置。它需要一个表值函数,该函数返回等于字符串中某个模式的数量的位置数。我还必须修改 CROSS APPLY 函数来处理多个电子邮件地址。

我的桌子

DECLARE @Table TABLE (comment VARCHAR(500));
INSERT INTO @Table (comment)
VALUES ('blah blah My.EmailAddress@domain.org more blah someemailaddress@domain.com even more blah asdf@gmail.com'),
       ('blah hello.world@domain.org more'),
       ('no email')

表值函数

CREATE FUNCTION dbo.fnFindPatternLocation
(
    @string NVARCHAR(MAX),
    @term   NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN 
    (
        SELECT pos = Number - LEN(@term) 
        FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@string, Number, 
        CHARINDEX(@term, @string + @term, Number) - Number)))
        FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
        FROM sys.all_objects) AS n(Number)
        WHERE Number > 1 AND Number <= CONVERT(INT, LEN(@string))
        AND SUBSTRING(@term + @string, Number, LEN(@term)) = @term
    ) AS y);
GO

查询

SELECT comment, pos, SUBSTRING(comment,beginningOfEmail,endOfEmail-beginningOfEmail) AS email
FROM @Table
CROSS APPLY (SELECT pos FROM dbo.fnFindPatternLocation(comment, '@')) AS A(pos)
CROSS APPLY (SELECT CHARINDEX(' ',comment + ' ', pos)) AS B(endOfEmail)
CROSS APPLY (SELECT pos - CHARINDEX(' ', REVERSE(SUBSTRING(comment, 1, pos))) + 2) AS C(beginningOfEmail)

结果

comment
---------------------------------------------------------------------------------------------------------
blah blah My.EmailAddress@domain.org more blah someemailaddress@domain.com even more blah asdf@gmail.com
blah blah My.EmailAddress@domain.org more blah someemailaddress@domain.com even more blah asdf@gmail.com
blah blah My.EmailAddress@domain.org more blah someemailaddress@domain.com even more blah asdf@gmail.com
blah hello.world@domain.org more

pos    email
---    ------------------------------
26     My.EmailAddress@domain.org
64     someemailaddress@domain.com
95     asdf@gmail.com
17     hello.world@domain.org

【讨论】:

  • 非常有帮助。我添加了一些 PATINDEX -clauses 以避免诸如“确保@mesomething”或其他不准确的事情。
【解决方案4】:
DECLARE @t TABLE (row_id INT, email VARCHAR(100))

INSERT @t (row_id, email)
VALUES (1, 'drgkls<ivan@gvi.ru>, info@gvi.com, @ dgh507-16-65@'),
        (2, 'hjshfkjshfj@kjs.kjsehf herwfjewr@kjsd.com adjfhja@.com u3483dhj@hhb@.dfj'),
        (3, 'kjsdghfjs4254.23detygh@jhjdfg.dgb лдоврывплдоо isgfsi@ klsdfksdl@,dd.com')

DECLARE @pat VARCHAR(100) = '%[^a-z0-9@._ ]%';

WITH f AS (
         SELECT    row_id,
                 CAST(' ' + email + ' ' AS VARCHAR(102)) email,
                 SUBSTRING(email, PATINDEX(@pat, email), 1) bad,
                 PATINDEX(@pat, email) pat
         FROM    @t
         UNION ALL
         SELECT    row_id,
                 CAST(REPLACE(email, bad, ' ') AS VARCHAR(102)),
                 SUBSTRING(REPLACE(email, bad, ' '), PATINDEX(@pat, REPLACE(email, bad, ' ')), 1) bad,
                 PATINDEX(@pat, REPLACE(email, bad, ' '))
         FROM    f
         WHERE    PATINDEX(@pat, email) > 0
     ),
     s AS 
     (
         SELECT    row_id,
                 email, PATINDEX('%@%', email) pos 
         FROM    f 
         WHERE    pat = 0
                 AND    PATINDEX('%@%', email) > 0
         UNION ALL
         SELECT    row_id,
                 SUBSTRING(email, pos + 1, 102), 
                 PATINDEX('%@%', SUBSTRING(email, pos + 1, 102))
         FROM    s
         WHERE    PATINDEX('%@%', SUBSTRING(email, pos + 1, 102)) > 0
     )

SELECT  row_id, o1 + pp
FROM    s   
        CROSS APPLY (SELECT    REVERSE(LEFT(email, pos -1)) s1) x
        CROSS APPLY (SELECT    CHARINDEX(' ', s1) i1) y
        CROSS APPLY (SELECT    REVERSE(LEFT(s1, i1 -1)) o1 WHERE i1 > 0) z
        CROSS APPLY (SELECT    CHARINDEX(' ', email, pos) i2) e
        CROSS APPLY (SELECT    SUBSTRING(email, pos, i2 -pos) pp WHERE    i2 > pos + 1) q
WHERE    LEN(o1) > 1
        AND CHARINDEX('.', pp) > 0
        AND PATINDEX('%@%@%', pp) = 0
        AND PATINDEX('%@.%', pp) = 0
        AND PATINDEX('%.', pp) = 0

【讨论】:

    【解决方案5】:

    这一行也可以(虽然有点长,但哈哈):

    --declare @a varchar(100) 
    --set @a = 'a asfd saasd asdfgh@asd.com wqe z zx cxzc '
    select substring(substring(@a,0,charindex('@',@a)),len(substring(@a,0,charindex('@',@a)))-charindex(' ',reverse(substring(@a,0,charindex('@',@a))))+2,len(substring(@a,0,charindex('@',@a)))) + substring(substring(@a,charindex('@',@a),len(@a)),0,charindex(' ',substring(@a,charindex('@',@a),len(@a))))
    

    【讨论】:

      【解决方案6】:

      对于包含换行符的字符串,我使用 PATINDEX 修改了 Felix 的答案,以搜索第一个控制字符而不是空格。

      我还必须修改 Right 字段以减去正确的文本数量。

          WITH CteEmail(email) AS(
              SELECT 'example string with new lines
      
          Email: some.example@email.address.com
      (first email address - should be returned)
      
          Email: another@test.co.uk
      (other email addresses should be ignored
      
      more example text' UNION ALL
              SELECT 'Email: some.example@email.address.com' UNION ALL
              SELECT 'someemail@domain.org' UNION ALL
              SELECT 'some text someemail@domain.org some text' UNION ALL
              SELECT 'no email'
          )
          ,CteStrings AS(
              SELECT
                  [Left] = LEFT(email, CHARINDEX('@', email, 0) - 1),
                  Reverse_Left = REVERSE(LEFT(email, CHARINDEX('@', email, 0) - 1)),
                  [Right] = RIGHT(email, LEN(email) - CHARINDEX('@', email, 0) + 1 )
              FROM CteEmail
              WHERE email LIKE '%@%'
          )
          SELECT *,
              REVERSE(
                  SUBSTRING(Reverse_Left, 0, 
                      CASE
                          WHEN PATINDEX('%[' + CHAR(10)+'- ]%', Reverse_Left) = 0 THEN LEN(Reverse_Left) + 1
                          ELSE PATINDEX('%[' + CHAR(0)+'- ]%', Reverse_Left)
                      END
                  )
              )
              +
              SUBSTRING([Right], 0,
                  CASE
                      WHEN PATINDEX('%[' + CHAR(0)+'- ]%', [Right]) = 0 THEN LEN([Right]) + 1
                      ELSE PATINDEX('%[' + CHAR(0)+'- ]%', [Right])
                  END
              )
          FROM CteStrings
      

      【讨论】:

        【解决方案7】:

        如果您在函数中需要它,那么这对我有用...

        CREATE FUNCTION [dbo].[extractEmail]
        (
            @input nvarchar(500)
        )
        RETURNS nvarchar(100)
        AS
        BEGIN
            DECLARE @atPosition int
            DECLARE @firstRelevantSpace int
            DECLARE @name nvarchar(100)
            DECLARE @secondRelelvantSpace int
            DECLARE @everythingAfterAt nvarchar(500)
            DECLARE @domain nvarchar(100)
            DECLARE @email nvarchar(100) = ''
            IF CHARINDEX('@', @input,0) > 0
            BEGIN
                SET @input = ' ' + @input
                SET @atPosition = CHARINDEX('@', @input, 0)
                SET @firstRelevantSpace = CHARINDEX(' ',REVERSE(LEFT(@input, CHARINDEX('@', @input, 0) - 1)))
                SET @name = REVERSE(LEFT(REVERSE(LEFT(@input, @atPosition - 1)),@firstRelevantSpace-1))
                SET @everythingAfterAt = SUBSTRING(@input, @atPosition,len(@input)-@atPosition+1)
                SET @secondRelelvantSpace = CHARINDEX(' ',@everythingAfterAt)
                IF @secondRelelvantSpace = 0
                    SET @domain = @everythingAfterAt
                ELSE
                    SET @domain = LEFT(@everythingAfterAt, @secondRelelvantSpace)
                SET @email = @name + @domain
            END
            RETURN @email
        END
        

        【讨论】:

          【解决方案8】:

          使用 Cymorg 的函数:我遇到了一个问题,我的数据包含 CR/LF,它阻止了函数 100% 工作。很难弄清楚,因为在 select 语句中使用该函数时,它偶尔会返回不正确的结果。如果我从我的查询结果中复制了有问题的文本并使用 sql print 和引号中的文本调用该函数,它会正常工作。不可思议!

          经过多次尝试和错误,我使用 sql replace 将 CR/LF 替换为空格和 huzza!我是一个很好的猜测者。

          select extractEmail(replace(replace(MyColumn,CHAR(10),' '),CHAR(13),' ')) as AsYouWish from FacilityContacts
          

          【讨论】:

            猜你喜欢
            • 2017-10-10
            • 2013-11-22
            • 1970-01-01
            • 2019-12-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-04-12
            • 1970-01-01
            相关资源
            最近更新 更多