【问题标题】:How to replace every other instance of a particular character in a MySQL string?如何替换 MySQL 字符串中特定字符的所有其他实例?
【发布时间】:2018-01-10 16:56:00
【问题描述】:

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

所以我想在 MySQL 中执行此操作,而不是 PHP。

【问题讨论】:

  • 所以你想在 MySQL 中而不是 PHP 中执行此操作?
  • 是的,你明白了,我正在复制你在我的问题中的评论
  • 请向我们展示架构。
  • 列是options,其类型为varchar(255)
  • 好的,让我重新措辞。 A 来自哪里? 10 来自哪里?它们在不同的列中吗?您已经使用过什么查询?

标签: php mysql sql string replace


【解决方案1】:

演示

Rextester demo

说明

如果只有 MySQL 有正则表达式替换功能但 unfortunately it doesn't,这个问题可以相对容易地解决。所以I wrote one - 见this blog post。这里需要“高级版本”以允许它在找到的匹配项中执行递归替换以进行替换。那么就可以使用如下比较简单的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】:

    您应该考虑将数据存储在规范化架构中。在您的情况下,表格应如下所示:

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

    在您的字符串中,您有一系列键,后跟它的值。键的位置是奇数。所以如果key的位置是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'
    

    demo at rextester.com

    【讨论】:

      【解决方案3】:

      你可以通过创建一个函数来做到这一点

      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(我假设第二个管道应该更改为昏迷)

      【讨论】:

        【解决方案4】:

        不使用存储过程,我会分两步完成:

        1. 在管道字符的第二次出现处插入逗号:

          update options set options = insert(options, locate('|', options, locate('|', options) + 1), 1, ',');
          
        2. 插入剩余的逗号 - 执行 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
        

        (我稍后会提供解释)

        【讨论】:

          【解决方案5】:

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

          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 没有办法做到这一点。
          猜你喜欢
          • 2021-12-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-16
          • 1970-01-01
          • 2018-03-04
          • 2017-09-04
          相关资源
          最近更新 更多