У меня есть несколько таких строк:
1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor
Мне нужно вернуть все после второго двоеточия (:
). Итак, из приведенной выше строки мне нужно:
NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor
Видел множество примеров, но ни один из них действительно не работает для этой задачи.
select array_to_string((string_to_array('1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor', ':'))[3:], ':');
NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor
Здесь используется string_to_array
:
https://www.postgresql.org/docs/current/functions-string.html
string_to_array (текст строки, текст-разделитель [, текст null_string]) → текст[]
Разбивает строку при появлении разделителя и формирует полученные поля в текстовый массив. Если разделитель равен NULL, каждый символ строки станет отдельным элементом массива. Если разделитель представляет собой пустую строку, строка рассматривается как одно поле. Если указана null_string и она не равна NULL, поля, соответствующие этой строке, заменяются NULL. См. также array_to_string.
string_to_array('xx~~yy~~zz', '~~', 'yy') → {xx,NULL,zz}
Это разбивает строку на составные части и создает из них массив. [3:]
выбирает элементы массива с третьего до конца.
Тогда отсюда:
https://www.postgresql.org/docs/current/functions-array.html
array_to_string (массив любой массив, текст-разделитель [, текст null_string]) → текст
Преобразует каждый элемент массива в его текстовое представление и объединяет элементы, разделенные строкой-разделителем. Если задана null_string и она не равна NULL, то записи массива NULL представлены этой строкой; в противном случае они опускаются. См. также string_to_array.
array_to_string(МАССИВ[1, 2, 3, NULL, 5], ',', '') → 1,2,3,,5
Это восстанавливает строку путем объединения элементов массива с разделителем.
Этот возвращает массив, разделяя содержимое на:
SELECT (regexp_split_to_array('1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor',':'))[3:];
Если вам нужна одна строка:
SELECT array_to_string((regexp_split_to_array('1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor',':'))[3:], ':');
Этот использует регулярное выражение для разделения содержимого, а также возвращает массив, но только один элемент, который легко преобразовать в одну строку:
SELECT (regexp_matches('1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor', '^[^:]*:[^:]*:(.*)', 'g'))[1];
substring()
с регулярным выражением:
SELECT substring(string, '^.+?:.+?:(.*$)');
Регулярное выражение объясняет:
^
... привязка к началу строки (необязательно).
...любой персонаж+?
... 1 и более раз, не жадный:
... буквальный персонаж
(2 раза)(.*$)
... любые символы 0 и более раз, жадный; в скобках
test=> SELECT substring('1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor', '^.+?:.+?:(.*$)');
substring
-----------------------------------------------------------------------------------
NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor
(1 row)
Если производительность имеет значение...
Вышеописанное просто и коротко, поэтому работает прилично. Но регулярные выражения стоят дорого. Даже (дорогостоящее) преобразование в массив и обратно, как показывает Адриан , обычно происходит быстрее.
Это в пару раз быстрее, чем все, что предлагалось до сих пор:
SELECT substring(string, length(split_part(string, ':', 1))
+ length(split_part(string, ':', 2))
+ 3);
Ниже показано использование NULLIF
и REGEXP_REPLACE
с регулярным выражением ^(([^:]*:){2}(.*))|(.*)
для извлечения части строки, которая появляется после секунды :
, или NULL
, если после второй :
нет символов:
WITH
test_cases (id, test_value, expected_result) AS (
VALUES
(1, NULL, NULL),
(2, '', NULL),
(3, '1:', NULL),
(4, '1:2:', NULL),
(5, '1:2: ', ' '),
(6,
'1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor',
'NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor'
)
),
runs AS (
SELECT
tc.id,
tc.test_value,
tc.expected_result,
NULLIF(
REGEXP_REPLACE(tc.test_value, '^(([^:]*:){2}(.*))|(.*)', '\3'),
''
) AS actual_result
FROM
test_cases tc
)
SELECT
runs.id,
runs.expected_result IS NOT DISTINCT FROM runs.actual_result AS passed,
runs.actual_result
FROM
runs
ORDER BY
runs.id;
Следующий скрипт измеряет накладные расходы, связанные с генерацией тестовых строк (тестовое выражение s.string||gs.n::TEXT
) и время выполнения каждого из четырех выражений, возвращающих часть строки, которая появляется после второго :
:
DROP TABLE IF EXISTS test_runs;
CREATE TEMPORARY TABLE test_runs (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
test_case INTEGER,
test_expression TEXT,
execution_time_ms NUMERIC
);
DO language plpgsql $BLOCK$
DECLARE
number_of_iterations CONSTANT INTEGER := 100;
number_of_rows CONSTANT INTEGER := 10000;
test_expressions CONSTANT TEXT[] := ARRAY[
$$s.string||gs.n::TEXT$$,
$$SUBSTRING(s.string||gs.n::TEXT, '^.+?:.+?:(.*$)')$$,
$$ARRAY_TO_STRING((STRING_TO_ARRAY(s.string||gs.n::TEXT, ':'))[3:], ':')$$,
$$ARRAY_TO_STRING((REGEXP_SPLIT_TO_ARRAY(s.string||gs.n::TEXT, ':'))[3:], ':')$$,
$$REGEXP_REPLACE(s.string||gs.n::TEXT, '^(([^:]*:){2}(.*))|(.*)', '\3')$$
];
iteration INTEGER;
results JSON;
test_case INTEGER;
test_run INTEGER := 0;
BEGIN
FOR iteration IN 1 .. number_of_iterations LOOP
FOR test_case IN 1 .. CARDINALITY(test_expressions) LOOP
EXECUTE
FORMAT($$
EXPLAIN (ANALYZE, FORMAT JSON)
SELECT %1$s
FROM
(VALUES ('1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor')) s(string)
CROSS JOIN GENERATE_SERIES(1, $2) gs(n);$$,
test_expressions[test_case])
INTO results
USING test_case, number_of_rows;
INSERT INTO test_runs(test_case, test_expression, execution_time_ms)
VALUES (test_case, test_expressions[test_case], (results -> 0 ->> 'Execution Time')::NUMERIC);
END LOOP;
END LOOP;
END;
$BLOCK$;
SELECT AVG(execution_time_ms)::NUMERIC(18,3) AS mean_execution_time_ms, test_expression
FROM test_runs
GROUP BY test_case, test_expression
ORDER BY mean_execution_time_ms, test_case;
Postgres может оптимизировать стабильные выражения в вашем тесте. Тест производительности с реальной таблицей даст достоверные результаты.
@ErwinBrandstetter, спасибо, что напомнили мне учитывать способность PostgreSQL оптимизировать избыточные вычисления константных выражений. Я соответствующим образом обновил часть своего ответа, посвященную сравнению производительности.
Ответ Эрвина с использованием
SUBSTRING
выполняется за время, сравнимое с использованиемREGEXP_REPLACE
, но имеет более простое регулярное выражение. Замена его регулярного выражения на^.+?:.+?:(.+$)
дает те же результаты, что и мое, но без необходимости вNULLIF
, посколькуSUBSTRING
возвращаетNULL
, если регулярное выражение не соответствует.