【问题标题】:SQL patindex equivalent in PostgreSQLPostgreSQL 中等效的 SQL patindex
【发布时间】:2017-05-11 11:51:33
【问题描述】:

我需要相当于 SQL 函数 patindex 的 Postgres

【问题讨论】:

  • Postgres 提供完整的正则表达式支持。它比 SQL Server 的patindex() 更强大、更标准。
  • 没有直接等价于patindex(虽然可以很容易地写出来)。但是,如果您向我们展示原始查询,也许 Postgres 有更好的选择(正如 Gordon 所说:Postgres 的正则表达式支持比 SQL Server 的要复杂得多)
  • 我想要一个具有 SQL“Patindex”函数的名称和功能的 Postgres 函数。我正在将 SQL 查询转换为等效的 Postgres,所以如果我在 SQl 中遇到一个函数作为 Patindex,我希望 Postgres 中也可以使用相同的函数

标签: sql postgresql patindex


【解决方案1】:

没有完全等同于 SQL Server 的 PATINDEX 函数。您可以根据需要使用其他字符串函数。这是文档:https://www.postgresql.org/docs/current/static/functions-string.html

但如果你需要完全相同的功能,你可以写一个“包装器”,如下所示:

CREATE OR REPLACE FUNCTION "patindex"( "pattern" VARCHAR, "expression" VARCHAR ) RETURNS INT AS $BODY$
SELECT
    COALESCE(
        STRPOS(
             $2
            ,(
                SELECT
                    ( REGEXP_MATCHES(
                        $2
                        ,'(' || REPLACE( REPLACE( TRIM( $1, '%' ), '%', '.*?' ), '_', '.' ) || ')'
                        ,'i'
                    ) )[ 1 ]
                LIMIT 1
            )
        )
        ,0
    )
;
$BODY$ LANGUAGE 'sql' IMMUTABLE;

例子:

SELECT patindex( '%e_t%', 'Test String' );

2

SELECT patindex( '%S_r%', 'Test String' );

6

SELECT patindex( '%x%', 'Test String' );

0

【讨论】:

  • 不完全正确。在 MS SQL 中 patindex('t%', 'Test string') = 1(不区分大小写),而在 PostgreSQL 中为 4,因为使用的 REGEXP_MATCHES() 区分大小写。将 LOWER() 添加到对我有用的所有参数中。
  • 它在 PG 和 SQL Server 中的相同数据上工作不正确,REGEXP_MATCHES 使用正则表达式,但 patindex 使用 LIKE 模式
  • @Евгений Ноздрев 感谢您指出缺失的部分。我没有注意到问题。我们可以使用 'i' 标志使正则表达式功能不区分大小写。
  • @Oleg 我们在这里采用 LIKE 模式并将它们转换为 REGEXP 模式。所以它有效。如果您可以提供有问题的用例,我会很高兴检查问题
  • @EvgenyNozdrev 在 MSSQL 中,区分大小写是通过选择字符编码来调节的 - CI(不区分大小写)或 CS(区分大小写)。看看:dbfiddle.uk/…。所以我认为原始答案对于 postgresql 是绝对正确的
【解决方案2】:

PATINDEX 和 POSIX 正则表达式不完全兼容。为了我的目的,我完成了这样的版本:

CREATE OR REPLACE FUNCTION _patexpr2regexp
    (pattern TEXT) RETURNS TEXT
AS
$$
DECLARE
    buffer         TEXT = '';
    matches        TEXT[];
    match_part     TEXT;
    pattern_part   TEXT;
    match_position INT;
BEGIN
    FOR matches IN SELECT regexp_matches(pattern, '(_|\[%\]|%|.)', 'g')
        LOOP
            -- parsing
            match_part := matches[1];
            match_position := position(match_part IN pattern);
            pattern_part := substring(pattern, 1, match_position + LENGTH(match_part) - 1);
            pattern := substring(pattern, LENGTH(pattern_part) + 1);

            -- replacements
            IF match_part = '%'
            THEN
                -- lookaround regex pattern
                pattern_part = replace(pattern_part, '%', '(?=.*)');
            END IF;

            IF match_part = '[%]'
            THEN
                -- % escape
                pattern_part = replace(pattern_part, '[%]', '%');
            END IF;

            IF match_part = '_'
            THEN
                -- MSSQL _ means anysymbol (dot in regexp)
                pattern_part = replace(pattern_part, '_', '.');
            END IF;

            IF match_part = '.'
            THEN
                -- actaul dot symbol should be escaped
                pattern_part = replace(pattern_part, '.', '\.');
            END IF;

            buffer := buffer || pattern_part;
            RAISE NOTICE 'matches: % -> % -> % -> % | %', matches, match_part, pattern_part, pattern, buffer;
        END LOOP;

    RAISE NOTICE 'result buffer: %', buffer;
    RETURN buffer;
END;
$$ LANGUAGE plpgsql STRICT
                      IMMUTABLE
                      PARALLEL SAFE;


CREATE OR REPLACE FUNCTION "PATINDEX"
    (pattern VARCHAR,
     expression VARCHAR,
     flags VARCHAR = 'i' -- default behaviour is case insensitive
     ) RETURNS INT
AS
$$
WITH cte AS (
    SELECT (
        REGEXP_MATCH(
            expression,
            _patexpr2regexp(pattern),
            flags)
        )[1] AS m
)
SELECT COALESCE(position(m IN expression), 0)
FROM cte;
$$ LANGUAGE sql STRICT
                  IMMUTABLE
                  PARALLEL SAFE;

-- checks
DO
$$
    BEGIN
        ASSERT "PATINDEX"('%.%', 'file.bat ') = 5;
        ASSERT "PATINDEX"('%.%', 'file.bat.as1   ') = 5;
        ASSERT "PATINDEX"('%.%', 'fileas') = 0;
        ASSERT "PATINDEX"(NULL, 'abc') IS NULL;
        ASSERT "PATINDEX"('abc', NULL) IS NULL;
        ASSERT "PATINDEX"('abc', 'abc') = 1;
        ASSERT "PATINDEX"('abc', 'cba   abc') = 7;
        -- TODO: not recognize properly
        -- ASSERT "PATINDEX"( '%_1_%[0-9][%]', '123 21234%') = 5;
    END;
$$;

【讨论】:

    猜你喜欢
    • 2022-10-24
    • 1970-01-01
    • 2023-01-08
    • 2018-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多