SQL erweitert und Bruno hinzugefügt

This commit is contained in:
Sven Riwoldt
2026-07-06 11:32:17 +02:00
parent 488426b1aa
commit eec5516e14
10 changed files with 426 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
# MySQL Binary Search Fehlerhafte Zeile automatisch finden
## Hintergrund
Bei der Migration von Daten zwischen zwei Tabellen treten gelegentlich Fehler auf. Mit `LIMIT` und `OFFSET` lässt sich der fehlerhafte Bereich manuell eingrenzen dieser Prozess kann jedoch durch einen Binary-Search-Ansatz vollständig automatisiert werden.
---
## Einfaches Skript (SELECT *)
Für ein simples `SELECT *` ohne komplexe Query:
```bash
#!/bin/bash
# Konfiguration
DB_USER="root"
DB_PASS="password"
DB_NAME="datenbank"
SOURCE_TABLE="quelle"
TARGET_TABLE="ziel"
# Gesamtanzahl Zeilen ermitteln
TOTAL=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -sNe \
"SELECT COUNT(*) FROM $SOURCE_TABLE;")
echo "Gesamt Zeilen: $TOTAL"
LOW=0
HIGH=$TOTAL
while [ $((HIGH - LOW)) -gt 1 ]; do
MID=$(( (LOW + HIGH) / 2 ))
FETCH=$((MID - LOW))
echo "Teste OFFSET=$LOW LIMIT=$FETCH ..."
ERROR=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" 2>&1 <<EOF
INSERT INTO $TARGET_TABLE
SELECT * FROM $SOURCE_TABLE
LIMIT $FETCH OFFSET $LOW;
EOF
)
if echo "$ERROR" | grep -qi "error"; then
HIGH=$MID
echo " → Fehler gefunden, eingrenzen bis Zeile $HIGH"
else
LOW=$MID
echo " → OK, weiter ab Zeile $LOW"
fi
done
echo ""
echo "=== Fehlerhafte Zeile gefunden ==="
echo "OFFSET: $LOW"
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"SELECT * FROM $SOURCE_TABLE LIMIT 1 OFFSET $LOW;"
```
---
## Erweitertes Skript (komplexe Query mit JOINs, MAX etc.)
Wenn die Source-Query komplex ist (JOINs, Aggregatfunktionen, GROUP BY), wird sie als Variable definiert und als Subquery genutzt.
> **Wichtig:** Ein `ORDER BY` ist zwingend erforderlich, damit die Zeilenreihenfolge zwischen den Aufrufen deterministisch bleibt.
```bash
#!/bin/bash
# Konfiguration
DB_USER="root"
DB_PASS="password"
DB_NAME="datenbank"
# Deine Source-Query OHNE LIMIT/OFFSET als Subquery nutzbar
SOURCE_QUERY="SELECT a.id, MAX(b.wert), a.name
FROM quelle a
JOIN andere b ON a.id = b.ref_id
GROUP BY a.id, a.name
ORDER BY a.id"
TARGET_TABLE="ziel"
# Gesamtanzahl Zeilen der Query ermitteln
TOTAL=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -sNe \
"SELECT COUNT(*) FROM ($SOURCE_QUERY) AS _count_query;")
echo "Gesamt Zeilen: $TOTAL"
LOW=0
HIGH=$TOTAL
while [ $((HIGH - LOW)) -gt 1 ]; do
MID=$(( (LOW + HIGH) / 2 ))
FETCH=$((MID - LOW))
echo "Teste OFFSET=$LOW LIMIT=$FETCH ..."
ERROR=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" 2>&1 <<EOF
INSERT INTO $TARGET_TABLE
$SOURCE_QUERY
LIMIT $FETCH OFFSET $LOW;
EOF
)
if echo "$ERROR" | grep -qi "error"; then
HIGH=$MID
echo " → Fehler, eingrenzen bis Zeile $HIGH"
else
LOW=$MID
echo " → OK, weiter ab Zeile $LOW"
fi
done
echo ""
echo "=== Fehlerhafte Zeile ==="
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"$SOURCE_QUERY LIMIT 1 OFFSET $LOW;"
```
---
## Gezielten Wert aus der fehlerhaften Zeile ausgeben
### Variante 1 Subquery-Wrapping
```bash
echo "=== Fehlerhafte Zeile ==="
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"SELECT a.id, a.name FROM ($SOURCE_QUERY) AS _fehler LIMIT 1 OFFSET $LOW;"
```
### Variante 2 Direkte Query (empfohlen bei Aggregaten)
```bash
echo "=== Fehlerhafte Zeile Detailansicht ==="
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"SELECT
a.id AS 'ID',
a.name AS 'Name',
MAX(b.wert) AS 'Max-Wert'
FROM quelle a
JOIN andere b ON a.id = b.ref_id
GROUP BY a.id, a.name
ORDER BY a.id
LIMIT 1 OFFSET $LOW;"
```
Variante 2 ist bei `MAX()` und anderen Aggregatfunktionen übersichtlicher, da die Rohwerte direkt aus der Query kommen und nicht erst durch ein Subquery-Wrapping gehen.
---
## Hinweise
- **ORDER BY** ist Pflicht für deterministische Ergebnisse bei wiederholten Aufrufen.
- Die erfolgreichen INSERT-Blöcke bleiben in der Zieltabelle erhalten bei Bedarf kann jeder Block in eine Transaktion mit `ROLLBACK` bei Fehler gekapselt werden.
- Der Binary Search reduziert die Anzahl der Testläufe auf `log₂(n)` bei 1000 Zeilen also maximal 10 Schritte.