应用带版本号的目录更新

SQLiteBeginner
立即练习

介绍

MEND & MOSS 已准备应用供应商更新。收货工作区中已有暂存的交付数据和上一实验中的基础检查脚本。这是独立环境,不需要上一 VM 中的任何文件。

有些看似可用的行对同一版本给出了矛盾内容,还有一些是迟到的旧数据。你将扩展检查来拒绝这些矛盾,再保存一份更新脚本,只接纳更高版本号的更新,同时保留店铺自己的货架备注。本实验假定你已掌握第一个引导实验中的基础 SQL;冲突检查、UPSERT 和事务会在这里讲解。

全程使用 Terminal。你可以修改 inspect.sql、创建 apply.sql,以及重新加载 raw_delivery。保留原有库存标识及其货架备注、供应商和两个 CSV 文件。由供应商维护的商品名称、价格和版本号可按下述规则更新。

拒绝内容矛盾的版本

本步骤中,你将区分完全相同的重复行与版本冲突。从 Terminal 开始:

cd /home/labex/project/supplier-desk

读取本单元实际的交付文件和提供的检查脚本:

cat deliveries/current.csv
cat inspect.sql

十行交付数据已暂存。第 3 行和第 7 行重复了相同的修补胶带标识、名称、价格和版本号;重复一条事实不会使其成为新商品。第 8 行和第 9 行都描述版本号为 2 的 N / PIN-2,但价格不同。两行都不能作为认可的结果。第 10 行给出了版本号为 1 的 N / WASH-2,但价格与库存中已有的版本 1 不同。同一个版本号不能对应两种不同内容。

第一个实验中的规则仍然适用。现在新增两种拒绝原因:其他方面有效的行,如果供应商、SKU、版本号相同,但名称或整数价格不同,则为 delivery conflict;如果该版本与当前库存的内容不同,则为 stock conflict。两者同时适用时,交付冲突优先。完全相同的重复行可以接受。较低版本可以通过检查,但绝不能覆盖较新的库存。

修改前先查看原始库存:

sqlite3 -header -column stock.db 'SELECT * FROM stock ORDER BY supplier,sku;'

运行基础检查,确保其视图已创建:

sqlite3 -bail stock.db < inspect.sql

编辑前,为提供的脚本制作一份恢复副本。cp 将第一个路径复制到第二个路径,不改变原文件:

cp inspect.sql inspect-start.sql

下面的 SQL 使用别名 t、other、s 和 e,分别表示一条交付记录、另一条交付记录、一条库存记录和一条错误记录。EXISTS (SELECT 1 ...) 判断是否存在匹配行;因为不返回该行的内容,1 只是占位值。AND 要求所有标识字段都匹配。<> 表示不相等。NOT EXISTS 保留没有错误的行。UNION ALL 合并基础错误和冲突,同时保留各自的数据行标识。

将扩展内容追加到 inspect.sql。>> 表示追加而非替换,因此上方已有的基础定义会保留。粘贴此代码块一次。这里重复粘贴不会破坏结果,因为每段扩展只会重建最后两个视图,但应避免不必要的重复文本:

cat >> inspect.sql <<'SQL'
DROP VIEW IF EXISTS delivery_ready;
DROP VIEW IF EXISTS delivery_errors;
CREATE VIEW delivery_errors AS
SELECT delivery_line,reason FROM delivery_check WHERE reason IS NOT NULL
UNION ALL
SELECT t.delivery_line,
  CASE WHEN EXISTS (
    SELECT 1 FROM typed_delivery AS other
    WHERE other.supplier=t.supplier AND other.sku=t.sku
      AND other.revision=t.revision
      AND (other.title<>t.title OR other.price_cents<>t.price_cents)
  ) THEN 'delivery conflict' ELSE 'stock conflict' END
FROM typed_delivery AS t
WHERE EXISTS (
  SELECT 1 FROM typed_delivery AS other
  WHERE other.supplier=t.supplier AND other.sku=t.sku
    AND other.revision=t.revision
    AND (other.title<>t.title OR other.price_cents<>t.price_cents)
) OR EXISTS (
  SELECT 1 FROM stock AS s
  WHERE s.supplier=t.supplier AND s.sku=t.sku AND s.revision=t.revision
    AND (s.title<>t.title OR s.price_cents<>t.price_cents)
);
CREATE VIEW delivery_ready AS
SELECT supplier,sku,title,price_cents,revision FROM typed_delivery AS t
WHERE NOT EXISTS (
  SELECT 1 FROM delivery_errors AS e WHERE e.delivery_line=t.delivery_line
);
SQL

重新执行保存的完整脚本:

sqlite3 -bail stock.db < inspect.sql
sqlite3 -header -column stock.db 'SELECT * FROM delivery_errors ORDER BY delivery_line;'

预期有六条错误:第 4 行为 invalid price,第 5 行为 unknown supplier,第 6 行为 missing SKU,第 8 行和第 9 行为 delivery conflict,第 10 行为 stock conflict。修补胶带在可接受记录的审查结果中出现两次,因为两个源数据行都可接受;两条完全相同的行不能使价格翻倍或创建两条库存商品记录。

