自己写:把开头匹配改成取一段
把下面的 TODO 改写成用范围表示"以 g1 开头"的写法,打出改写后判了多少次。
两张表 goods 400 行 id 主键|sup_id 1~20|cat 只有 A/B/C|price = i*7%500|name "g1"~"g400" sup 20 行 id 主键|city 杭州/苏州/南京/合肥(杭州 5 家)|name "s1"~"s20" 五个现成的工具函数(题面里已经写好) plan(sql) → 执行计划的每一行(一条查询可能有好几行) first(sql) → 只取第一行的第一个词:SCAN 还是 SEARCH sortstep(sql) → 有没有为排序/分组额外搭架子;有的话是为了哪一句 usedindex(sql) → 计划第一行用上的是哪一份索引 counted(sql) → 数出这条查询把条件判了多少次(条件里要放 tick())
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE sup (id INTEGER PRIMARY KEY, city TEXT NOT NULL, "
"name TEXT NOT NULL)")
db.execute("CREATE TABLE goods (id INTEGER PRIMARY KEY, sup_id INTEGER NOT NULL, "
"cat TEXT NOT NULL, price INTEGER NOT NULL, name TEXT NOT NULL)")
CITY = ["杭州", "苏州", "南京", "合肥"]
db.executemany("INSERT INTO sup VALUES (?,?,?)",
[(i, CITY[i % 4], "s" + str(i)) for i in range(1, 21)])
db.executemany("INSERT INTO goods VALUES (?,?,?,?,?)",
[(i, i % 20 + 1, "ABC"[i % 3], i * 7 % 500, "g" + str(i))
for i in range(1, 401)])
db.commit()
def plan(sql):
"""把执行计划一行一行打出来(每行开头那个词就是取数方式)。"""
return [r[3] for r in db.execute("EXPLAIN QUERY PLAN " + sql)]
def first(sql):
"""只要计划第一行的第一个词:SCAN(扫过去)还是 SEARCH(定点找)。"""
return plan(sql)[0].split()[0]
def sortstep(sql):
"""有没有为了排序/分组**额外搭一次架子**?有的话是为了哪一句。"""
for line in plan(sql):
if "TEMP B-TREE" in line:
return line.split("FOR ")[1].replace(" ", "")
return "没有"
def usedindex(sql):
"""计划第一行用上的是哪一份索引(没用上就是"没有")。"""
head = plan(sql)[0]
if "INDEX " not in head:
return "没有"
return head.split("INDEX ")[1].split(" ")[0]
def counted(sql):
"""数一数这条查询把条件判了多少次。
⚠️ tick() 必须写在条件的最前面:写在后面时它只对已经匹配上的行求值,
数出来的就变成"结果有几行",而不是"过了几行"。"""
hits = {"n": 0}
db.create_function("tick", 0, lambda: hits.__setitem__("n", hits["n"] + 1) or 1)
db.execute(sql).fetchall()
return hits["n"]
db.execute("CREATE INDEX ix_name ON goods(name)")
# 原来的写法:SELECT * FROM goods WHERE name LIKE 'g1%'
q = "SELECT * FROM goods WHERE tick() AND name LIKE 'g1%'"
# TODO:改写成 name >= ... AND name < ... 的范围写法
print(counted(q))
全部评论