如何在MySQL字符串中替换特定字符的每个其他实例?

16

如何通过查询替换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);
?>

所以,我想用MySQL而不是PHP来实现这个。

https://eval.in/841007


1
所以你想用MySQL来完成这个,而不是用PHP? - Milan Chheda
请展示给我们模式。 - Milan Chheda
所以你想将一个有 6 列的表格变成一个有 4 列的表格? - RiggsFolly
1
好的,让我重新表述一下。A 是从哪里来的?10 又是从哪里来的?它们是否在不同的列中?你已经使用了什么查询? - Milan Chheda
@MilanChheda:我更新了我的问题。 - GRESPL Nagpur
显示剩余4条评论
5个回答

7

您应该考虑将数据存储在规范化的模式中。在您的情况下,表应该如下:

| 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 |

所以,为什么使用这种格式是更好的选择?除了其他许多原因外,一个原因是你可以轻松地将其转换为旧架构,并且保留HTML标记。
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上查看演示(点击此处)


3

不使用存储过程,我会分两步来做:

  1. Insert the comma at the second occurrence of the pipe character:

    update options set options = insert(options, locate('|', options, locate('|', options) + 1), 1, ',');
    
  2. Insert the remaining commas - execute the query N times:

    update options set options = insert(options, locate('|', options, locate('|', options, length(options) - locate(',', reverse(options)) + 1) + 1), 1, ',');
    

    where N =

    select max(round(((length(options) - length(replace(options, '|', ''))) - 1 ) / 2) - 1) from options;
    

    (or don't bother with counting and continue to execute the query as long as it doesn't tell you "0 rows affected")

使用这组数据进行了检查:

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

演示

Rextester演示

解释

如果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;

2

嗯,我认为你正在尝试做类似于这样的事情

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中,没有这种方法 :/

我已经编辑了我的答案,但很抱歉要说,在MySQL中只有这种方法是不可能的。 - Latsuj

2
你可以通过创建一个函数来实现。
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(我假设第二个管道符应更改为逗号)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接