【问题标题】:How can I parse delimited list in string to rows?如何将字符串中的分隔列表解析为行?
【发布时间】:2020-09-20 09:31:09
【问题描述】:

这是我之前的问题:How can I merge two strings of comma-separated numbers in MySQL?

我尝试使用分隔列表,原因如下:

  1. 数据超过 2000 行。
  2. 我只有一天时间。
  3. 这不是生产级别
  4. 我需要亲手解析数据。

我以为我别无选择。但我发现了这个:SQL split values to multiple rows

所以,我从中得到了一些希望。但是把它应用到我的桌子上是相当困难的。

  1. 我的是不同的形式。我有多个列,我想避免重复行。
  2. 我更喜欢 UPDATE 和 INSERT 而不是 SELECT,因为更改表的格式会更容易管理,原因是:Is storing a delimited list in a database column really that bad?
  3. 我需要找到未列出的号码。例如,如果时间值为 1, 3, 8 然后我需要 2,4,5,6,7,9,10,11,12,13(在 1 和 13 之间)

我的示例表如下所示:

+-----+------+--------+------+-------+-----------+-------------+
| cid | sid  | type   | day  | time  | building  | room_number |
+-----+------+--------+------+-------+-----------+-------------+
|   1 |    1 | daytime   | mon   | 6,7,8 | sky    | 507         |
|   2 |    2 | daytime   | thu   | 2,3,4 | nuri    | 906         |
|   3 |    3 | daytime   | tue   | 6,7,8 | nuri    | 906         |
|   4 |    4 | daytime   | thu   | 6,7   | sky    | 1003        |
|   5 |    5 | daytime   | mon   | 2,3,4 | sky    | 507         |
|   6 |    6 | daytime   | wed   | 6,7,8 | belief    | 1003        |
|   7 |    7 | daytime   | mon   | 2,3,4 | belief    | 905         |
|   8 |    8 | daytime   | fri   | 6,7,8 | truth    | 905         |
|   9 |    9 | daytime   | tue   | 6,7,8 | truth    | 905         |
|  10 |   10 | daytime   | fri   | 2,3,4 | truth    | 905         |
|  11 |   11 | daytime   | wed   | 6,7,8 | truth    | 905         |
|  12 |   12 | daytime   | fri   | 2,3,4 | truth    | 1003        |
|  13 |   13 | daytime   | mon   | 6,7,8 | truth    | 905         |
|  14 |   14 | daytime   | tue   | 2,3,4 | truth    | 905         |
|  15 |   15 | daytime   | tue   | 6,7,8 | sky    | 208         |
|  16 |   16 | daytime   | tue   | 2,3,4 | sky    | 208         |
|  17 |   17 | daytime   | tue   | 2,3,4 | truth    | 1004        |
|  18 |   19 | daytime   | mon   | 2,3,4 | sky    | 208         |
|  19 |   20 | daytime   | thu   | 2,3,4 | truth    | 1003        |
|  20 |   21 | daytime   | wed   | 6,7,8 | sky    | 208         |
|  21 |   22 | night   | tue   | 4,5,6 | nuri    | 405         |
|  22 |   23 | night   | tue   | 1,2,3 | nuri    | 405         |
|  23 |   24 | night   | tue   | 1,2,3 | nuri    | 306         |
|  24 |   25 | night   | thu   | 1,2,3 | nuri    | 205         |
|  25 |   26 | night   | thu   | 4,5,6 | sky    | 306         |
|  26 |   27 | night   | wed   | 1,2,3 | nuri    | 306         |
|  27 |   28 | night   | wed   | 4,5,6 | sky    | 309         |
|  28 |   29 | night   | wed   | 4,5,6 | nuri    | 407         |
|  29 |   30 | night   | tue   | 4,5,6 | nuri    | 306         |
|  30 |   31 | night   | thu   | 1,2,3 | nuri    | 307         |
| 31 |    0 | always   |      |      | sky    | 201         |
| 32 |    0 | always   |      |      | sky    | 202         |
| 33 |    0 | always   |      |      | sky    | 203         |
| 34 |    0 | always   |      |      | sky    | 204         |
| 35 |    0 | always   |      |      | nuri    | 205         |
| 36 |    0 | always   |      |      | nuri    | 206         |
| 37 |    0 | always   |      |      | truth    | 207         |
| ... |    ... | ...   |      |      | ...    | ...         |
| 2000 |    0 | always   |      |      | belief    | 1101         |
+-----+------+--------+------+-------+-----------+-------------+
  1. 507|周一的天空大厦:预定2,3,4,6,7,8。因此,将添加 1,5,9,10,11,12,13。
  2. 906|nuri 大楼在星期四、星期二有时间表。这是不同的一天。因此,它们不会相互影响。所以,周四 1,5,6,7,8,9,10,11,12,13 和周二 1,2,3,4,5,9,10,11,12,13。
  3. 如果有重复行,不影响。

