Как заменить каждый другой экземпляр определенного символа в строке MySQL?
Как заменить значение в столбце mysql на запрос вроде: Столбец options
и его тип varchar(255)
С
id options
1 A|10|B|20|C|30
2 A|Positive|B|Negative
Для
id options
1 A|10,B|20,C|30
2 A|Positive,B|Negative
Я делаю это по php, как это.
<?php
$str = "A|10|B|20|C|30";
$arr = explode("|",$str);
$newArr = array();
for($i=0;$i<count($arr);$i+=2){
if($arr[$i] && $arr[$i+1]){
$newArr[] = $arr[$i]."|".$arr[$i+1];
}
}
echo "Before:".$str."\n";
echo "After :".implode(",",$newArr);
?>
https://eval.in/841007
Поэтому вместо PHP я хочу сделать это в MySQL.
Ответы
Ответ 1
Вы должны рассмотреть возможность хранения ваших данных в нормализованной схеме. В вашем случае таблица должна выглядеть так:
| id | k | v |
|----|---|----------|
| 1 | A | 10 |
| 1 | B | 20 |
| 1 | C | 30 |
| 2 | A | Positive |
| 2 | B | Negative |
Эта схема более гибкая, и вы увидите, почему.
Итак, как преобразовать данные в новую схему? Вам понадобится вспомогательная таблица, содержащая порядковые номера. Поскольку ваш столбец varchar(255)
, вы можете хранить только 128 значений (+ 127 разделителей). Но позвольте просто создать 1000 номеров. Вы можете использовать любую таблицу с достаточным количеством строк. Но поскольку любой сервер MySQL имеет таблицу information_schema.columns
, я буду использовать его.
drop table if exists helper_sequence;
create table helper_sequence (i int auto_increment primary key)
select null as i
from information_schema.columns c1
join information_schema.columns c2
limit 1000;
Мы будем использовать эти числа как позицию значений в вашей строке, соединяя эти две таблицы.
Чтобы извлечь значение из строки с разделителями, вы можете использовать функцию substring_index()
. Значение в позиции i
будет
substring_index(substring_index(t.options, '|', i ), '|', -1)
В вашей строке есть последовательность ключей, за которыми следуют ее значения. Позиция ключа - нечетное число. Поэтому, если позиция ключа i
, позиция соответствующего значения будет i+1
Чтобы получить число разделителей в строке и ограничить наше соединение, мы можем использовать
char_length(t.options) - char_length(replace(t.options, '|', ''))
Запрос для хранения данных в нормализованной форме:
create table normalized_table
select t.id
, substring_index(substring_index(t.options, '|', i ), '|', -1) as k
, substring_index(substring_index(t.options, '|', i+1), '|', -1) as v
from old_table t
join helper_sequence s
on s.i <= char_length(t.options) - char_length(replace(t.options, '|', ''))
where s.i % 2 = 1
Теперь запустите select * from normalized_table
, и вы получите следующее:
| id | k | v |
|----|---|----------|
| 1 | A | 10 |
| 1 | B | 20 |
| 1 | C | 30 |
| 2 | A | Positive |
| 2 | B | Negative |
Итак, почему этот формат является лучшим выбором? Помимо многих других причин, вы можете легко преобразовать его в свою старую схему с помощью
select id, group_concat(concat(k, '|', v) order by k separator '|') as options
from normalized_table
group by id;
| id | options |
|----|-----------------------|
| 1 | A|10|B|20|C|30 |
| 2 | A|Positive|B|Negative |
или в желаемый формат
select id, group_concat(concat(k, '|', v) order by k separator ',') as options
from normalized_table
group by id;
| id | options |
|----|-----------------------|
| 1 | A|10,B|20,C|30 |
| 2 | A|Positive,B|Negative |
Если вы не заботитесь о нормализации и просто хотите, чтобы эта задача выполнялась, вы можете обновить таблицу с помощью
update old_table o
join (
select id, group_concat(concat(k, '|', v) order by k separator ',') as options
from normalized_table
group by id
) n using (id)
set o.options = n.options;
И отбросьте normalized_table
.
Но тогда вы не сможете использовать простые запросы, например
select *
from normalized_table
where k = 'A'
Смотрите демон на rextester.com
Ответ 2
Не используя хранимые процедуры, я бы сделал это в 2 этапа:
-
Вставьте запятую во второе вхождение символа трубы:
update options set options = insert(options, locate('|', options, locate('|', options) + 1), 1, ',');
-
Вставьте остальные запятые - выполните запрос N раз:
update options set options = insert(options, locate('|', options, locate('|', options, length(options) - locate(',', reverse(options)) + 1) + 1), 1, ',');
где N =
select max(round(((length(options) - length(replace(options, '|', ''))) - 1 ) / 2) - 1) from options;
(или не беспокоиться о подсчете и продолжать выполнять запрос до тех пор, пока он не скажет вам "0 строк затронуты" )
Проверено с помощью этого набора данных:
id options
1 A|10|B|20|C|30
2 A|Positive|B|Negative
3 A|10|B|20|C|30|D|40|E|50|F|60
4 A|Positive|B|Negative|C|Neutral|D|Dunno
приводит к:
id options
1 A|10,B|20,C|30
2 A|Positive,B|Negative
3 A|10,B|20,C|30,D|40,E|50,F|60
4 A|Positive,B|Negative,C|Neutral,D|Dunno
(я объясню позже)
Ответ 3
демонстрация
Демо-версия реестров
объяснение
Это можно было бы решить относительно легко, если бы у MySQL была функция замены регулярных выражений, но, к сожалению, это не так. Поэтому я написал один - см. Этот пост в блоге. Здесь нужна "расширенная версия", позволяющая выполнять рекурсивную замену в найденном совпадении для замены. Тогда можно использовать следующий относительно простой SQL:
SQL (код функции опущен для краткости)
SELECT id,
options AS 'before',
reg_replace(options,
'\\|.*\\|', -- 2 pipe symbols with any text in between
'\\|$', -- Replace the second pipe symbol
',', -- Replace with a comma
FALSE, -- Non-greedy matching
2, -- Min match length = 2 (2 pipe symbols)
0, -- No max match length
0, -- Min sub-match length = 1 (1 pipe symbol)
0 -- Max sub-match length = 1 (1 pipe symbol)
) AS 'after'
FROM tbl;
Ответ 4
Хм, я думаю, вы пытаетесь сделать что-то вроде этого
SELECT GROUP_CONCAT(CONCAT(options,",") SEPARATOR "|") FROM Table.name;
Я кратко объясняю, беру за каждую строку результат, и я конкатенирую "," и я объединяю всю строку с разделителем "|".
Вам нужно будет изменить Table.name с именем вашей таблицы
Если вы хотите объединить еще одно значение, например A, B, C (вы не объяснили, откуда взялось значение ABC, так что скажем ValueWhereABCisComingFrom):
SELECT GROUP_CONCAT(CONCAT(ValueWhereABCisComingFrom,"|",options) SEPARATOR ",") FROM Table.name;
если моя таблица выглядит так:
id | ValueWhereABCisComingFrom | options
0 | A | 10
1 | B | 20
2 | C | 30
У вас есть что-то вроде этого:
A|10,B|20,C|30
РЕДАКТИРОВАТЬ 1
В этом случае нет способа сделать это. В mysql нет функции, например preg_replace. Все, что вы можете сделать, это заменить все "|" как
SELECT Replace(options, '|', ',') AS P
FROM `docs`;
В MariaDB есть такая функция, поэтому вы можете попробовать перейти от одной базы к другой. Но только с MYSQL, никоим образом:/
Ответ 5
Вы можете сделать, создав функцию
CREATE FUNCTION doiterate(str TEXT, i INT, next INT, isp TINYINT(1))
RETURNS TEXT
BEGIN
myloop: LOOP
IF next = 0 THEN
LEAVE myloop;
END IF;
IF isp = TRUE THEN
set str = insert(str, i, 1, ',');
set isp = FALSE;
set i = next;
set next = locate('|', str, i + 1);
ITERATE myloop;
ELSE
set isp = TRUE;
set i = next;
set next = locate('|', str, i + 1);
ITERATE myloop;
END IF;
LEAVE myloop;
END LOOP;
return str;
END;
и называя это так:
SELECT t.`column`,
@loc := locate('|', t.`column`) as position,
@next := locate('|', t.`column`, @loc +1) as next,
@isp := 0 is_pipe,
@r := doiterate(t.column, @loc, @next, @isp) as returnstring
from test t;
Я предполагаю, что вы будете достаточно умны, чтобы
- изменить имя и имя столбца
- вставьте это в запрос на обновление
Вы можете изменить @isp: = на 1, если я получил неправильное изменение трубы/комы (я предполагал, что вторая труба должна быть изменена на кому)