Initial commit: 宁夏智慧养殖监管平台
This commit is contained in:
225
backend/analyze-foreign-keys.js
Normal file
225
backend/analyze-foreign-keys.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 分析数据库中表之间的外键关系
|
||||
* @file analyze-foreign-keys.js
|
||||
* @description 识别所有外键约束和引用关系,为ID重排做准备
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function analyzeForeignKeys() {
|
||||
try {
|
||||
console.log('=== 分析数据库外键关系 ===\n');
|
||||
|
||||
// 获取所有外键约束
|
||||
const foreignKeys = await sequelize.query(`
|
||||
SELECT
|
||||
kcu.TABLE_NAME as table_name,
|
||||
kcu.COLUMN_NAME as column_name,
|
||||
kcu.REFERENCED_TABLE_NAME as referenced_table,
|
||||
kcu.REFERENCED_COLUMN_NAME as referenced_column,
|
||||
rc.CONSTRAINT_NAME as constraint_name,
|
||||
rc.UPDATE_RULE as update_rule,
|
||||
rc.DELETE_RULE as delete_rule
|
||||
FROM information_schema.KEY_COLUMN_USAGE kcu
|
||||
JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
||||
ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
|
||||
WHERE kcu.TABLE_SCHEMA = DATABASE()
|
||||
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
||||
ORDER BY kcu.TABLE_NAME, kcu.COLUMN_NAME
|
||||
`, { type: QueryTypes.SELECT });
|
||||
|
||||
console.log(`发现 ${foreignKeys.length} 个外键关系:\n`);
|
||||
|
||||
const relationshipMap = new Map();
|
||||
const tablesWithForeignKeys = new Set();
|
||||
const referencedTables = new Set();
|
||||
|
||||
foreignKeys.forEach(fk => {
|
||||
const key = `${fk.table_name}.${fk.column_name}`;
|
||||
const reference = `${fk.referenced_table}.${fk.referenced_column}`;
|
||||
|
||||
relationshipMap.set(key, {
|
||||
table: fk.table_name,
|
||||
column: fk.column_name,
|
||||
referencedTable: fk.referenced_table,
|
||||
referencedColumn: fk.referenced_column,
|
||||
constraintName: fk.constraint_name,
|
||||
updateRule: fk.update_rule,
|
||||
deleteRule: fk.delete_rule
|
||||
});
|
||||
|
||||
tablesWithForeignKeys.add(fk.table_name);
|
||||
referencedTables.add(fk.referenced_table);
|
||||
|
||||
console.log(`🔗 ${fk.table_name}.${fk.column_name} -> ${fk.referenced_table}.${fk.referenced_column}`);
|
||||
console.log(` 约束名: ${fk.constraint_name}`);
|
||||
console.log(` 更新规则: ${fk.update_rule}`);
|
||||
console.log(` 删除规则: ${fk.delete_rule}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// 分析每个外键字段的数据分布
|
||||
console.log('\n=== 外键字段数据分布 ===\n');
|
||||
|
||||
const foreignKeyStats = [];
|
||||
|
||||
for (const [key, relationship] of relationshipMap) {
|
||||
const { table, column, referencedTable, referencedColumn } = relationship;
|
||||
|
||||
try {
|
||||
// 获取外键字段的统计信息
|
||||
const stats = await sequelize.query(`
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COUNT(DISTINCT ${column}) as unique_count,
|
||||
MIN(${column}) as min_value,
|
||||
MAX(${column}) as max_value,
|
||||
COUNT(CASE WHEN ${column} IS NULL THEN 1 END) as null_count
|
||||
FROM ${table}
|
||||
`, { type: QueryTypes.SELECT });
|
||||
|
||||
const stat = stats[0];
|
||||
|
||||
// 检查引用完整性
|
||||
const integrityCheck = await sequelize.query(`
|
||||
SELECT COUNT(*) as invalid_references
|
||||
FROM ${table} t
|
||||
WHERE t.${column} IS NOT NULL
|
||||
AND t.${column} NOT IN (
|
||||
SELECT ${referencedColumn}
|
||||
FROM ${referencedTable}
|
||||
WHERE ${referencedColumn} IS NOT NULL
|
||||
)
|
||||
`, { type: QueryTypes.SELECT });
|
||||
|
||||
const invalidRefs = parseInt(integrityCheck[0].invalid_references);
|
||||
|
||||
const fkStat = {
|
||||
table,
|
||||
column,
|
||||
referencedTable,
|
||||
referencedColumn,
|
||||
totalCount: parseInt(stat.total_count),
|
||||
uniqueCount: parseInt(stat.unique_count),
|
||||
minValue: stat.min_value,
|
||||
maxValue: stat.max_value,
|
||||
nullCount: parseInt(stat.null_count),
|
||||
invalidReferences: invalidRefs,
|
||||
hasIntegrityIssues: invalidRefs > 0
|
||||
};
|
||||
|
||||
foreignKeyStats.push(fkStat);
|
||||
|
||||
console.log(`📊 ${table}.${column} -> ${referencedTable}.${referencedColumn}:`);
|
||||
console.log(` - 总记录数: ${fkStat.totalCount}`);
|
||||
console.log(` - 唯一值数: ${fkStat.uniqueCount}`);
|
||||
console.log(` - 值范围: ${fkStat.minValue} - ${fkStat.maxValue}`);
|
||||
console.log(` - NULL值数: ${fkStat.nullCount}`);
|
||||
console.log(` - 无效引用: ${fkStat.invalidReferences}`);
|
||||
console.log(` - 完整性问题: ${fkStat.hasIntegrityIssues ? '是' : '否'}`);
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ ${table}.${column}: 分析失败 - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成依赖关系图
|
||||
console.log('\n=== 表依赖关系 ===\n');
|
||||
|
||||
const dependencyGraph = new Map();
|
||||
|
||||
foreignKeys.forEach(fk => {
|
||||
if (!dependencyGraph.has(fk.table_name)) {
|
||||
dependencyGraph.set(fk.table_name, new Set());
|
||||
}
|
||||
dependencyGraph.get(fk.table_name).add(fk.referenced_table);
|
||||
});
|
||||
|
||||
// 计算更新顺序(拓扑排序)
|
||||
const updateOrder = [];
|
||||
const visited = new Set();
|
||||
const visiting = new Set();
|
||||
|
||||
function topologicalSort(table) {
|
||||
if (visiting.has(table)) {
|
||||
console.log(`⚠️ 检测到循环依赖: ${table}`);
|
||||
return;
|
||||
}
|
||||
if (visited.has(table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
visiting.add(table);
|
||||
|
||||
const dependencies = dependencyGraph.get(table) || new Set();
|
||||
for (const dep of dependencies) {
|
||||
topologicalSort(dep);
|
||||
}
|
||||
|
||||
visiting.delete(table);
|
||||
visited.add(table);
|
||||
updateOrder.push(table);
|
||||
}
|
||||
|
||||
// 对所有表进行拓扑排序
|
||||
const allTables = new Set([...tablesWithForeignKeys, ...referencedTables]);
|
||||
for (const table of allTables) {
|
||||
topologicalSort(table);
|
||||
}
|
||||
|
||||
console.log('建议的ID重排顺序(被引用的表优先):');
|
||||
updateOrder.reverse().forEach((table, index) => {
|
||||
const deps = dependencyGraph.get(table);
|
||||
const depList = deps ? Array.from(deps).join(', ') : '无';
|
||||
console.log(`${index + 1}. ${table} (依赖: ${depList})`);
|
||||
});
|
||||
|
||||
// 汇总报告
|
||||
console.log('\n=== 汇总报告 ===');
|
||||
console.log(`外键关系总数: ${foreignKeys.length}`);
|
||||
console.log(`涉及外键的表: ${tablesWithForeignKeys.size}`);
|
||||
console.log(`被引用的表: ${referencedTables.size}`);
|
||||
|
||||
const tablesWithIssues = foreignKeyStats.filter(stat => stat.hasIntegrityIssues);
|
||||
if (tablesWithIssues.length > 0) {
|
||||
console.log(`\n⚠️ 发现完整性问题的表:`);
|
||||
tablesWithIssues.forEach(stat => {
|
||||
console.log(`- ${stat.table}.${stat.column}: ${stat.invalidReferences} 个无效引用`);
|
||||
});
|
||||
} else {
|
||||
console.log('\n✅ 所有外键关系完整性正常');
|
||||
}
|
||||
|
||||
return {
|
||||
foreignKeys,
|
||||
foreignKeyStats,
|
||||
updateOrder: updateOrder.reverse(),
|
||||
relationshipMap,
|
||||
tablesWithIssues
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('分析外键关系失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
analyzeForeignKeys()
|
||||
.then(() => {
|
||||
console.log('\n分析完成!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('分析失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { analyzeForeignKeys };
|
||||
Reference in New Issue
Block a user