| name | data-integrity-auditor |
| description | Detects data integrity issues including orphaned records, broken foreign key relationships, constraint violations, and provides automated fix migrations. Use for "data integrity", "orphaned records", "broken relationships", or "data quality". |
Data Integrity Auditor
Detect and fix data integrity issues automatically.
Integrity Check Types
1. Orphaned Records
SELECT o.id, o.user_id
FROM orders o
LEFT JOIN users u ON u.id = o.user_id
WHERE u.id IS NULL;
SELECT oi.id, oi.order_id
FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL;
2. Broken Foreign Keys
async function checkForeignKeys() {
const issues: string[] = [];
const orphanedOrders = await prisma.$queryRaw<any[]>`
SELECT o.id, o.user_id
FROM orders o
LEFT JOIN users u ON u.id = o.user_id
WHERE u.id IS NULL
LIMIT 100
`;
if (orphanedOrders.length > 0) {
issues.push(
`❌ Found ${orphanedOrders.length} orders with invalid user_id`
);
console.log(
" Sample IDs:",
orphanedOrders.slice(0, 5).map((o) => o.id)
);
}
const orphanedItems = await prisma.$queryRaw<any[]>`
SELECT oi.id, oi.order_id
FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL
LIMIT 100
`;
if (orphanedItems.length > 0) {
issues.push(
`❌ Found ${orphanedItems.length} order items with invalid order_id`
);
}
const orphanedProducts = await prisma.$queryRaw<any[]>`
SELECT p.id, p.category_id
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
WHERE p.category_id IS NOT NULL
AND c.id IS NULL
LIMIT 100
`;
if (orphanedProducts.length > 0) {
issues.push(
`❌ Found ${orphanedProducts.length} products with invalid category_id`
);
}
return issues;
}
3. Constraint Violations
async function checkConstraints() {
const issues: string[] = [];
const duplicateEmails = await prisma.$queryRaw<any[]>`
SELECT email, COUNT(*) as count
FROM users
GROUP BY email
HAVING COUNT(*) > 1
`;
if (duplicateEmails.length > 0) {
issues.push(`❌ Found ${duplicateEmails.length} duplicate emails`);
}
const negativeStock = await prisma.$queryRaw<any[]>`
SELECT id, name, stock
FROM products
WHERE stock < 0
`;
if (negativeStock.length > 0) {
issues.push(
`❌ Found ${negativeStock.length} products with negative stock`
);
}
const negativePrices = await prisma.$queryRaw<any[]>`
SELECT id, name, price
FROM products
WHERE price < 0
`;
if (negativePrices.length > 0) {
issues.push(
`❌ Found ${negativePrices.length} products with negative prices`
);
}
const invalidStatus = prisma.<[]>;
(invalidStatus. > ) {
issues.();
}
issues;
}
4. Missing Required Fields
async function checkMissingFields() {
const issues: string[] = [];
const usersNoEmail = await prisma.user.count({
where: { email: null },
});
if (usersNoEmail > 0) {
issues.push(`❌ Found ${usersNoEmail} users without email`);
}
const ordersNoTotal = await prisma.order.count({
where: { total: null },
});
if (ordersNoTotal > 0) {
issues.push(`❌ Found ${ordersNoTotal} orders without total`);
}
return issues;
}
Comprehensive Audit Script
interface IntegrityIssue {
severity: "critical" | "warning" | "info";
category: string;
message: string;
count: number;
query?: string;
fix?: string;
}
async function auditDataIntegrity(): Promise<IntegrityIssue[]> {
const issues: IntegrityIssue[] = [];
console.log("🔍 Auditing data integrity...\n");
const orphanedOrders = await prisma.$queryRaw<any[]>`
SELECT COUNT(*) as count FROM orders o
LEFT JOIN users u ON u.id = o.user_id
WHERE u.id IS NULL
`;
if (orphanedOrders[0].count > 0) {
issues.push({
severity: "critical",
category: "orphaned-records",
message: "Orders with invalid user references",
count: orphanedOrders[0].count,
query:
,
: ,
});
}
duplicateEmails = prisma.<[]>;
(duplicateEmails. > ) {
issues.({
: ,
: ,
: ,
: duplicateEmails.,
: ,
});
}
invalidPrices = prisma.<[]>;
(invalidPrices[]. > ) {
issues.({
: ,
: ,
: ,
: invalidPrices[].,
: ,
});
}
brokenOrderItems = prisma.<[]>;
(brokenOrderItems[]. > ) {
issues.({
: ,
: ,
: ,
: brokenOrderItems[].,
: ,
});
}
issues;
}
() {
issues = ();
.();
.();
grouped = issues.( {
(!acc[issue.]) acc[issue.] = [];
acc[issue.].(issue);
acc;
}, {} <, []>);
([, , ] ).( {
items = grouped[severity] || [];
(items. === ) ;
.();
items.( {
.();
.();
(issue.) {
.();
}
(issue.) {
.();
}
.();
});
});
process.(grouped.?. > ? : );
}
();
Automated Fixes
async function fixOrphanedRecords() {
console.log("🔧 Fixing orphaned records...\n");
const deletedOrders = await prisma.$executeRaw`
DELETE FROM orders
WHERE id IN (
SELECT o.id FROM orders o
LEFT JOIN users u ON u.id = o.user_id
WHERE u.id IS NULL
)
`;
console.log(`✅ Deleted ${deletedOrders} orphaned orders`);
const deletedItems = await prisma.$executeRaw`
DELETE FROM order_items
WHERE id IN (
SELECT oi.id FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL
)
`;
console.log(`✅ Deleted ${deletedItems} orphaned order items`);
}
async function fixDuplicates() {
console.log("🔧 Fixing duplicate records...\n");
await prisma.$executeRaw`
DELETE FROM users
WHERE id IN (
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) as rn
FROM users
) t
WHERE rn > 1
)
`;
console.log(`✅ Fixed duplicate emails`);
}
() {
.();
fixedPrices = prisma.;
.();
fixedStock = prisma.;
.();
}
Prevention: Add Missing Constraints
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE;
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order_id
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE;
ALTER TABLE products
ADD CONSTRAINT chk_products_price_positive
CHECK (price >= 0);
ALTER TABLE products
ADD CONSTRAINT chk_products_stock_non_negative
CHECK (stock >= 0);
CREATE UNIQUE INDEX idx_users_email_unique
ON users(LOWER(email));
ALTER TABLE users
ALTER COLUMN email SET NOT NULL;
ALTER TABLE orders
ALTER COLUMN total SET NOT NULL;
Automated Testing
describe("Data Integrity", () => {
it("should not allow orphaned orders", async () => {
await expect(
prisma.order.create({
data: {
userId: 99999,
total: 100,
status: "pending",
},
})
).rejects.toThrow("Foreign key constraint");
});
it("should not allow negative prices", async () => {
await expect(
prisma.product.create({
data: {
name: "Test",
price: -10,
stock: 100,
},
})
).rejects.toThrow("Check constraint");
});
it("should not allow duplicate emails", async () => {
await prisma.user.create({
data: { : , : },
});
(
prisma..({
: { : , : },
})
)..();
});
});
Monitoring Dashboard
async function getDataQualityMetrics() {
return {
orphanedOrders: await prisma.$queryRaw`
SELECT COUNT(*) FROM orders o
LEFT JOIN users u ON u.id = o.user_id
WHERE u.id IS NULL
`,
duplicateEmails: await prisma.$queryRaw`
SELECT COUNT(*) FROM (
SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1
) t
`,
invalidPrices: await prisma.$queryRaw`
SELECT COUNT(*) FROM products WHERE price < 0
`,
missingData: await prisma.$queryRaw`
SELECT
SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) as users_no_email,
SUM(CASE WHEN total IS NULL THEN 1 ELSE 0 END) as orders_no_total
FROM users
CROSS JOIN orders
`,
};
}
Best Practices
- Add constraints: Prevent issues at database level
- Regular audits: Weekly integrity checks
- Automated fixes: Safe, reversible repairs
- Monitor metrics: Track data quality over time
- Test constraints: Ensure they work
- Soft deletes: Easier recovery
- Backup before fixes: Always
Output Checklist