【问题标题】:Mysql Split ordered list into multiple rowsMysql 将有序列表拆分为多行
【发布时间】:2018-05-21 22:11:01
【问题描述】:

我有一个带有“说明”字段(中文本)的数据库,其中每个条目都包含一段有序列表形式的说明。 目前,在查看时,每个列表项通过 PHP nl2br 函数调用显示在一个新行上。

示例条目:

  1. 将面粉、发酵粉和一小撮盐放入碗中并混合。搁置。 2. 将黄油和糖放入搅拌碗中,然后 用桨高速打发奶油,直到变淡并呈奶油状 附件。 3. 将搅拌机调至中速,逐渐加入 鸡蛋直到充分乳化。 4. 加入面粉混合物,搅拌至 它聚在一起形成面团。从搅拌机中取出面团 碗,放在两张烤羊皮纸之间。 5.擀面团 至 5mm 的厚度。 6. 预热时放入冰箱 烤箱至 170°C/340°F。 7.剥去羊皮纸并烘烤面团 直到金黄色。 8. 冷却,然后储存在密封容器中,直到 需要。

如您所见,文本中还有数字。

我想将此单个字段拆分为一个单独的表,其中每个单独的指令列表项都有自己的行和将其链接到当前项的 id。

有没有办法可以用 MySQL 拆分现有字段?可以用“Number.”作为分隔符吗?

【问题讨论】:

  • 考虑规范化你的架构。
  • 对数字进行分割并不够好 - 像 between 2 sheets170°C/340°F 这样的文本会破坏这种方法。你将需要一些更高级的东西,比如 NLP / 上下文感知。 (或雇人手动分解步骤)
  • 你可以尝试建立一个模式,比如数字(可能是多位数字)、点和之后的空格。根据您的数据,可能需要一些手动修复。
  • 这种解析通常最好通过将数据选择到过程语言/客户端中,在客户端解析它,然后重新插入处理后的结果(并删除源)来完成。 可以在存储过程中完成,但非 SQL 语言通常对这类事情有更好的库支持。 (MySQL 甚至没有内置对“分隔符分割”的支持,更不用说“某个数字”了。)

标签: mysql split delimiter preg-split


【解决方案1】:

您可以使用存储过程来做到这一点。这个假设步骤从 1 开始,按顺序编号,并且所有步骤看起来都像步骤编号,后跟一个句点、一个空格,然后是步骤文本(这就是您的示例数据的样子)。它应该相当容易修改以使用略有不同的格式。我已使该过程生成步骤的结果集,但是您也可以将 SELECT 更改为 INSERT 以将步骤复制到新表中。

DELIMITER //
DROP PROCEDURE IF EXISTS split_recipe //
CREATE PROCEDURE split_recipe(IN recipe VARCHAR(2048))
BEGIN
  DECLARE step INT DEFAULT 1;
  DECLARE next_step INT DEFAULT step+1;
  DECLARE this_step VARCHAR(256);
  WHILE recipe RLIKE CONCAT('^[[:blank:]]*', step, '[[.period.]]') DO
    -- is there a next step?
    IF recipe RLIKE CONCAT('^[[:blank:]]*', step, '[[.period.]] .*', next_step, '[[.period.]]') THEN
      SET this_step = SUBSTRING_INDEX(SUBSTRING_INDEX(recipe, CONCAT(next_step, '. '), 1), CONCAT(step, '. '), -1);
    ELSE
      SET this_step = SUBSTRING_INDEX(recipe, CONCAT(step, '. '), -1);
    END IF;
    -- output this step
    SELECT step, this_step;
    -- remove this step from the recipe
    SET recipe = SUBSTRING_INDEX(recipe, CONCAT(step, '. ', this_step), -1);
    SET step = next_step;
    SET next_step = step + 1;
  END WHILE;
END //

使用您的示例数据:

CALL split_recipe('1. Place the flour, baking powder and a pinch of salt in a bowl and combine. Set aside. 2. Place the butter and sugar in a mixer bowl and cream at high speed until light and creamy, using the paddle attachment. 3. Reduce the mixer to a moderate speed and gradually add the egg until well emulsified. 4. Add the flour mixture and mix until it comes together to form a dough. Remove the dough from the mixing bowl and place between 2 sheets of baking parchment. 5. Roll the dough to a thickness of 5mm. 6. Place in the freezer while preheating the oven to 170°C/340°F. 7. Peel off the parchment and bake the dough until golden. 8. Allow to cool, then store in a sealed container until needed.')