sqlite3 -header -column stock.db 'SELECT * FROM delivery_ready ORDER BY supplier,sku;'

四条可接受的交付数据行代表三个商品标识。库存仍未改变。如果只看到三条基础错误,请检查追加代码块是否存在,然后重新运行完整脚本。如果追加代码块有语法错误,用 cp inspect-start.sql inspect.sql 恢复提供的基础脚本,再重新追加完整代码块并执行。此恢复方式不会改变库存或两个 CSV 文件。

应用较新值并安全地重复处理交付

本步骤中,你将应用可接受的记录而不改变本地货架备注,然后证明重复处理同一批交付或接收旧批次不会撤销你的工作。从同一 Terminal 目录开始。

stock 表在 (supplier,sku) 上定义了主键:这两个字段标识一行。UPSERT 会插入尚不存在的标识,或处理与已有主键的冲突。excluded 表示拟写入的传入行。只有它的版本号大于当前库存版本号时,才会更新。提供的 shelf_note 列归店铺维护:传入值和更新赋值中都不要包含它。新库存标识使用已准备好的空备注默认值。

BEGIN 开始事务,COMMIT 将其作为一个整体保存。出现错误会使这条带 -bail 的命令停止;连接关闭时,未完成的事务会回滚。此示例更新的是一个预先准备的小型数据库,并不是并发同步或备份系统。

SELECT 读取实际的可接受记录视图。其 WHERE true 保留所有这些行,同时避免 SQLite 把后面的 ON CONFLICT 误认为连接条件。保存完整脚本:

cat > apply.sql <<'SQL'
BEGIN;
INSERT INTO stock(supplier,sku,title,price_cents,revision)
SELECT supplier,sku,title,price_cents,revision
FROM delivery_ready WHERE true
ON CONFLICT(supplier,sku) DO UPDATE SET
  title=excluded.title,
  price_cents=excluded.price_cents,
  revision=excluded.revision
WHERE excluded.revision>stock.revision;
COMMIT;
SQL

在应用脚本所用的连接上启用外键。-cmd 先运行引号中的连接设置;此设置不会全局保存。随后 < apply.sql 提供 SQL:

sqlite3 -bail -cmd 'PRAGMA foreign_keys=ON;' stock.db < apply.sql
sqlite3 -header -column stock.db 'SELECT * FROM stock ORDER BY supplier,sku;'

预期有四条库存记录:

supplier  sku     title        price_cents  revision  shelf_note
--------  ------  -----------  -----------  --------  ----------
N         BOLT-1  Steel bolt   135          3         Bin A
N         TAPE-4  Repair tape  450          1
N         WASH-2  Washer       40           1         Bin C
W         BOLT-1  Brass bolt   190          2         Drawer 4

垫圈价格保持为 40,因为其内容矛盾的版本被拒绝。没有新增销钉、价格无效的盖帽、未知供应商或 SKU 为空的库存商品。两种原有螺栓的备注均保留。修补胶带虽然在交付中出现两次,但只有一条库存记录。

重复执行同一脚本:

sqlite3 -bail -cmd 'PRAGMA foreign_keys=ON;' stock.db < apply.sql
sqlite3 -header -column stock.db 'SELECT * FROM stock ORDER BY supplier,sku;'

结果保持不变。版本号条件让完全相同的重复导入可以安全处理;它不会累加价格,也不会替换本地备注。

现在,使用旧批次前先读取它:

cat deliveries/older.csv

这些是螺栓的旧版本。使用交互式命令行,只重新加载暂存表:

sqlite3 stock.db
DELETE FROM raw_delivery;
.import --csv --skip 1 deliveries/older.csv raw_delivery
.quit

视图会自动读取表中的当前行。执行未修改的更新脚本,然后检查库存:

sqlite3 -bail -cmd 'PRAGMA foreign_keys=ON;' stock.db < apply.sql
sqlite3 -header -column stock.db 'SELECT * FROM stock ORDER BY supplier,sku;'

四条记录的值仍然全部相同。检查允许有效的旧版本数字,但应用它们不能使库存回退。这与交付数据无效或内容矛盾是不同的情况。

让工作区恢复到可再次审查今天交付数据的状态。清空暂存表不会清空库存:

sqlite3 stock.db
DELETE FROM raw_delivery;
.import --csv --skip 1 deliveries/current.csv raw_delivery
.quit
sqlite3 -header -column stock.db 'SELECT * FROM delivery_errors ORDER BY delivery_line;'

六条被拒绝的行及其原因仍然可见。保留两个脚本:inspect.sql 描述如何审查另一批兼容的交付数据,apply.sql 描述如何用其中安全的记录更新库存。路径错误可以用绝对路径 cd 恢复;脚本输错可以用完整代码块替换。不要为了修复拼写错误而重建 stock.db 或删除记录。

总结

你通过真实的 SQLite 数据行,建立了供应商标识、交付数据与本地库存之间的联系。你解读了实际接受和拒绝的记录,保留了原始来源,并保存了供下一批交付复用的 SQL。