Optimize database import queries with improved index selection

This commit is contained in:
2025-01-29 18:42:29 -05:00
parent fb5bf4a144
commit 0d377466aa
3 changed files with 187 additions and 4 deletions

View File

@@ -52,7 +52,7 @@ async function importOrders(prodConnection, localConnection) {
let processed = 0; let processed = 0;
// Process in batches // Process in batches
const batchSize = 20000; // Increased from 1000 since order records are small const batchSize = 10000; // Increased from 1000 since order records are small
let offset = 0; let offset = 0;
while (offset < total) { while (offset < total) {

View File

@@ -32,7 +32,7 @@ async function importPurchaseOrders(prodConnection, localConnection) {
FROM ( FROM (
SELECT DISTINCT pop.po_id, pop.pid SELECT DISTINCT pop.po_id, pop.pid
FROM po p FROM po p
FORCE INDEX (idx_date_created) USE INDEX (idx_date_created)
JOIN po_products pop ON p.po_id = pop.po_id JOIN po_products pop ON p.po_id = pop.po_id
JOIN suppliers s ON p.supplier_id = s.supplierid JOIN suppliers s ON p.supplier_id = s.supplierid
WHERE p.date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR) WHERE p.date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
@@ -42,6 +42,7 @@ async function importPurchaseOrders(prodConnection, localConnection) {
UNION UNION
SELECT DISTINCT r.receiving_id as po_id, rp.pid SELECT DISTINCT r.receiving_id as po_id, rp.pid
FROM receivings_products rp FROM receivings_products rp
USE INDEX (received_date)
LEFT JOIN receivings r ON r.receiving_id = rp.receiving_id LEFT JOIN receivings r ON r.receiving_id = rp.receiving_id
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR) WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
AND (rp.received_date > ? AND (rp.received_date > ?
@@ -64,6 +65,7 @@ async function importPurchaseOrders(prodConnection, localConnection) {
COALESCE(p.notes, '') as long_note COALESCE(p.notes, '') as long_note
FROM ( FROM (
SELECT po_id FROM po SELECT po_id FROM po
USE INDEX (idx_date_created)
WHERE date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR) WHERE date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
AND (date_ordered > ? AND (date_ordered > ?
OR stamp > ? OR stamp > ?
@@ -71,7 +73,7 @@ async function importPurchaseOrders(prodConnection, localConnection) {
UNION UNION
SELECT DISTINCT r.receiving_id as po_id SELECT DISTINCT r.receiving_id as po_id
FROM receivings r FROM receivings r
JOIN receivings_products rp ON r.receiving_id = rp.receiving_id JOIN receivings_products rp USE INDEX (received_date) ON r.receiving_id = rp.receiving_id
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR) WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
AND (rp.received_date > ? AND (rp.received_date > ?
OR rp.stamp > ?) OR rp.stamp > ?)
@@ -110,7 +112,7 @@ async function importPurchaseOrders(prodConnection, localConnection) {
pop.cost_each as cost_price, pop.cost_each as cost_price,
pop.qty_each as ordered pop.qty_each as ordered
FROM po_products pop FROM po_products pop
FORCE INDEX (PRIMARY) USE INDEX (PRIMARY)
JOIN products pr ON pop.pid = pr.pid JOIN products pr ON pop.pid = pr.pid
WHERE pop.po_id IN (?) WHERE pop.po_id IN (?)
`, [poIds]); `, [poIds]);
@@ -138,6 +140,7 @@ async function importPurchaseOrders(prodConnection, localConnection) {
ELSE 1 -- Different PO ELSE 1 -- Different PO
END as is_alt_po END as is_alt_po
FROM receivings_products rp FROM receivings_products rp
USE INDEX (received_date)
LEFT JOIN receivings r ON r.receiving_id = rp.receiving_id LEFT JOIN receivings r ON r.receiving_id = rp.receiving_id
WHERE rp.pid IN (?) WHERE rp.pid IN (?)
AND rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 2 YEAR) AND rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 2 YEAR)

View File

@@ -0,0 +1,180 @@
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
async function loadScript(name) {
try {
return await require(name);
} catch (error) {
console.error(`Failed to load script ${name}:`, error);
return null;
}
}
async function runWithTimeout(fn) {
return new Promise((resolve, reject) => {
// Create a child process for the script
const child = require('child_process').fork(fn.toString(), [], {
stdio: 'inherit'
});
child.on('exit', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Script exited with code ${code}`));
}
});
child.on('error', (err) => {
reject(err);
});
});
}
function clearScreen() {
process.stdout.write('\x1Bc');
}
const scripts = {
'Import Scripts': {
'1': { name: 'Full Import From Production', path: './import-from-prod' },
'2': { name: 'Individual Import Scripts ▸', submenu: {
'1': { name: 'Import Orders', path: './import/orders', key: 'importOrders' },
'2': { name: 'Import Products', path: './import/products', key: 'importProducts' },
'3': { name: 'Import Purchase Orders', path: './import/purchase-orders' },
'4': { name: 'Import Categories', path: './import/categories' },
'b': { name: 'Back to Main Menu' }
}}
},
'Metrics': {
'3': { name: 'Calculate All Metrics', path: './calculate-metrics' },
'4': { name: 'Individual Metric Scripts ▸', submenu: {
'1': { name: 'Brand Metrics', path: './metrics/brand-metrics' },
'2': { name: 'Category Metrics', path: './metrics/category-metrics' },
'3': { name: 'Financial Metrics', path: './metrics/financial-metrics' },
'4': { name: 'Product Metrics', path: './metrics/product-metrics' },
'5': { name: 'Sales Forecasts', path: './metrics/sales-forecasts' },
'6': { name: 'Time Aggregates', path: './metrics/time-aggregates' },
'7': { name: 'Vendor Metrics', path: './metrics/vendor-metrics' },
'b': { name: 'Back to Main Menu' }
}}
},
'Database Management': {
'5': { name: 'Test Production Connection', path: './test-prod-connection' }
},
'Reset Scripts': {
'6': { name: 'Reset Database', path: './reset-db' },
'7': { name: 'Reset Metrics', path: './reset-metrics' }
}
};
let lastRun = null;
async function displayMenu(menuItems, title = 'Inventory Management Script Runner') {
clearScreen();
console.log(`\n${title}\n`);
for (const [category, items] of Object.entries(menuItems)) {
console.log(`\n${category}:`);
Object.entries(items).forEach(([key, script]) => {
console.log(`${key}. ${script.name}`);
});
}
if (lastRun) {
console.log('\nQuick Access:');
console.log(`r. Repeat Last Script (${lastRun.name})`);
}
console.log('\nq. Quit\n');
}
async function handleSubmenu(submenu, title) {
while (true) {
await displayMenu({"Individual Scripts": submenu}, title);
const choice = await question('Select an option (or b to go back): ');
if (choice.toLowerCase() === 'b') {
return null;
}
if (submenu[choice]) {
return submenu[choice];
}
console.log('Invalid selection. Please try again.');
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
async function runScript(script) {
console.log(`\nRunning: ${script.name}`);
try {
const scriptPath = require.resolve(script.path);
await runWithTimeout(scriptPath);
console.log('\nScript completed successfully');
lastRun = script;
} catch (error) {
console.error('\nError running script:', error);
}
await question('\nPress Enter to continue...');
}
async function main() {
while (true) {
await displayMenu(scripts);
const choice = await question('Select an option: ');
if (choice.toLowerCase() === 'q') {
break;
}
if (choice.toLowerCase() === 'r' && lastRun) {
await runScript(lastRun);
continue;
}
let selectedScript = null;
for (const category of Object.values(scripts)) {
if (category[choice]) {
selectedScript = category[choice];
break;
}
}
if (!selectedScript) {
console.log('Invalid selection. Please try again.');
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
if (selectedScript.submenu) {
const submenuChoice = await handleSubmenu(
selectedScript.submenu,
selectedScript.name
);
if (submenuChoice && submenuChoice.path) {
await runScript(submenuChoice);
}
} else if (selectedScript.path) {
await runScript(selectedScript);
}
}
rl.close();
process.exit(0);
}
if (require.main === module) {
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}