输出:

step    this_step   
1       Place the flour, baking powder and a pinch of salt in a bowl and combine. Set aside. 
2       Place the butter and sugar in a mixer bowl and cream at high speed until light and creamy, using the paddle attachment. 
3       Reduce the mixer to a moderate speed and gradually add the egg until well emulsified. 
4       Add the flour mixture and mix until it comes together to form a dough. Remove the dough from the mixing bowl and place between 2 sheets of baking parchment. 
5       Roll the dough to a thickness of 5mm. 
6       Place in the freezer while preheating the oven to 170°C/340°F. 
7       Peel off the parchment and bake the dough until golden. 
8       Allow to cool, then store in a sealed container until needed.

请注意,此过程会生成多个单行结果集(每个步骤一个 - 为了便于阅读上面,我将它们组合在一起)。如果只需要一个结果集,则需要修改过程以将步骤存储到临时表中,然后最后从临时表中获取所有数据。或者,可以在应用程序中使用如下代码(用于 PHP/PDO/MySQL):

$result = $link->query("call split_recipe('1. Place the flour...')");
do {
    if ($result->columnCount()) {
        $row = $result->fetch();
        print_r($row);
    }
} while ($result->nextRowset());

这是该过程的修改版本,它将把配方从表 recipes (RecipeID INT, Instructions VARCHAR(2048)) 拆分成一个新表 new_recipes (RecipeID INT, step_num INT, Instruction VARCHAR(256))

DELIMITER //
DROP PROCEDURE IF EXISTS split_recipes //
CREATE PROCEDURE split_recipes()
BEGIN
  DECLARE rid INT;
  DECLARE recipe VARCHAR(2048);
  DECLARE step INT;
  DECLARE next_step INT;
  DECLARE this_step VARCHAR(256);
  DECLARE finished INT DEFAULT 0;
  DECLARE recipe_cursor CURSOR FOR SELECT RecipeID, Instructions FROM recipes;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
  DROP TABLE IF EXISTS new_recipes;
  CREATE TABLE new_recipes (RecipeID INT, step_num INT, Instruction VARCHAR(256));
  OPEN recipe_cursor;
  recipe_loop: LOOP
    FETCH recipe_cursor INTO rid, recipe;
    IF finished = 1 THEN
      LEAVE recipe_loop;
    END IF;
    SET step = 1;
    SET next_step = 2;
    WHILE recipe RLIKE CONCAT('^[[:blank:]]*', step, '[[.period.]]') DO
      -- is there a next step?
      IF recipe RLIKE CONCAT('^[[:blank:]]*', step, '[[.period.]] .*', next_step, '[[.period.]]') THEN
        SET this_step = SUBSTRING_INDEX(SUBSTRING_INDEX(recipe, CONCAT(next_step, '. '), 1), CONCAT(step, '. '), -1);
      ELSE
        SET this_step = SUBSTRING_INDEX(recipe, CONCAT(step, '. '), -1);
      END IF;
      -- insert this step into the new table
      INSERT INTO new_recipes VALUES (rid, step, this_step);
      -- remove this step from the recipe
      SET recipe = SUBSTRING_INDEX(recipe, CONCAT(step, '. ', this_step), -1);
      SET step = next_step;
      SET next_step = step + 1;
    END WHILE;
  END LOOP;
END //

【讨论】:

  • 这会产生一个 8 行的结果集,还是 8 个单行结果?
  • 它产生 8 个单行结果。如果需要单个结果集,则需要将数据放入一个临时表中以在最后选择。
  • 尼克看起来是完美的解决方案。我以为我将不得不手动编辑这么多记录(我知道无论发生什么我都必须适度/检查它们)。我正在努力实现它。我想出了如何调用存储过程,但是将它应用到我的数据库给我带来了麻烦。鉴于我有表 recipes 与字段 RecipeIDInstructions,我如何修改您必须将指令字段拆分到新表的内容,同时将相关的 RecipeID 附加到新的单个指令行。跨度>
  • 嗨@HowardHosk 我已经在答案中添加了第二个程序,它应该可以满足您的需求。
猜你喜欢
  • 2021-03-25
  • 2017-05-20
  • 2021-12-19
  • 2021-12-31
  • 2022-12-20
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多