iOS13正常的SQL窗口函数语句在iOS12报语法错误,求原因及修复方案
嘿,这个问题我之前帮同事排查过,刚好能给你说清楚!
你猜的完全没错,这确实是iOS 12自带的SQLite版本限制导致的。SQLite在3.25.0版本才正式引入了窗口函数(也就是你用的ROW_NUMBER() OVER (...)这类语法),而iOS 12搭载的SQLite版本是3.24.0,刚好不支持这个特性——当SQLite解析器碰到它不认识的窗口函数语法时,就会把ROW_NUMBER() OVER (里的左括号当成非法语法,抛出你看到的错误。
我整理了几个适合iOS场景的解决方案,你可以根据自己的需求选:
方案1:用自连接替代窗口函数实现行号逻辑
既然窗口函数用不了,我们可以用传统的自连接来生成行号,然后筛选出模5等于0的行。改写后的SQL如下:
SELECT t1.* FROM ( SELECT f1.*, COUNT(f2.foodname) AS rn FROM foodtable f1 LEFT JOIN foodtable f2 ON f2.foodname COLLATE NOCASE <= f1.foodname COLLATE NOCASE GROUP BY f1.foodname, f1.id -- 这里要包含表的主键或者所有列,避免分组逻辑出错 ) t1 WHERE t1.rn % 5 = 0
小提示:如果你的
foodname存在重复值,上面的SQL会给相同foodname的行分配相同的行号。如果需要严格连续的唯一行号,可以把连接条件改成:ON (f2.foodname COLLATE NOCASE < f1.foodname COLLATE NOCASE) OR (f2.foodname COLLATE NOCASE = f1.foodname COLLATE NOCASE AND f2.id < f1.id)这样就能通过主键区分相同foodname的行,生成连续唯一的行号了。
方案2:集成独立的SQLite库到App中
如果你的App依赖很多SQLite新特性,或者不想修改现有的SQL逻辑,最彻底的办法是把最新版的SQLite静态库集成到项目里,替换系统自带的SQLite。这样不管是iOS 12还是更高版本,App都会使用同一个高版本的SQLite,彻底解决版本兼容问题。
- 你可以直接从SQLite官网下载预编译的静态库,或者通过CocoaPods引入带自定义SQLite的FMDB(比如
pod 'FMDB/SQLite3',需要配置一下让它使用你引入的高版本SQLite)。 - 注意集成后要确保项目链接的是你引入的SQLite库,而不是系统自带的,避免符号冲突。
方案3:在App端处理行号筛选
如果你的foodtable数据量不大,也可以把所有数据先查询出来,在iOS代码里完成排序、生成行号和筛选的逻辑。比如用Swift实现的话:
// 假设已经从数据库获取到了Food模型的数组foodItems let sortedItems = foodItems.sorted { $0.foodname.lowercased() < $1.foodname.lowercased() } // 这里offset从0开始,所以要+1对应SQL里的行号 let filteredItems = sortedItems.enumerated() .filter { ($0.offset + 1) % 5 == 0 } .map { $0.element }
这个方案的优点是不用修改SQL,实现简单;但如果数据量很大,把所有数据加载到内存会占用过多资源,影响App性能,这种情况就不推荐了。
内容的提问来源于stack exchange,提问作者Andrew Barsby

