【问题标题】:Split a string into rows using pure SQLite使用纯 SQLite 将字符串拆分为行
【发布时间】:2016-04-12 02:36:27
【问题描述】:

使用SQLite,我想按以下方式拆分字符串。

输入字符串:

C:\Users\fidel\Desktop\Temp

并让查询返回这些

C:\
C:\Users\
C:\Users\fidel\
C:\Users\fidel\Desktop\
C:\Users\fidel\Desktop\Temp

换句话说,我想将文件路径拆分为其组成路径。有没有办法在纯 SQLite 中做到这一点?

【问题讨论】:

    标签: sql string sqlite split


    【解决方案1】:

    这可以通过递归common table expression

    WITH RECURSIVE split(s, last, rest) AS (
      VALUES('', '', 'C:\Users\fidel\Desktop\Temp')
      UNION ALL
      SELECT s || substr(rest, 1, 1),
             substr(rest, 1, 1),
             substr(rest, 2)
      FROM split
      WHERE rest <> ''
    )
    SELECT s
    FROM split
    WHERE rest = ''
       OR last = '\';
    

    (你没有要求一个合理的方式。)

    【讨论】:

    • 我不太了解使用 CTE(至少以这种方式)。它的可靠性/性能如何?也就是说,它的实现是否足够健壮,可以通过更多的调整最终进入标准的 SQLite 库函数?对我来说,缺少 SPLIT 可能是 Sqlite 中最痛苦的日常缺失功能。
    • 到目前为止的所有答案都是对输入字符串进行硬编码。有人可以展示如何使这些解决方案之一通用,例如,可以在更大的查询中拆分 blah 列?
    • @CraigSilver 那将是一个不同的问题,您必须指定输出的确切外观。
    【解决方案2】:

    递归 CTE:

    WITH RECURSIVE cte(org, part, rest, pos) AS (
      VALUES('C:\Users\fidel\Desktop\Temp', '','C:\Users\fidel\Desktop\Temp'|| '\', 0)
      UNION ALL
      SELECT org,
             SUBSTR(org,1, pos + INSTR(rest, '\')),
             SUBSTR(rest, INSTR(rest, '\')+1),
             pos + INSTR(rest, '\')
      FROM cte
      WHERE INSTR(rest, '\') > 0                         
    )
    SELECT *
    FROM cte
    WHERE pos <> 0
    ORDER BY pos; 
    

    SqlFiddleDemo

    输出:

    ╔═════════════════════════════╗
    ║            part             ║
    ╠═════════════════════════════╣
    ║ C:\                         ║
    ║ C:\Users\                   ║
    ║ C:\Users\fidel\             ║
    ║ C:\Users\fidel\Desktop\     ║
    ║ C:\Users\fidel\Desktop\Temp ║
    ╚═════════════════════════════╝
    

    它是如何工作的:

    org  - original string does not change
    part - simply `LEFT` equivalent of original string taking pos number of chars
    rest - simply `RIGHT` equivalent, rest of org string
    pos  - position of first `\` in the rest 
    

    追踪:

    ╔══════════════════════════════╦══════════════════════════════╦════════════════════════════╦═════╗
    ║             org              ║            part              ║           rest             ║ pos ║
    ╠══════════════════════════════╬══════════════════════════════╬════════════════════════════╬═════╣
    ║ C:\Users\fidel\Desktop\Temp  ║ C:\                          ║ Users\fidel\Desktop\Temp\  ║   3 ║
    ║ C:\Users\fidel\Desktop\Temp  ║ C:\Users\                    ║ fidel\Desktop\Temp\        ║   9 ║
    ║ C:\Users\fidel\Desktop\Temp  ║ C:\Users\fidel\              ║ Desktop\Temp\              ║  15 ║
    ║ C:\Users\fidel\Desktop\Temp  ║ C:\Users\fidel\Desktop\      ║ Temp\                      ║  23 ║
    ║ C:\Users\fidel\Desktop\Temp  ║ C:\Users\fidel\Desktop\Temp  ║                            ║  28 ║
    ╚══════════════════════════════╩══════════════════════════════╩════════════════════════════╩═════╝
    

    【讨论】:

      【解决方案3】:

      如果要逐个搜索值,使用下面的代码:

      WITH RECURSIVE split(content, last, rest) AS (
      VALUES('', '', 'value1§value2§value3§value4§value5§value6§value7')
      UNION ALL
        SELECT 
      
          CASE WHEN last = '§' 
                  THEN
                      substr(rest, 1, 1)
                  ELSE
                      content || substr(rest, 1, 1)
          END,
           substr(rest, 1, 1),
           substr(rest, 2)
        FROM split
        WHERE rest <> ''
      )
      SELECT 
             REPLACE(content, '§','') AS 'ValueSplit'     
      FROM 
             split
      WHERE 
             last = '§' OR rest ='';
      

      结果:

      **ValueSplit**
      
      value1
      value2
      value3
      value4
      value5
      value6
      value7
      

      希望能帮助遇到同样问题的人。

      【讨论】:

        【解决方案4】:

        递归 CTE 有一个更简单的替代方案,它也可以应用于结果集中的多个文件路径(或者通常是您希望通过分隔符“拆分”为多行的任何分隔字符串)。

        SQLite 具有 JSON1 扩展名。它是compatible with SQLite >= 3.9.0 (2015-10-14),但sqlite3 现在几乎总是用它编译(例如 Ubuntu、Debian、官方 Python Docker 映像等,您可以使用PRAGMA compile_optionsthis answer 有更多细节)。

        JSON1 有json_each,这是扩展中的两个表值函数之一:

        遍历作为第一个参数提供的 JSON 值,并为每个元素返回一行。

        因此,如果您可以将字符串转换为 JSON 数组字符串,则此函数将完成剩下的工作。而且不难做到。

        const sql = `
          WITH input(filename) AS (
            VALUES  
              ('/etc/redis/redis.conf'),
              ('/run/redis/redis-server.pid'),
              ('/var/log/redis-server.log')
          ), tmp AS (
            SELECT 
              filename, 
              '["' || replace(filename, '/', '", "') || '"]' as filename_array
            FROM input
          )
          SELECT (
            SELECT group_concat(ip.value, '/') 
            FROM json_each(filename_array) ip 
            WHERE ip.id <= p.id
          ) AS path
          FROM tmp, json_each(filename_array) AS p
          WHERE p.id > 1  -- because the filenames start with the separator
        `
        
        async function run() {
          const wasmUrl = 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.5.0/sql-wasm.wasm'
          const sqljs = await window.initSqlJs({locateFile: file => wasmUrl})
          const db = new sqljs.Database()
          const results = db.exec(sql) 
          ko.applyBindings(results[0])
        }
        run()
        <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.5.0/sql-wasm.min.js"></script>
        <table>
          <thead>
            <tr data-bind="foreach: columns"><th data-bind="text: $data"></th></tr>
          </thead>
          <tbody data-bind="foreach: values">
            <tr data-bind="foreach: $data"><td data-bind="text: $data"></td></tr>
          </tbody>
        </table>

        【讨论】:

          【解决方案5】:

          灵感来自 Lukasz Szozda 的回答:

           WITH RECURSIVE cte("pre","post") AS (   
             VALUES('C:', 'Users\fidel\Desktop\Temp' || '\')
           UNION ALL
             SELECT "pre" || '\' || left("post",     position('\' in "post")-1),
                               substring("post" from position('\' in "post")+1)
             FROM cte   
             WHERE "post" > ''                             
           ) 
           SELECT "pre" FROM cte
          

          (在 PostgreSQL 上测试)

          现在的想法是替换 VALUES 行

          VALUES('C:', 'Users\fidel\Desktop\Temp' || '\')
          

          使用类似的占位符

           VALUES(?, ? || '\')
          

          已在将针对数据库运行上述 SQL 语句的编程语言中预先拆分。

          阅读 SQLite 文档,我看到 substring(... from ...) 必须替换为 substr(..., ...) 并且 position(... in ...) 将替换为 instr(..., ...) 并交换参数。

          对我来说很烦,因为我想要在 PostgreSQL 和 SQLite 上运行的 SQL 代码。

          【讨论】:

            猜你喜欢
            • 2021-11-28
            • 1970-01-01
            • 2016-01-02
            • 2015-12-28
            • 2016-12-31
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多