SQL: поиск/замена, но только в первый раз, когда значение отображается в записи

У меня есть html-содержимое в столбце post_content.

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

Следующий запрос, очевидно, заменит все экземпляры A на B

UPDATE wp_posts SET post_content = REPLACE (post_content, 'A', 'B');

Ответ 1

Это действительно должно быть то, что вы хотите в MySQL:

UPDATE wp_post
SET post_content = CONCAT(REPLACE(LEFT(post_content, INSTR(post_content, 'A')), 'A', 'B'), SUBSTRING(post_content, INSTR(post_content, 'A') + 1));

Это немного сложнее, чем мой предыдущий ответ. Вам нужно найти первый экземпляр "A" (используя функцию INSTR), затем использовать LEFT в сочетании с REPLACE для замены именно этого экземпляра, чем использовать SUBSTRING и INSTR для найдите тот же "A", который вы заменяете, и CONCAT с предыдущей строкой.

Смотрите мой тест ниже:

SET @string = 'this is A string with A replace and An Answer';
SELECT @string as actual_string
, CONCAT(REPLACE(LEFT(@string, INSTR(@string, 'A')), 'A', 'B'), SUBSTRING(@string, INSTR(@string, 'A') + 1)) as new_string;

Выдает:

actual_string                                  new_string
---------------------------------------------  ---------------------------------------------
this is A string with A replace and An Answer  this is B string with A replace and An Answer

Ответ 2

В качестве альтернативы вы можете использовать функции LOCATE(), INSERT() и CHAR_LENGTH() следующим образом:

INSERT(originalvalue, LOCATE('A', originalvalue), CHAR_LENGTH('A'), 'B')

Полный запрос:

UPDATE wp_posts
SET post_content = INSERT(originalvalue, LOCATE('A', originalvalue), CHAR_LENGTH('A'), 'B');

Ответ 3

Если вы используете Oracle DB, вы должны написать что-то вроде:

UPDATE wp_posts SET post_content = regexp_replace(post_content,'A','B',1,1)

Смотрите здесь для получения дополнительной информации: http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions130.htm

Примечание: вам действительно стоит позаботиться о post_content в отношении проблемы безопасности, поскольку он кажется пользователем.

Ответ 4

Я сделал следующую маленькую функцию и получил ее:

CREATE DEFINER='virtueyes_adm1'@'%' FUNCTION 'replace_first'(
    'p_text' TEXT,
    'p_old_text' TEXT,
    'p_new_text' TEXT

)
RETURNS text CHARSET latin1
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'troca a primeira ocorrencia apenas no texto'
BEGIN
    SET @str = p_text;
    SET @STR2 = p_old_text;
    SET @STR3 = p_new_text;
    SET @retorno = '';

    SELECT CONCAT(SUBSTRING(@STR, 1 , (INSTR(@STR, @STR2)-1 ))
                ,@str3
                  ,SUBSTRING(@STR, (INSTR(@str, @str2)-1 )+LENGTH(@str2)+1 , LENGTH(@STR)))
    INTO @retorno;

    RETURN @retorno;    
END

Ответ 5

Решение Greg Reda не работало для меня на строках длиной более 1 символа из-за того, как был написан REPLACE() (заменяя только первый символ строки, которую нужно заменить). Вот решение, которое, по моему мнению, более полно и охватывает все варианты использования проблемы, если определено как "Как заменить первое вхождение" String A "на" String B "в" String C "?

CONCAT(LEFT(buycraft, INSTR(buycraft, 'blah') - 1), '', SUBSTRING(buycraft FROM INSTR(buycraft, 'blah') + CHAR_LENGTH('blah')))

Это предполагает, что вы уверены, что запись УЖЕ СОДЕРЖИТ СТРОНУЮ ЗАМЕНУ! Если вы попробуете заменить "собаку" на "кошку" в строке "puppy", она даст вам "per", что не то, что вы хотите. Вот запрос, который обрабатывает это, сначала проверяя, существует ли строка, подлежащая замене, в полной строке:

IF(INSTR(buycraft, 'blah') <> 0, CONCAT(LEFT(buycraft, INSTR(buycraft, 'blah') - 1), '', SUBSTRING(buycraft FROM INSTR(buycraft, 'blah') + CHAR_LENGTH('blah'))), buycraft)

