Извлечь текст после N-го появления

У меня есть несколько таких строк:

1:723:NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor

Мне нужно вернуть все после второго двоеточия (:). Итак, из приведенной выше строки мне нужно:

NVDA:NSDQ::Z4189274321:Snapshot Report:10:COMPUTER & TECHNOLOGY:241:Semiconductor

Видел множество примеров, но ни один из них действительно не работает для этой задачи.

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
0
52
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий
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;
среднее_исполнение_время_мс test_expression 3.486 s.string||gs.n::TEXT 24.911 ARRAY_TO_STRING((STRING_TO_ARRAY(s.string||gs.n::TEXT, ':'))[3:], ':') 41.494 ПОДСТРОКА(s.string||gs.n::TEXT, '^.+?:.+?:(.*$)') 61,743 REGEXP_REPLACE(s.string||gs.n::TEXT, '^(([^:]:){2}(.))|(.*)', '\3') 87.302 ARRAY_TO_STRING((REGEXP_SPLIT_TO_ARRAY(s.string||gs.n::TEXT, ':'))[3:], ':')

Ответ Эрвина с использованием SUBSTRING выполняется за время, сравнимое с использованием REGEXP_REPLACE, но имеет более простое регулярное выражение. Замена его регулярного выражения на ^.+?:.+?:(.+$) дает те же результаты, что и мое, но без необходимости в NULLIF, поскольку SUBSTRING возвращает NULL, если регулярное выражение не соответствует.

JohnH 27.08.2024 21:12

Postgres может оптимизировать стабильные выражения в вашем тесте. Тест производительности с реальной таблицей даст достоверные результаты.

Erwin Brandstetter 27.08.2024 21:29

@ErwinBrandstetter, спасибо, что напомнили мне учитывать способность PostgreSQL оптимизировать избыточные вычисления константных выражений. Я соответствующим образом обновил часть своего ответа, посвященную сравнению производительности.

JohnH 28.08.2024 08:05

Другие вопросы по теме