Liquibase LoadData计算值为NULL致插入失败问题求助
我有一个使用CSV文件加载数据的Liquibase变更集,数据通过查询其他表的SELECT语句计算生成。当依赖的其他表数据存在时加载正常;但数据不存在时计算值返回NULL,而目标是含非NULL列的关联表,插入操作因此报错。
变更集代码:
<changeSet author="s.davis" id="0000000000053-1"> <loadData tableName="PRODUCT_V2_REF_PRODUCT_JOURNEY_TYPE" file="db/liquibase/data/product_journey_type_initialisation.csv"> <column name="PRODUCT_V2_ID" type="computed"/> <column name="REF_PRODUCT_JOURNEY_TYPE_ID" type="computed"/> </loadData> </changeSet>
CSV内容:
PRODUCT_V2_ID,REF_PRODUCT_JOURNEY_TYPE_ID (select ID from PRODUCT_V2 where PRODUCT_CODE='no-such-code-so-returns-null-id'),(select ID from REF_PRODUCT_JOURNEY_TYPE where TYPE='Type1') ... other rows
问题在于语句 select ID from PRODUCT_V2 where PRODUCT_CODE='no-such-code-so-returns-null-id' 因PRODUCT_CODE不存在始终返回NULL。已尝试设置列默认值、使用COALESCE函数、设置preConditions的onFail为CONTINUE等方法,均未解决,求可行方案。
方案1:修改CSV查询,只在依赖数据存在时返回有效行
给CSV里的计算语句加上存在性判断,让查询在依赖数据不存在时返回空结果,Liquibase会自动跳过该行插入。
修改CSV对应行:
(select ID from PRODUCT_V2 where PRODUCT_CODE='no-such-code-so-returns-null-id' AND EXISTS(select 1 from PRODUCT_V2 where PRODUCT_CODE='no-such-code-so-returns-null-id')),(select ID from REF_PRODUCT_JOURNEY_TYPE where TYPE='Type1' AND EXISTS(select 1 from REF_PRODUCT_JOURNEY_TYPE where TYPE='Type1'))
方案2:替换成INSERT SELECT语句,直接过滤无效数据
放弃CSV加载方式,改用Liquibase的sql标签写插入逻辑,精准控制有效插入行。
变更集修改为:
<changeSet author="s.davis" id="0000000000053-1"> <sql> INSERT INTO PRODUCT_V2_REF_PRODUCT_JOURNEY_TYPE (PRODUCT_V2_ID, REF_PRODUCT_JOURNEY_TYPE_ID) -- 第一行数据 SELECT p.ID, t.ID FROM PRODUCT_V2 p, REF_PRODUCT_JOURNEY_TYPE t WHERE p.PRODUCT_CODE='no-such-code-so-returns-null-id' AND t.TYPE='Type1' UNION ALL -- 第二行数据 SELECT p.ID, t.ID FROM PRODUCT_V2 p, REF_PRODUCT_JOURNEY_TYPE t WHERE p.PRODUCT_CODE='existing-code-1' AND t.TYPE='Type2' -- 其他行依次添加 </sql> </changeSet>
这种写法只有当两个依赖表的对应数据都存在时,才会生成有效插入行,从根源避免NULL问题。
方案3:拆分变更集,给每行加前置检查
如果必须保留CSV加载,把原单变更集拆分为多个,每个变更集对应CSV里的一行,并添加前置检查,依赖数据不存在则跳过执行。
示例:
<changeSet author="s.davis" id="0000000000053-1"> <preConditions onFail="MARK_RAN"> <sqlCheck expectedResult="1">SELECT COUNT(*) FROM PRODUCT_V2 WHERE PRODUCT_CODE='no-such-code-so-returns-null-id'</sqlCheck> <sqlCheck expectedResult="1">SELECT COUNT(*) FROM REF_PRODUCT_JOURNEY_TYPE WHERE TYPE='Type1'</sqlCheck> </preConditions> <loadData tableName="PRODUCT_V2_REF_PRODUCT_JOURNEY_TYPE" file="db/liquibase/data/row1.csv"> <column name="PRODUCT_V2_ID" type="computed"/> <column name="REF_PRODUCT_JOURNEY_TYPE_ID" type="computed"/> </loadData> </changeSet> <changeSet author="s.davis" id="0000000000053-2"> <preConditions onFail="MARK_RAN"> <sqlCheck expectedResult="1">SELECT COUNT(*) FROM PRODUCT_V2 WHERE PRODUCT_CODE='existing-code'</sqlCheck> <sqlCheck expectedResult="1">SELECT COUNT(*) FROM REF_PRODUCT_JOURNEY_TYPE WHERE TYPE='Type2'</sqlCheck> </preConditions> <loadData tableName="PRODUCT_V2_REF_PRODUCT_JOURNEY_TYPE" file="db/liquibase/data/row2.csv"> <column name="PRODUCT_V2_ID" type="computed"/> <column name="REF_PRODUCT_JOURNEY_TYPE_ID" type="computed"/> </loadData> </changeSet>
每个小变更集仅处理一行数据,前置检查不通过时会标记为已执行,不会触发插入报错。
方案4:临时插入占位符,事后清理无效行
如果允许先插入临时数据再清理,给NULL值设置一个不会与有效ID冲突的占位符,插入后删除无效记录。
先修改CSV行:
(select COALESCE(ID, -999) from PRODUCT_V2 where PRODUCT_CODE='no-such-code-so-returns-null-id'),(select ID from REF_PRODUCT_JOURNEY_TYPE where TYPE='Type1')
然后在同一个变更集里添加清理SQL:
<changeSet author="s.davis" id="0000000000053-1"> <loadData tableName="PRODUCT_V2_REF_PRODUCT_JOURNEY_TYPE" file="db/liquibase/data/product_journey_type_initialisation.csv"> <column name="PRODUCT_V2_ID" type="computed"/> <column name="REF_PRODUCT_JOURNEY_TYPE_ID" type="computed"/> </loadData> <sql> DELETE FROM PRODUCT_V2_REF_PRODUCT_JOURNEY_TYPE WHERE PRODUCT_V2_ID NOT IN (SELECT ID FROM PRODUCT_V2) OR REF_PRODUCT_JOURNEY_TYPE_ID NOT IN (SELECT ID FROM REF_PRODUCT_JOURNEY_TYPE) </sql> </changeSet>
注意占位符要选一个肯定不会出现在PRODUCT_V2 ID范围内的值,比如负数,避免误删有效数据。
内容的提问来源于stack exchange,提问作者Steve Davis

