SQLite substr 负长度:往前取两个字符,为什么结果没有倒过来

前天 3阅读

从固定后缀前取出四位编号,写出负长度以后,结果顺序却没有反转。有时甚至少取一个字符。这里容易把三个概念混在一起:起点从哪边数、选择起点哪一边,以及最终文本按什么顺序返回。

负长度改变取值方向,不改变排列顺序

SQLite 的 substr 使用一基位置,正起点从左边数,负起点从右边数。第三个参数为负时,返回起点字符之前、最多相应数量的字符,并不包含定位到的字符,也不会倒排所选文本。起点的正负与长度的正负是两个独立选择。

例如 abcdef 的第四个字符是 d。从四开始取正二得到 de;从四开始取负二得到 bc。若起点为负二,定位到从右数第二个字符 e,再取负二,就得到它前面的 cd。将字符先写成一排,边界会比凭“倒数”二字猜测清楚。

下面用 Python 标准库创建内存数据库,直接绑定参数执行表达式。保存为 demo.py,用 python3 demo.py 运行。每一组都检查实际字符串,最后增加固定后缀的例子,观察是否满足提取编号的前提。

SQLite substr 负长度:往前取两个字符,为什么结果没有倒过来

配图为 AI 生成的概念插图,用抽象物件说明本文关系,并非真实软件截图或运行输出。

import sqlite3

db = sqlite3.connect(':memory:')
try:
    cases = [
        ('abcdef', 4, 2, 'de'),
        ('abcdef', 4, -2, 'bc'),
        ('abcdef', -2, 2, 'ef'),
        ('abcdef', -2, -2, 'cd'),
        ('abcdef', 1, -2, ''),
        ('abcdef', 2, -8, 'a'),
        ('甲乙丙丁', 3, -2, '甲乙'),
    ]
    for text, start, length, wanted in cases:
        actual = db.execute('SELECT substr(?, ?, ?)',
                            (text, start, length)).fetchone()[0]
        assert actual == wanted
        print(repr(text), start, length, '=>', repr(actual))
    values = ['ticket-ABCD.done', 'AB.done', 'ticket-ABCD.wait', None]
    query = """
        SELECT CASE
            WHEN substr(?, -5) = '.done' AND length(?) >= 9
            THEN substr(?, -5, -4)
        END
    """
    actual = [db.execute(query, (v, v, v)).fetchone()[0] for v in values]
    assert actual == ['ABCD', None, None, None]
    print('guarded identifiers:', actual)
    assert db.execute('SELECT substr(NULL, 4, -2)').fetchone() == (None,)
finally:
    db.close()

靠近开头时,长度不够不会自动补齐

输出中的第四个结果是 cd,不是 dc。起点为一时,它前面没有字符,所以得到空串;起点为二、长度为负八时,只能取得现有的 a。指定长度并不是“必须存在这么多字符”的校验条件,想要固定四位还需要检查输入长度。

编号示例把负五作为 .done 开头点号的位置,再取它前面的四个字符。查询先确认后缀完全相符,并要求总长度至少九,才返回内容。因此 ticket-ABCD.done 得到 ABCD,过短、后缀不同与空值都留为 NULL,不会把截短结果当成有效编号。

这个长度条件只保证位置空间足够,没有证明前缀必须是 ticket,也没有证明四位都是字母。真实编号规则如果还要求这些条件,应继续单独验证。提取表达式负责选择一段位置,业务格式负责判断这段数据能否使用,二者不能互相代替。

位置单位必须和输入表示一致

中文样本返回甲乙,说明文本位置按 Unicode 码点计数,不能拿 UTF-8 字节数直接代入。SQLite 对 BLOB 使用字节位置;同样的整数放进不同表示,就可能选中不同范围。应用侧算好的偏移值应附带明确单位。

码点也不等于界面上完整的字形。带组合附加符号的文字和某些表情可能由多个码点组成,机械截取会拆开视觉单元。本例用于结构明确的编号和普通示范字符;如果目标是自然语言显示摘要,应在支持字素边界的层处理。

维护旧查询时,把起点与长度分别标注含义,再保留正正、正负、负正、负负四组小样本。额外加上接近开头、短输入和空值,能及时发现少取一位或把空结果误当错误的变化。不要为了让一条样本看起来对,就同时改动两个负号。

资料核对日期:2026年10月2日(北京时间)。最终展示代码在 Python 3.12.14、SQLite 3.53.1 中独立运行并通过全部断言。

官方参考

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。