Конкретный вариант использования здесь заменяет первый экземпляр 'blah' внутри столбца buycraft пустой строкой ''. Я думаю, что это довольно интуитивное и естественное решение:

  • Найдите индекс первого вхождения строки, которая должна быть заменена.
  • Получить все слева от него, не считая самого индекса (таким образом, "-1" ).
  • Конкатенация того, что вы заменяете исходную строку.
  • Вычислить конечный индекс части строки, которая заменяется. Это легко сделать, если снова найти индекс первого вхождения и добавить длину замененной строки. Это даст вам индекс первого char после исходной строки
  • Объединить подстроку, начинающуюся с конечного индекса строки

Пример прокрутки замены "pupper" в "lil_puppers_yay" на "dog":

  • Индекс "щенка" равен 5.
  • Получить слева от 5-1 = 4. Таким образом, индексы 1-4, которые являются "lil _"
  • Объединить 'собаку' для 'lil_dog'
  • Вычислить конечный индекс. Начальный индекс равен 5, а 5 + длина "щенка" = 11. Обратите внимание, что индекс 11 относится к 's'.
  • Объедините подстроку, начинающуюся с конечного индекса, который является 's_yay', чтобы получить 'lil_dogs_yay'.

Все сделано!

Примечание. SQL имеет 1-индексированную строку (как новичок SQL, я не знал этого, прежде чем я решил эту проблему). Кроме того, SQL LEFT и SUBSTRING, похоже, работают с недопустимыми индексами как идеальный способ (корректируя его либо в начале, либо в конце строки), что очень удобно для начинающего SQLer, такого как я: P

Другое примечание: Я - новичок в SQL, и это самый сложный запрос, который я когда-либо писал, поэтому могут быть некоторые неэффективности. Он выполняет работу точно, хотя.

Ответ 6

Это проще

UPDATE table_name SET column_name = CONCAT('A',SUBSTRING(column_name, INSTR(column_name, 'B') + LENGTH('A')));

Ответ 7

Прошли годы с тех пор, как был задан этот вопрос, и MySQL 8 представил REGEX_REPLACE:

REGEXP_REPLACE (expr, pat, repl [, pos [, вхождение [, match_type]]])

Заменяет вхождения в строке expr, которые соответствуют регулярному выражению, указанному в шаблоне pat, на строку замены repl и возвращает результирующую строку. Если expr, pat или repl имеют значение NULL, возвращаемое значение равно NULL.

REGEXP_REPLACE() принимает следующие необязательные аргументы:

pos: позиция в expr, с которой начинается поиск. Если опущено, по умолчанию 1.
вхождение: какое вхождение совпадения заменить. Если опущено, по умолчанию 0 (что означает "заменить все вхождения").
match_type: строка, которая указывает, как выполнить сопоставление. Значение как описано для REGEXP_LIKE().

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

UPDATE wp_posts SET post_content = REGEXP_REPLACE (post_content, 'A', 'B', 1, 1);

К сожалению для тех из нас, кто работает на MariaDB, в его варианте REGEXP_REPLACE отсутствует параметр вхождения. Вот версия решения Andriy M с поддержкой регулярных выражений, которую удобно хранить в виде функции многократного использования, как было предложено Лучано Сейбелем:

DELIMITER //
DROP FUNCTION IF EXISTS replace_first //
CREATE FUNCTION 'replace_first'(
    'i' TEXT,
    's' TEXT,
    'r' TEXT
)
RETURNS text CHARSET utf8mb4
BEGIN
    SELECT REGEXP_INSTR(i, s) INTO @pos;
    IF @pos = 0 THEN RETURN i; END IF;
    RETURN INSERT(i, @pos, CHAR_LENGTH(REGEXP_SUBSTR(i, s)), r);
END;
//
DELIMITER ;

Ответ 8

Чтобы сохранить образец gjreda немного проще, используйте это:

UPDATE wp_post
    SET post_content =
            CONCAT(
                REPLACE(LEFT(post_content, 1), 'A', 'B'),
                SUBSTRING(post_content, 2)
            ) 
    WHERE post_content LIKE 'A%';