SQL Server:在列中选择多次出现正则表达式匹配的行

时间:2017-10-30 17:24:06

标签: sql sql-server regex

我很习惯使用MySQL,但对SQL Server并不是特别熟悉。运气好,我在这里处理的数据库是在SQL Server 2014上。

我有一个包含列的表,其值都是带有前导,分隔和尾随分号的整数,就像这三个虚构的行一样:

;905;1493;384;13387;29;933;467;28732;
;905;138;3084;1387;290;9353;4767;2732;
;9085;14493;3864;130387;289;933;4767;28732;

我现在要做的是从此列中选择从数字列表中取出多个数字的所有行。所以例如,给定上面的三行,如果我有组905,467,4767,我试图弄清楚如何构造的语句应该返回前两行:第一行包含905和467;第二行包含905和4767.第三行仅包含4767,因此不应返回该行。

As far as I can tell,SQL Server实际上并不直接支持正则表达式(我甚至不知道托管代码是什么),这没有用。即使使用正则表达式,我也不知道从哪里开始。 Oracle似乎有a function that would be very useful,但那是Oracle。

这里大多数类似的问题都涉及找到同一个角色的多个实例(通常是单数)并通过replacing the string to match with nothing and counting the difference in length来解决问题。我认为这在技术上也可以在这里工作,但是如果给出一个由15个数字组成的“过滤器”组,那么SELECT语句会变得荒谬冗长而且错综复杂,完全不可读。另外,我只想匹配整个数字(因此,如果要匹配的数字之一是29,则第29行将匹配第一行,但第二行中的值290不匹配) ,这意味着我必须在REPLACE子句中包含分号,然后在计算长度时对它们进行折扣。一团糟。

我理想的做法是这样的:

SELECT * FROM table WHERE REGEXP_COUNT(column, ';(905|467|4767);') > 1

- 但由于各种原因(最明显的一个原因是Oracle外部REGEXP_COUNT不存在),这显然不会起作用。

是否有一些理智,可管理的方式?

4 个答案:

答案 0 :(得分:2)

你可以做到

SELECT *
FROM   Mess
       CROSS APPLY (SELECT COUNT(*)
                    FROM   (VALUES (905),
                                   (467),
                                   (4767)) V(Num)
                    WHERE  Col LIKE CONCAT('%;', Num, ';%')) ca(count)
WHERE  count > 1 

SQL Fiddle

或者

WITH Nums
     AS (SELECT Num
         FROM   (VALUES (905),
                        (467),
                        (4767)) V(Num))
SELECT Mess.*
FROM   Mess
       CROSS APPLY (VALUES(CAST(CONCAT('<x>', REPLACE(Col, ';', '</x><x>'), '</x>') AS XML))) x(x)
       CROSS APPLY (SELECT COUNT(*)
                    FROM   (SELECT n.value('.', 'int')
                            FROM   x.x.nodes('/x') n(n)
                            WHERE  n.value('.', 'varchar') <> ''
                            INTERSECT
                            SELECT Num
                            FROM   Nums) T(count)
                    HAVING COUNT(*) > 1) ca2(count) 

答案 1 :(得分:1)

你能把你的参数放到一个表中(可能使用一个表值函数接受一个字符串(逗号分隔的整数)作为参数)并使用这样的东西吗?

DECLARE @T table (String varchar(255))
INSERT INTO @T
VALUES
(';905;1493;384;13387;29;933;467;28732;')
, (';905;138;3084;1387;290;9353;4767;2732;')
, (';9085;14493;3864;130387;289;933;4767;28732;')

DECLARE @Arguments table (Arg int)
INSERT INTO @Arguments
VALUES
(905)
, (467)
, (4767)

SELECT String
FROM
    @T
    CROSS JOIN @Arguments
GROUP BY String
HAVING SUM(CASE WHEN PATINDEX('%;' + CAST(Arg AS varchar) + ';%', String) > 0 THEN 1 ELSE 0 END) > 1

使用此函数生成参数的示例:

CREATE FUNCTION GenerateArguments (@Integers varchar(255))
RETURNS @Arguments table (Arg int)
AS

BEGIN

    WITH cte
    AS
    (
        SELECT
            PATINDEX('%,%', @Integers) p
            , LEFT(@Integers, PATINDEX('%,%', @Integers) - 1) n
        UNION ALL
        SELECT
            CASE WHEN PATINDEX('%,%', SUBSTRING(@Integers, p + 1, LEN(@Integers))) + p = p THEN 0 ELSE PATINDEX('%,%', SUBSTRING(@Integers, p + 1, LEN(@Integers))) + p END
            , CASE WHEN PATINDEX('%,%', SUBSTRING(@Integers, p + 1, LEN(@Integers))) = 0 THEN RIGHT(@Integers, PATINDEX('%,%', REVERSE(@Integers)) - 1) ELSE LEFT(SUBSTRING(@Integers, p + 1, LEN(@Integers)), PATINDEX('%,%', SUBSTRING(@Integers, p + 1, LEN(@Integers))) - 1) END
        FROM cte
        WHERE p <> 0
    )

    INSERT INTO @Arguments (Arg)

    SELECT n
    FROM cte

    RETURN

END
GO

DECLARE @T table (String varchar(255))
INSERT INTO @T
VALUES
(';905;1493;384;13387;29;933;467;28732;')
, (';905;138;3084;1387;290;9353;4767;2732;')
, (';9085;14493;3864;130387;289;933;4767;28732;')
;

SELECT String
FROM
    @T
    CROSS JOIN GenerateArguments('905,467,4767')
GROUP BY String
HAVING SUM(CASE WHEN PATINDEX('%;' + CAST(Arg AS varchar) + ';%', String) > 0 THEN 1 ELSE 0 END) > 1

答案 2 :(得分:0)

你可以使用regex和row_number的like函数来确定匹配的数量。

这里我们声明用于测试的列值:

DECLARE @tbl TABLE (
string NVARCHAR(MAX)
)

INSERT @tbl VALUES
(';905;1493;384;13387;29;933;467;28732;'),
(';905;138;3084;1387;290;9353;4767;2732;'),
(';9085;14493;3864;130387;289;933;4767;28732;')

然后我们将您的搜索参数传递给要加入的表变量:

DECLARE @search_tbl TABLE (
search_value INT
)

INSERT @search_tbl VALUES
(905),
(467),
(4767)

最后,我们将表与列一起搜索到搜索表。我们应用row_number函数来确定它匹配的次数。我们从这个子查询中选择row_number = 2,这意味着它至少加入了两次。

SELECT
    string
FROM (
    SELECT
        tbl.string,
        ROW_NUMBER() OVER (PARTITION BY tbl.string ORDER BY tbl.string) AS rn
    FROM @tbl tbl
    JOIN @search_tbl search_tbl ON
        tbl.string LIKE '%;' + CAST(search_tbl.search_value AS NVARCHAR(MAX)) + ';%'
    ) tbl
WHERE rn = 2

答案 3 :(得分:0)

您可以构建一个这样的where子句:

WHERE
case when column like '%;905;%' then 1 else 0 end +
case when column like '%;467;%' then 1 else 0 end +
case when column like '%;4767;%' then 1 else 0 end >= 2

优点是您不需要帮助程序表。我不知道你是如何构建查询的,但是以下内容也可以工作,如果数字在tsql变量中,则非常有用。

 case when column like ('%;' + @n + ';%')  then 1 else 0 end