【发布时间】: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 中做到这一点?
【问题讨论】:
使用SQLite,我想按以下方式拆分字符串。
输入字符串:
C:\Users\fidel\Desktop\Temp
并让查询返回这些行:
C:\
C:\Users\
C:\Users\fidel\
C:\Users\fidel\Desktop\
C:\Users\fidel\Desktop\Temp
换句话说,我想将文件路径拆分为其组成路径。有没有办法在纯 SQLite 中做到这一点?
【问题讨论】:
这可以通过递归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 = '\';
(你没有要求一个合理的方式。)
【讨论】:
blah 列?
递归 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;
输出:
╔═════════════════════════════╗
║ 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 ║
╚══════════════════════════════╩══════════════════════════════╩════════════════════════════╩═════╝
【讨论】:
如果要逐个搜索值,使用下面的代码:
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
希望能帮助遇到同样问题的人。
【讨论】:
递归 CTE 有一个更简单的替代方案,它也可以应用于结果集中的多个文件路径(或者通常是您希望通过分隔符“拆分”为多行的任何分隔字符串)。
SQLite 具有 JSON1 扩展名。它是compatible with SQLite >= 3.9.0 (2015-10-14),但sqlite3 现在几乎总是用它编译(例如 Ubuntu、Debian、官方 Python Docker 映像等,您可以使用PRAGMA compile_options 和 this 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>
【讨论】:
灵感来自 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 代码。
【讨论】: