Met behulp van ChatGPT ben ik een webapplicatie aan het maken, en bij het verwijderen van een bestaande record waarbij sommige tabellen nu leeg zijn, verschijnt telkens bovenstaande foutmelding.
Helaas kom ik niet verder met ChatGPT en hopelijk kunnen jullie een inzicht geven of een richting geven waar ik het kan gaan zoeken
De code van dashboard pagina, sorry het is een lange pagina:
// Functie om te controleren of een kolom bestaat in een tabel
function kolomBestaat($mysqli, $tabel, $kolom) {
$result = $mysqli->query("SHOW COLUMNS FROM `$tabel` LIKE '$kolom'");
return $result && $result->num_rows > 0;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user_id = intval($_POST['user_id'] ?? 0);
if (isset($_POST['toggle_disable_user'])) {
$new_status = $mysqli->query("SELECT IF(status = 'disabled', 'active', 'disabled') AS new_status FROM users WHERE id = $user_id")->fetch_assoc()['new_status'];
$stmt = $mysqli->prepare("UPDATE users SET status = ? WHERE id = ?");
$stmt->bind_param("si", $new_status, $user_id);
$stmt->execute();
$_SESSION['success'] = "Status gebruiker #$user_id aangepast naar " . ucfirst($new_status);
} elseif (isset($_POST['archive_user'])) {
$mysqli->begin_transaction();
try {
$mysqli->query("UPDATE users SET status = 'archived', deleted_at = NOW() WHERE id = $user_id");
$mysqli->query("UPDATE businesses SET owner_id = NULL WHERE owner_id = $user_id");
$mysqli->commit();
$_SESSION['success'] = "Gebruiker #$user_id gearchiveerd";
} catch (Exception $e) {
$mysqli->rollback();
$_SESSION['error'] = "Archiveren mislukt: " . $e->getMessage();
}
} elseif (isset($_POST['delete_user'])) {
$mysqli->begin_transaction();
try {
error_log("START: Verwijder gebruiker #$user_id");
$businessResult = $mysqli->query("SELECT id FROM businesses WHERE owner_id = $user_id");
De betreffende kolom bestaat wel hoor.
Zie hieronder de output van alle tabellen:
SHOW COLUMNS FROM appointments
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
customer_id bigint(20) unsigned NO MUL NULL
staff_id bigint(20) unsigned YES MUL NULL
service_id bigint(20) unsigned NO MUL NULL
date_time timestamp NO CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
status varchar(50) YES confirmed
created_at timestamp NO CURRENT_TIMESTAMP
updated_at timestamp NO CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
deleted_at datetime YES NULL
location_id bigint(20) unsigned YES MUL NULL
business_id bigint(20) unsigned NO MUL NULL
========================================================================
SHOW COLUMNS FROM businesses
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
name varchar(100) NO UNI NULL
business_type enum('kapper','schoonheidssalon') YES kapper
address text YES NULL
created_at datetime YES CURRENT_TIMESTAMP
updated_at datetime YES CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
status enum('active','inactive') YES active
owner_id bigint(20) unsigned NO MUL NULL
deleted_at datetime YES NULL
=======================================================================
SHOW COLUMNS FROM customers
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
name varchar(100) NO NULL
email varchar(100) YES NULL
phone varchar(15) YES NULL
preferences text YES NULL
deleted_at datetime YES NULL
business_id bigint(20) unsigned NO MUL NULL
===========================================================================
SHOW COLUMNS FROM inventory
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
business_id bigint(20) unsigned NO MUL NULL
name varchar(100) NO NULL
quantity int(11) YES 0
price_per_unit decimal(10,2) YES NULL
deleted_at datetime YES NULL
location_id bigint(20) unsigned YES MUL NULL
=========================================================================
SHOW COLUMNS FROM locations
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
business_id bigint(20) unsigned NO MUL NULL
naam varchar(255) NO NULL
adres varchar(255) NO NULL
postcode varchar(20) NO NULL
stad varchar(100) NO NULL
land varchar(100) NO NULL
created_at timestamp NO CURRENT_TIMESTAMP
updated_at timestamp NO CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
deleted_at datetime YES NULL
=================================================================================
SHOW COLUMNS FROM services
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
name varchar(100) NO NULL
duration int(11) NO NULL
price decimal(10,2) NO NULL
deleted_at datetime YES NULL
business_id bigint(20) unsigned NO MUL NULL
================================================================================
SHOW COLUMNS FROM transactions
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
user_id bigint(20) unsigned YES MUL NULL
business_id bigint(20) unsigned NO MUL NULL
customer_id bigint(20) unsigned YES MUL NULL
amount decimal(10,2) NO NULL
discount_percentage decimal(5,2) YES 0.00
final_amount decimal(10,2) NO NULL
payment_method enum('cash','card','online') NO NULL
created_at timestamp NO CURRENT_TIMESTAMP
deleted_at datetime YES NULL
appointment_id bigint(20) unsigned YES MUL NULL
==================================================================================
SHOW COLUMNS FROM users
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
voornaam varchar(100) NO NULL
achternaam varchar(100) NO NULL
email varchar(100) NO UNI NULL
mobielnummer varchar(15) NO UNI NULL
password_hash varchar(255) NO NULL
role enum('admin','owner','employee') NO NULL
created_at timestamp NO CURRENT_TIMESTAMP
updated_at timestamp NO CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
status enum('onhold','active','inactive','archived','disa... NO onhold
verification_status tinyint(1) YES 0
verification_token varchar(255) YES NULL
deleted_at datetime YES NULL
last_login datetime YES MUL NULL
profiel_foto varchar(255) YES NULL
reset_token varchar(255) YES NULL
reset_expires datetime YES NULL
pending_email varchar(100) YES NULL
verification_token_email varchar(255) YES NULL
token_expires_at_email datetime YES NULL
====================================================================================
SHOW COLUMNS FROM user_locations
Field Type Null Key Default Extra
id bigint(20) unsigned NO PRI NULL auto_increment
user_id bigint(20) unsigned NO MUL NULL
location_id bigint(20) unsigned YES MUL NULL
error_log("???? Actieve database: " . $mysqli->query("SELECT DATABASE()")->fetch_row()[0]);
foreach ($tabelNamen as $tabel => $kolom) {
$tabel = trim($tabel);
$kolom = trim($kolom);
// Check of kolom bestaat in de tabel
$result = $mysqli->query("SHOW COLUMNS FROM `$tabel` LIKE '$kolom'");
if ($result && $result->num_rows > 0) {
$query = "DELETE FROM `$tabel` WHERE `$kolom` = $business_id";
error_log("? Uitvoeren query: $query");
if (!$mysqli->query($query)) {
error_log("? MYSQL ERROR bij $tabel: " . $mysqli->error);
}
} else {
error_log("?? Kolom '$kolom' bestaat niet in tabel '$tabel'");
}
}
$mysqli->query("DELETE FROM businesses WHERE id = $business_id");
}
// Overige user-data verwijderen
$mysqli->query("DELETE FROM user_locations WHERE user_id = $user_id");
if (kolomBestaat($mysqli, "appointments", "customer_id") && kolomBestaat($mysqli, "appointments", "staff_id")) {
$mysqli->query("DELETE FROM appointments WHERE customer_id = $user_id OR staff_id = $user_id");
}
if (kolomBestaat($mysqli, "transactions", "user_id")) {
$mysqli->query("DELETE FROM transactions WHERE user_id = $user_id");
}
$mysqli->query("DELETE FROM users WHERE id = $user_id");
$mysqli->commit();
$_SESSION['success'] = "Gebruiker #$user_id en alle gekoppelde gegevens zijn succesvol verwijderd.";
} catch (Exception $e) {
$mysqli->rollback();
error_log("FOUT: " . $e->getMessage());
$_SESSION['error'] = "Verwijderen mislukt: " . $e->getMessage();
}
}
header("Location: dashboard.php");
exit();
}
$query = "SELECT u.*,
(SELECT COUNT(*) FROM businesses b WHERE b.owner_id = u.id) AS business_count
FROM users u
WHERE u.role != 'admin' AND u.status != 'archived'";
$result = $mysqli->query($query);
require_once 'includes/header.php';
?>
Probeer ook de query zelf in je foutmelding te zetten.
Maar ik zie een heel rare methode om on the fly uit te vinden of je tabel wel een kolom X bevat, om daarna een delete query uit te voeren.
Ik vind dat je er vanuit mag gaan dat JIJ weet hoe je tabellen uitzien en dat deze dus echt die kolom bevat.
Dat mag je dus gewoon hard in je code zetten.
Als je kolom soms ontbreekt, is er iets anders grondig mis met je data-opbouw.
Je hebt de relaties in de database niet goed ingesteld. Je probeert nu in een while-loop alle records van een bepaalde business-ID te verwijderen.
<?php
// Selecteer `businesses.id`: de kolom `id` in de tabel `businesses`
$businessResult = $mysqli->query("SELECT id FROM businesses WHERE owner_id = $user_id");
while ($business = $businessResult->fetch_assoc()) {
// `$business_id` is één waarde van de kolom `businesses.id`
$business_id = $business['id'];
Die ‘mapping’ van de business-ID in andere tabellen is overbodig als je een ON DELETE CASCADE toevoegt aan tabeldefinities. Je hebt dan nog maar één DELETE-query nodig: die verwijdert vervolgens automatisch alle afhankelijke rijen met dezelfde ID uit andere tabellen.
ChatGPT begrijpt talen, maar doorziet abstractere logica zoals een genormaliseerd database-ontwerp vaak slecht. Je zult daarvoor echt zelf aan de bak moeten.
Dankjewel allemaal voor jullie feedback, maar ik snap het niet goed.
Als je kijkt naar mijn database design, dan zie je op 2 tabellen na, een ouder => kinderen constructie.
Ben benieuwd waarom deze constructie niet klopt?
En wat moet ik (laten) veranderen aan de bovenstaande query, zodat het optimaal is/werkt?
Bedoel je dat je in al die tabellen een kolom business_id hebt opgenomen?
Of bedoel je, wat Ward bedoelt, dat je ook aan de database verteld hebt dat het nummer dat in die kolom staat verwijst naar het ID in de tabel Businesses?
Als je dat namelijk doet, dan kan zo'n parent niet verwijderd worden zonder dat het kind eerst verwijderd wordt. óf je stelt die on-cascade in, en dan heeft verwijderen van een parent tot gevold dat al zijn kinderen ook verwijderd worden.
[size=xsmall]Toevoeging op 03/08/2025 22:14:04:[/size]
Voorbeeld:
CREATE TABLE businesses (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL UNIQUE,
business_type ENUM('kapper','schoonheidssalon') DEFAULT 'kapper',
address TEXT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
status ENUM('active','inactive') DEFAULT 'active',
owner_id BIGINT(20) UNSIGNED NOT NULL,
deleted_at DATETIME NULL,
PRIMARY KEY (id)
-- Eventueel FOREIGN KEY (owner_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE services (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
duration INT(11) NOT NULL,
price DECIMAL(10,2) NOT NULL,
deleted_at DATETIME NULL,
business_id BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_services_business
FOREIGN KEY (business_id) REFERENCES businesses(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
TEXT is trouwens wel erg groot om een adres in op te slaan.
En een INT kan 2,147,483,647 bevatten.
Dat lijkt me meer dan genoeg voor een beetje normale webapp. Maar goed: het is in elk geval voor de FK relaties belangrijk dat het type veld gelijk is voor parent en child.