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

时间:2017-08-03 08:00:22

标签: php mysql sql string replace

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

5 个答案:

答案 0 :(得分: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 |

那么为什么这种格式是更好的选择呢?除了许多其他原因之外,还有一个原因是您可以使用

轻松将其转换为旧架构
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

答案 1 :(得分:3)

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

  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行受影响”)

  3. 检查这组数据:

    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
    

    (我稍后会提供解释)

答案 2 :(得分:3)

<强>演示

Rextester demo

<强>解释

如果只有MySQL具有正则表达式替换函数但unfortunately it doesn't,则可以相对容易地解决这个问题。所以I wrote one - 请参阅this blog post。 &#34;高级版&#34;这里需要它允许它在找到的替换匹配中执行递归替换。然后可以使用以下相对简单的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;

答案 3 :(得分:2)

嗯,我想你正试图做这样的事情

SELECT GROUP_CONCAT(CONCAT(options,",") SEPARATOR "|") FROM Table.name;

我简要解释一下,我对结果采取了每一行,并且我连接了#34;,&#34;然后我将所有行与分隔符&#34; |&#34;连接起来。 您必须使用表的名称更改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这样的函数。你所能做的就是更换所有&#34; |&#34;像

SELECT  Replace(options, '|', ',') AS P
FROM `docs`;

在MariaDB中,有这样一个功能,所以你可以尝试从一个基地传递到另一个基地。但是只使用MYSQL,没办法:/

答案 4 :(得分: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;

我认为你会聪明到

  • 更改表名称&amp;列名称
  • 将此插入更新请求

如果管道/昏迷变化错误,我可以将@isp:=更改为1(我假设第二个管道应该更改为昏迷)