340 lines
10 KiB
JavaScript
340 lines
10 KiB
JavaScript
/**
|
||
* 重新排序数据库中所有表的主键ID
|
||
* @file reorder-primary-keys.js
|
||
* @description 将所有表的主键ID重新排序,从1开始升序,同时更新外键引用
|
||
*/
|
||
|
||
const { sequelize } = require('./config/database-simple');
|
||
const { QueryTypes } = require('sequelize');
|
||
|
||
// 根据依赖关系确定的处理顺序(被引用的表优先)
|
||
const TABLE_ORDER = [
|
||
'roles',
|
||
'users',
|
||
'farms',
|
||
'devices',
|
||
'products',
|
||
'animals',
|
||
'alerts',
|
||
'orders',
|
||
'sensor_data',
|
||
'order_items',
|
||
'user_roles',
|
||
'seeds'
|
||
];
|
||
|
||
// 外键映射关系
|
||
const FOREIGN_KEY_MAPPINGS = {
|
||
'animals': [{ column: 'farm_id', referencedTable: 'farms' }],
|
||
'alerts': [
|
||
{ column: 'device_id', referencedTable: 'devices' },
|
||
{ column: 'farm_id', referencedTable: 'farms' }
|
||
],
|
||
'devices': [{ column: 'farm_id', referencedTable: 'farms' }],
|
||
'order_items': [
|
||
{ column: 'order_id', referencedTable: 'orders' },
|
||
{ column: 'product_id', referencedTable: 'products' }
|
||
],
|
||
'orders': [{ column: 'user_id', referencedTable: 'users' }],
|
||
'sensor_data': [
|
||
{ column: 'device_id', referencedTable: 'devices' },
|
||
{ column: 'farm_id', referencedTable: 'farms' }
|
||
],
|
||
'user_roles': [
|
||
{ column: 'role_id', referencedTable: 'roles' },
|
||
{ column: 'user_id', referencedTable: 'users' }
|
||
]
|
||
};
|
||
|
||
class IDReorderManager {
|
||
constructor() {
|
||
this.idMappings = new Map(); // 存储旧ID到新ID的映射
|
||
this.transaction = null;
|
||
}
|
||
|
||
async reorderAllTables() {
|
||
this.transaction = await sequelize.transaction();
|
||
|
||
try {
|
||
console.log('=== 开始重新排序所有表的主键ID ===\n');
|
||
|
||
// 临时禁用外键检查
|
||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0', { transaction: this.transaction });
|
||
|
||
// 按顺序处理每个表
|
||
for (const tableName of TABLE_ORDER) {
|
||
await this.reorderTableIDs(tableName);
|
||
}
|
||
|
||
// 重新启用外键检查
|
||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1', { transaction: this.transaction });
|
||
|
||
// 验证数据完整性
|
||
await this.verifyIntegrity();
|
||
|
||
await this.transaction.commit();
|
||
console.log('\n✅ 所有表的ID重新排序完成!');
|
||
|
||
} catch (error) {
|
||
await this.transaction.rollback();
|
||
console.error('❌ ID重新排序失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async reorderTableIDs(tableName) {
|
||
try {
|
||
console.log(`\n🔄 处理表: ${tableName}`);
|
||
|
||
// 检查表是否存在且有数据
|
||
const tableExists = await this.checkTableExists(tableName);
|
||
if (!tableExists) {
|
||
console.log(`⚠️ 表 ${tableName} 不存在,跳过`);
|
||
return;
|
||
}
|
||
|
||
const recordCount = await this.getRecordCount(tableName);
|
||
if (recordCount === 0) {
|
||
console.log(`ℹ️ 表 ${tableName} 无数据,跳过`);
|
||
return;
|
||
}
|
||
|
||
console.log(` 记录数: ${recordCount}`);
|
||
|
||
// 获取当前所有记录(按ID排序)
|
||
const records = await sequelize.query(
|
||
`SELECT * FROM ${tableName} ORDER BY id`,
|
||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||
);
|
||
|
||
if (records.length === 0) {
|
||
console.log(` 无记录需要处理`);
|
||
return;
|
||
}
|
||
|
||
// 创建ID映射
|
||
const oldToNewIdMap = new Map();
|
||
records.forEach((record, index) => {
|
||
const oldId = record.id;
|
||
const newId = index + 1;
|
||
oldToNewIdMap.set(oldId, newId);
|
||
});
|
||
|
||
// 存储映射供其他表使用
|
||
this.idMappings.set(tableName, oldToNewIdMap);
|
||
|
||
// 检查是否需要重新排序
|
||
const needsReorder = records.some((record, index) => record.id !== index + 1);
|
||
|
||
if (!needsReorder) {
|
||
console.log(` ✅ ID已经是连续的,无需重新排序`);
|
||
return;
|
||
}
|
||
|
||
console.log(` 🔧 重新排序ID: ${records[0].id}-${records[records.length-1].id} -> 1-${records.length}`);
|
||
|
||
// 更新外键引用(如果有的话)
|
||
await this.updateForeignKeyReferences(tableName, records);
|
||
|
||
// 创建临时表
|
||
const tempTableName = `${tableName}_temp_reorder`;
|
||
await this.createTempTable(tableName, tempTableName);
|
||
|
||
// 将数据插入临时表(使用新ID)
|
||
await this.insertDataWithNewIDs(tableName, tempTableName, records, oldToNewIdMap);
|
||
|
||
// 删除原表数据
|
||
await sequelize.query(
|
||
`DELETE FROM ${tableName}`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
|
||
// 重置自增ID
|
||
await sequelize.query(
|
||
`ALTER TABLE ${tableName} AUTO_INCREMENT = 1`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
|
||
// 将临时表数据复制回原表
|
||
await this.copyDataFromTempTable(tableName, tempTableName);
|
||
|
||
// 删除临时表
|
||
await sequelize.query(
|
||
`DROP TABLE ${tempTableName}`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
|
||
console.log(` ✅ 表 ${tableName} ID重新排序完成`);
|
||
|
||
} catch (error) {
|
||
console.error(`❌ 处理表 ${tableName} 失败:`, error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async checkTableExists(tableName) {
|
||
const result = await sequelize.query(
|
||
`SELECT COUNT(*) as count FROM information_schema.tables
|
||
WHERE table_schema = DATABASE() AND table_name = '${tableName}'`,
|
||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||
);
|
||
return result[0].count > 0;
|
||
}
|
||
|
||
async getRecordCount(tableName) {
|
||
const result = await sequelize.query(
|
||
`SELECT COUNT(*) as count FROM ${tableName}`,
|
||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||
);
|
||
return parseInt(result[0].count);
|
||
}
|
||
|
||
async updateForeignKeyReferences(tableName, records) {
|
||
const foreignKeys = FOREIGN_KEY_MAPPINGS[tableName];
|
||
if (!foreignKeys) return;
|
||
|
||
console.log(` 🔗 更新外键引用...`);
|
||
|
||
for (const fk of foreignKeys) {
|
||
const referencedTableMapping = this.idMappings.get(fk.referencedTable);
|
||
if (!referencedTableMapping) {
|
||
console.log(` ⚠️ 引用表 ${fk.referencedTable} 的映射不存在,跳过外键 ${fk.column}`);
|
||
continue;
|
||
}
|
||
|
||
// 更新外键值
|
||
for (const record of records) {
|
||
const oldForeignKeyValue = record[fk.column];
|
||
if (oldForeignKeyValue && referencedTableMapping.has(oldForeignKeyValue)) {
|
||
const newForeignKeyValue = referencedTableMapping.get(oldForeignKeyValue);
|
||
record[fk.column] = newForeignKeyValue;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async createTempTable(originalTable, tempTable) {
|
||
// 先删除临时表(如果存在)
|
||
try {
|
||
await sequelize.query(
|
||
`DROP TABLE IF EXISTS ${tempTable}`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
} catch (error) {
|
||
// 忽略删除错误
|
||
}
|
||
|
||
await sequelize.query(
|
||
`CREATE TABLE ${tempTable} LIKE ${originalTable}`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
}
|
||
|
||
async insertDataWithNewIDs(originalTable, tempTable, records, idMapping) {
|
||
if (records.length === 0) return;
|
||
|
||
// 获取表结构
|
||
const columns = await sequelize.query(
|
||
`SELECT COLUMN_NAME FROM information_schema.columns
|
||
WHERE table_schema = DATABASE() AND table_name = '${originalTable}'
|
||
ORDER BY ordinal_position`,
|
||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||
);
|
||
|
||
const columnNames = columns.map(col => col.COLUMN_NAME);
|
||
|
||
// 批量插入数据
|
||
for (let i = 0; i < records.length; i += 100) {
|
||
const batch = records.slice(i, i + 100);
|
||
const values = batch.map((record, batchIndex) => {
|
||
const newId = i + batchIndex + 1;
|
||
const values = columnNames.map(col => {
|
||
if (col === 'id') {
|
||
return newId;
|
||
}
|
||
const value = record[col];
|
||
if (value === null || value === undefined) {
|
||
return 'NULL';
|
||
}
|
||
if (typeof value === 'string') {
|
||
return `'${value.replace(/'/g, "''")}'`;
|
||
}
|
||
if (value instanceof Date) {
|
||
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
||
}
|
||
if (typeof value === 'object') {
|
||
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
||
}
|
||
return value;
|
||
});
|
||
return `(${values.join(', ')})`;
|
||
});
|
||
|
||
await sequelize.query(
|
||
`INSERT INTO ${tempTable} (${columnNames.join(', ')}) VALUES ${values.join(', ')}`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
}
|
||
}
|
||
|
||
async copyDataFromTempTable(originalTable, tempTable) {
|
||
await sequelize.query(
|
||
`INSERT INTO ${originalTable} SELECT * FROM ${tempTable}`,
|
||
{ transaction: this.transaction }
|
||
);
|
||
}
|
||
|
||
async verifyIntegrity() {
|
||
console.log('\n🔍 验证数据完整性...');
|
||
|
||
for (const [tableName, foreignKeys] of Object.entries(FOREIGN_KEY_MAPPINGS)) {
|
||
if (!foreignKeys) continue;
|
||
|
||
for (const fk of foreignKeys) {
|
||
try {
|
||
const result = await sequelize.query(`
|
||
SELECT COUNT(*) as invalid_count
|
||
FROM ${tableName} t
|
||
WHERE t.${fk.column} IS NOT NULL
|
||
AND t.${fk.column} NOT IN (
|
||
SELECT id FROM ${fk.referencedTable} WHERE id IS NOT NULL
|
||
)
|
||
`, { type: QueryTypes.SELECT, transaction: this.transaction });
|
||
|
||
const invalidCount = parseInt(result[0].invalid_count);
|
||
if (invalidCount > 0) {
|
||
throw new Error(`表 ${tableName}.${fk.column} 有 ${invalidCount} 个无效的外键引用`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`❌ 完整性检查失败: ${tableName}.${fk.column}`, error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('✅ 数据完整性验证通过');
|
||
}
|
||
}
|
||
|
||
async function reorderPrimaryKeys() {
|
||
const manager = new IDReorderManager();
|
||
await manager.reorderAllTables();
|
||
}
|
||
|
||
// 如果直接运行此脚本
|
||
if (require.main === module) {
|
||
reorderPrimaryKeys()
|
||
.then(() => {
|
||
console.log('\n🎉 主键ID重新排序完成!');
|
||
process.exit(0);
|
||
})
|
||
.catch(error => {
|
||
console.error('💥 主键ID重新排序失败:', error);
|
||
process.exit(1);
|
||
})
|
||
.finally(() => {
|
||
sequelize.close();
|
||
});
|
||
}
|
||
|
||
module.exports = { reorderPrimaryKeys, IDReorderManager }; |