我对更改表的期望是这样的:


+-----+------+--------+------+-------+-----------+-------------+
| cid | sid  | type   | day  | time  | building  | room_number |
+-----+------+--------+------+-------+-----------+-------------+
|   1 |    1 | daytime   | mon   | 1 | sky    | 507         |
|   2 |    1 | daytime   | mon   | 5 | sky    | 507         |
|   3 |    1 | daytime   | mon   | 9 | sky    | 507         |
|   4 |    1 | daytime   | mon   | 10 | sky    | 507         |
|   5 |    1 | daytime   | mon   | 11 | sky    | 507         |
|   6 |    1 | daytime   | mon   | 12 | sky    | 507         |
|   7 |    1 | daytime   | mon   | 13 | sky    | 507         |
|   8 |    2 | daytime   | thu   | 1 | nuri    | 906         |
|   9 |    2 | daytime   | thu   | 5 | nuri    | 906         |
|   10 |    2 | daytime   | thu   | 6 | nuri    | 906         |
|   11 |    2 | daytime   | thu   | 7 | nuri    | 906         |
|   12 |    2 | daytime   | thu   | 8 | nuri    | 906         |
|   13 |    2 | daytime   | thu   | 9 | nuri    | 906         |
|   14 |    2 | daytime   | thu   | 10 | nuri    | 906         |
|   15 |    2 | daytime   | thu   | 11 | nuri    | 906         |
|   16 |    2 | daytime   | thu   | 12 | nuri    | 906         |
|   17 |    2 | daytime   | thu   | 13 | nuri    | 906         |
|   18 |    3 | daytime   | tue   | 1 | nuri    | 906         |
|   19 |    3 | daytime   | tue   | 2 | nuri    | 906         |
|   20 |    3 | daytime   | tue   | 3 | nuri    | 906         |
| ... |    ... | ...   |      |      | ...    | ...         |
| 302 |    0 | always   |      |      | nuri    | 206         |
| 303 |    0 | always   |      |      | truth    | 207         |
| ... |    ... | ...   |      |      | ...    | ...         |
| 4020 |    0 | always   |      |      | belief    | 1101         |
+-----+------+--------+------+-------+-----------+-------------+

我这样做的原因是原始数据是课程的时间表。而且我想找个空闲的时间,这样人们可以在上课的时候使用教室。

【问题讨论】:

  • 你提到的问题是关于 MySQL 的,所以大概这个也是。
  • 您使用的是哪个版本的 MySQL?
  • @Nick 它说mysql Ver 15.1 Distrib 10.0.21-MariaDB, for Linux (x86_64) using readline 5.1

标签: mysql sql string database-design recursive-query


【解决方案1】:

这是一种使用递归查询将字符串拆分为行的方法。然后您可以使用此结果集来识别丢失的记录:为此,您可以cross join将原始表与时间列表一起使用,并使用反left join来提取丢失的记录:

with recursive 
    cte as (
        select cid, sid, type, day, room_number,
            substring(time, 1, locate(',', time) - 1) time,
            substring(concat(time, ','), locate(',', time) + 1) rest
        from mytable
        union all
        select cid, sid, type, day, room_number,
            substring(rest, 1, locate(',', rest) - 1),
            substring(rest, locate(',', rest) + 1)
        from cte
        where  locate(',', rest) > 0
    ),
    all_times as (
        select 1 time 
        union all select time + 1 from all_times where time < 13
    )
select  t.cid, t.sid, t.type, t.day, at.time, t.building, t.room_number
from all_times at
cross join mytable t
left join cte c on c.cid = t.cid and c.time = at.time
where c.cid is null
order by t.cid, at.time

Demo on DB Fiddle

样本数据:

西德 |席位 |类型 |天 |时间 |建设|房间号 --: | --: | :-------- | :-- | :---- | :------- | ----------: 1 | 1 |白天|星期一 | 6,7,8 |天空 | 507 2 | 2 |白天|星期四 | 2,3,4 |努里 | 906

查询结果:

西德 |席位 |类型 |天 |时间 |建设|房间号 --: | --: | :-------- | :-- | ---: | :------- | ----------: 1 | 1 |白天|星期一 | 1 |天空 | 507 1 | 1 |白天|星期一 | 2 |天空 | 507 1 | 1 |白天|星期一 | 3 |天空 | 507 1 | 1 |白天|星期一 | 4 |天空 | 507 1 | 1 |白天|星期一 | 5 |天空 | 507 1 | 1 |白天|星期一 | 9 |天空 | 507 1 | 1 |白天|星期一 | 10 |天空 | 507 1 | 1 |白天|星期一 | 11 |天空 | 507 1 | 1 |白天|星期一 | 12 |天空 | 507 1 | 1 |白天|星期一 | 13 |天空 | 507 2 | 2 |白天|星期四 | 1 |努里 | 906 2 | 2 |白天|星期四 | 5 |努里 | 906 2 | 2 |白天|星期四 | 6 |努里 | 906 2 | 2 |白天|星期四 | 7 |努里 | 906 2 | 2 |白天|星期四 | 8 |努里 | 906 2 | 2 |白天|星期四 | 9 |努里 | 906 2 | 2 |白天|星期四 | 10 |努里 | 906 2 | 2 |白天|星期四 | 11 |努里 | 906 2 | 2 |白天|星期四 | 12 |努里 | 906 2 | 2 |白天|星期四 | 13 |努里 | 906

您可以轻松地将其转换为 insert 查询,只需在其前面加上 insert 指令,例如:

insert into sometable(cid, sid, type, day, time, building, room_number)
with recursive ...
select ...
from ...
where ...

实际上,由于 csv 列表中的值列表是预先定义的,因此无需递归即可很好地完成。您可以在表格中加入天数列表并使用find_in_set() 来识别差距。好处是它可以在不支持递归查询的 MySQL / MariaDB 版本上工作:

select  t.cid, t.sid, t.type, t.day, at.time, t.building, t.room_number
from (
    select 1 time union all select 2 time union all select 3 
    union all select 4 union all select 5 union all select 6 
    union all select 7 union all select 8 union all select 9 
    union all select 10 union all select 11 union all select 12 union all select 13
) at
cross join (
    select distinct cid, sid, type, day, building, room_number 
    from mytable
) t
left join mytable t1 on t1.cid = t.cid and find_in_set(at.time, t1.time)
where t1.cid is null
order by t.cid, at.time

Demo

【讨论】:

  • MariaDB 10.0 不支持 CTE
  • @c-an:我的错。在您提供数据库版本之前,我就开始写答案,并且不回头就发布了。我用早期版本的解决方案更新了我的答案(实际上我觉得更简单)。
  • MariaDB 确实支持 seq_1_to_13 作为 UNION 混乱的简单替代品。
猜你喜欢
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-28
  • 2016-04-17
相关资源
最近更新 更多