如何从T-SQL中的字符串中获取前n个句子?

时间:2016-10-05 19:01:41

标签: sql sql-server tsql

我正在努力填写我们的简短描述"字段使用"完整描述"领域。基本上,我想将ShortDescription列设置为等于FullDescription列中的前三个句子。

我知道如何在C#中执行此操作,但我在SQL查询中完成此操作时遇到一些麻烦。我不关心性能 - 因为此查询只会运行一次以生成此临时数据。所以,任何和所有解决方案都可以找到我们的工作!

我的尝试:

UPDATE Product
    SET ShortDescription = (
        CASE
            WHEN (LEN(FullDescription) - LEN(REPLACE(FullDescription, '.', ''))) >= 3 THEN
                (
                    SELECT
                        LEFT(str, pos)
                    FROM (
                        SELECT
                            FullDescription AS str,
                            CHARINDEX('.', FullDescription) AS pos
                    ) x
                )
            ELSE
                FullDescription
        END
    )
WHERE FullDescription IS NOT NULL;

不幸的是,上面的查询只获得第一句话。我似乎无法弄清楚如何找到第三期的CHARINDEX。任何人都知道找到这个角色的简单方法吗?

另外,我是否认为句号确实是判断句子的唯一方法?我担心(在极少数情况下),句子中可能会有小数,这会提供一些可怕的描述,如:"这个产品很棒。它有很棒的功能。它是2。" ...

非常感谢任何方向或反馈!谢谢!

6 个答案:

答案 0 :(得分:3)

您可以使用真正的正则表达式,这使得此任务更容易。 T-SQL本身不支持正则表达式,但您可以使用SQLCLR通过.NET访问它们,在这种情况下,它们可以直接绑定到UPDATE语句中。例如:

DECLARE @Pattern NVARCHAR(4000) = 
                           N'((?:Mr\.|Ms\.|Mrs\.|Sr\.|Jr\.|Dr\.|.)+?[.!?](?:\s+|$)){1,3}';

SELECT SQL#.RegEx_MatchSimple4k(tmp.[txt], @Pattern, 1, NULL)
FROM   (VALUES (N'Sentence uno!  Two, I think.  Only 2.3 till 3:12 A.M. Numero 4. Y five.'),
               (N'First one?      Second one.'),
               (N'Hello, this is Dr. Zhivago. Nice to meet you! I''m Mr. Mister. Really?')
       ) tmp(txt);

返回:

Sentence uno!  Two, I think.  Only 2.3 till 3:12 A.M. 
First one?      Second one.
Hello, this is Dr. Zhivago. Nice to meet you! I'm Mr. Mister. 

一些注意事项:

  • 您可以从多个地方获取SQLCLR RegEx对象。预编译的RegEx函数的一个来源是我创建的SQL# SQLCLR库。免费版本包含大多数RegEx功能,包括上面示例中使用的功能。

  • 正则表达式:

    • 查找以下列任意字符结尾的“句子”:.?!
    • 在这种情况下,“句子”是:“先生”,“女士”,“太太”,“老”,“小”,“博士”的出现次数最少,或者在找到句号,感叹号或问号之前的任何其他字符。
    • 可以处理包含少于3个“句子”的字符串(如示例所示)
    • 可以处理用作小数位或缩写词的句点(如示例所示)
  • 我想不出有任何编程方式来了解标题的缩写(例如Mr.Ms.Mrs.Dr.等)一般来说,不是句子的结尾,除了有一个要检查的列表。我提供了上面显示的模式的简短列表,它可以很容易地扩展。但这些都有点容易,因为他们永远不会结束一句话。您还可以使用度量单位的缩写(例如lbs.),可以在句子中间或结尾处。

答案 1 :(得分:2)

我有一个可能有帮助的TVF。如果您不想使用UDF,则可以轻松地将代码移植到Cross Apply中。

我应该注意。此分隔符是一个句点,后跟一个空格。刚才认为它不会捕获其他标点符号(i,e。!)

Declare @String varchar(max) ='This is sentance one. This is sentance two.  This is Sentence 3.  This is sentecne 4.'
Declare @YourTable table (ID int,FullDescription varchar(max))
Insert Into @YourTable values
(1,'Some sentance with a decimal like 25.26 is OK. Sentance number two.  Sentance number 3. Sentance number 4 would not be included.'),
(2,'I know how I would do this in C#.  I am having a little trouble getting it done in my SQL query. I don''t care about performance. This query will only be ran one time.')

Select A.*
      ,B.ShortDescription
 From @YourTable A
 Cross Apply (Select ShortDescription=concat(Pos1,'. ',Pos2,'. ',Pos3,'.') From [dbo].[udf-Str-Parse-Row](A.FullDescription,'. ')) B

返回 enter image description here

如果需要,可以使用UDF

CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (@String varchar(max),@Delimiter varchar(10))
Returns Table 
As
Return (
    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(@String,@Delimiter,'</x><x>')+'</x>' as XML) as xDim) A
)
--Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')

答案 2 :(得分:2)

尝试这个简单的技巧:

UPDATE Product
    SET ShortDescription = (
        CASE
            WHEN (LEN(FullDescription) - LEN(REPLACE(FullDescription, '.', ''))) >= 3 THEN
                (
                    SELECT
                        LEFT(str, pos)
                    FROM (
                        SELECT
                            FullDescription AS str,
                            CHARINDEX('.', FullDescription,
                               CHARINDEX('.', FullDescription,
                                  CHARINDEX('.', FullDescription)+1)+1) AS pos
                    ) x
                )
            ELSE
                FullDescription
        END
    )
WHERE FullDescription IS NOT NULL;

说明:T-SQL CHARINDEX function接受一个可选参数,指示开始搜索的索引。因此,通过嵌套其中三个调用,每个嵌套调用的结果将用作搜索下一个调用的起始点。最嵌套的调用将找到第一个句点,然后我们将超过该字符的一个字符前进并搜索第二个字符,然后再搜索第三个字符。

如果您想要超过三个句子,这将不是正确的答案,并且如果没有CASE结构中已有的保护条款,它将会产生错误,但它应该根据您的基本策略发挥作用。

如果描述不是用会话英语写的,那么寻找句号的基本策略并不好用;包含对源代码的引用或省略号的描述将破坏模型。您可以切换到搜索". ",但这需要将三个句子作为同一段落的一部分以及第四个句子。您需要升级到正则表达式搜索,该搜索匹配句点后跟任何空白字符(包括换行符)或字符串末尾,以使其真正有效。

答案 3 :(得分:1)

以下是使用CURSOR的代码。 (您可以将计算ShortDescription的部分放入单独的函数中,以使其更具可读性。)

DECLARE @fd NVARCHAR(500);
DECLARE @tmp NVARCHAR(500);
DECLARE @sd NVARCHAR(500);

DECLARE @count INT;
DECLARE @pos INT;

DECLARE update_cursor CURSOR FOR SELECT FullDescription FROM Product;

OPEN update_cursor;

FETCH NEXT FROM update_cursor INTO @fd;
WHILE (@@FETCH_STATUS= 0)
BEGIN
    SET @sd = '';
    IF (LEN(@fd) - LEN(REPLACE(@fd,'.','')) < 3)
    BEGIN
        SET @sd = @fd;
    END;
    ELSE
        BEGIN
            SET @tmp = @fd;
            SET @count = 1;
            SET @pos = 0;
            WHILE (@count < 4)
            BEGIN
                SET @pos = CHARINDEX('.',@tmp, @pos);
                SET @sd = CONCAT(@sd, SUBSTRING(@tmp,1,@pos));
                SET @tmp = SUBSTRING(@tmp,@pos+1,LEN(@tmp));
                SET @count = @count +1;
            END;
        END;

    UPDATE Product
    SET ShortDescription = @sd
    WHERE CURRENT OF update_cursor;

    FETCH NEXT FROM update_cursor INTO @fd;
END;

CLOSE update_cursor;
DEALLOCATE update_cursor;

答案 4 :(得分:0)

这太脏了,但试一试。

private InterstitialAd interstitial;

void showAdFunction()
{

    interstitial = new InterstitialAd(adUnitId);
    AdRequest request = new AdRequest.Builder().Build();
    //....   
    interstitial.LoadAd(request);
}

void OnDestroy()
{
    interstitial.Destroy(); //Destroy
}

答案 5 :(得分:0)

以下用户定义的函数将使用SQL Server的PATINDEX函数来匹配第一次出现的未跟随数字的句点。这将避免句子在句子中某处包含小数值的情况。

CREATE FUNCTION GetFirstThreeSentences 
(
    @fullText NVARCHAR(MAX)
)
RETURNS NVARCHAR(MAX)
AS
BEGIN

    DECLARE @finalPosition  INT = 0;
    DECLARE @patternIndex INT = 0;
    DECLARE @textLenght INT = LEN(@fullText);

    -- Get first sentence.
    DECLARE @currentSentencePosition INT = PATINDEX('%.[^0-9]%', @fullText);
    SET @finalPosition = @currentSentencePosition;
    DECLARE @remainingText NVARCHAR(MAX) = RIGHT(@fullText, @textLenght - @finalPosition);

    -- Get second sentence.
    SET @currentSentencePosition  = PATINDEX('%.[^0-9]%', @remainingText);
    SET @finalPosition = @finalPosition + @currentSentencePosition;
    SET @remainingText  = RIGHT(@fullText, @textLenght - @finalPosition);

    -- Get third sentence.
    SET @currentSentencePosition  = PATINDEX('%.[^0-9]%', @remainingText);
    SET @finalPosition = @finalPosition + @currentSentencePosition;
    SET @remainingText  = RIGHT(@fullText, @textLenght - @finalPosition);

    -- Return first three setences
    RETURN LEFT(@fullText, @finalPosition)

END
GO

现在可以在查询中调用该函数:

UPDATE Product
SET ShortDescription = dbo.GetFirstThreeSentences(FullDescription)
WHERE FullDescription IS NOT NULL;