Kontrola databáze#
Sada dotazů, které se pouštějí při nasazení portálu na existující databázi — typicky když se starší instalace povyšuje na novou verzi systému. Odhalí vady schématu, které se jinak projeví až za měsíce jako nevysvětlitelně zdvojený nebo prázdný text.
Všechno jsou to samé SELECTy. Nic nemění, dá se to pustit i na produkci.
Skript: sql/2026-09-03_audit_texts_tables_primary_keys.sql
Proč se to kontroluje#
Každá překladová tabulka *_texts má mít složený primární klíč:
Klíč není kosmetika — je to jediné, co brání tomu, aby jedna entita měla v jednom jazyce dva různé překlady. Kde chybí, skládají se tři problémy na sebe:
| Co chybí | Následek |
|---|---|
| primární klíč | databáze pouští duplicitní řádky |
| vynucení identity | Doctrine má v mapování složenou identitu, kterou DB nedrží — který řádek se přečte, nic negarantuje |
| unikátní index | ON DUPLICATE KEY UPDATE nemá s čím kolidovat a místo aktualizace tiše vloží další řádek |
Poslední bod je ten zákeřný: seedy jsou psané jako idempotentní, takže se pouštějí opakovaně — a každý běh duplicity přidá.
validateClass to nezachytí
SchemaValidator::validateClass porovnává sloupce a typy, ne indexy.
Chybějící primární klíč tedy projde všemi automatickými kontrolami. Proto
tenhle ruční audit existuje.
Kdy ho pustit#
- Před nasazením nové verze na starší databázi — ať se ví, co se dědí.
- Po zásahu do schématu, který zakládal nebo měnil tabulky
*_texts. - Při vyšetřování hlášení typu „na webu se ukazuje jiný text než v administraci“ nebo „překlad se po uložení vrátil na starou hodnotu“.
1. Které tabulky nemají jazyk v primárním klíči#
SELECT t.TABLE_NAME AS tabulka,
COALESCE(GROUP_CONCAT(k.COLUMN_NAME ORDER BY k.SEQ_IN_INDEX),
'(žádný PK)') AS primarni_klic
FROM information_schema.TABLES t
LEFT JOIN information_schema.STATISTICS k
ON k.TABLE_SCHEMA = t.TABLE_SCHEMA
AND k.TABLE_NAME = t.TABLE_NAME
AND k.INDEX_NAME = 'PRIMARY'
WHERE t.TABLE_SCHEMA = DATABASE()
AND t.TABLE_NAME LIKE '%\_texts'
AND t.TABLE_NAME NOT LIKE 'bak\_%'
AND t.TABLE_NAME NOT LIKE 'tmp\_%'
GROUP BY t.TABLE_NAME
HAVING primarni_klic NOT LIKE '%cms_system_languages_language_id%'
ORDER BY t.TABLE_NAME;
Zdravá databáze vrátí 0 řádků.
Zálohové a dočasné tabulky (bak_, tmp_) se vynechávají schválně — kopie klíč
mít nemají a jen by výpis zaplevelily.
2. Duplicitní překlady#
Jedním dotazem to nejde: každá tabulka má jinak pojmenovaný FK sloupec. Tenhle dotaz proto vygeneruje hotové dotazy — výstup zkopírujte a spusťte.
SELECT CONCAT(
'SELECT ''', t.TABLE_NAME, ''' AS tabulka, `', c.COLUMN_NAME,
'` AS entita, cms_system_languages_language_id AS jazyk, ',
'COUNT(*) AS pocet FROM `', t.TABLE_NAME,
'` GROUP BY 1,2,3 HAVING pocet > 1;'
) AS spust_tohle
FROM information_schema.TABLES t
JOIN information_schema.COLUMNS c
ON c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.TABLE_NAME = t.TABLE_NAME
AND c.COLUMN_NAME LIKE '%\_id'
AND c.COLUMN_NAME <> 'cms_system_languages_language_id'
LEFT JOIN information_schema.STATISTICS k
ON k.TABLE_SCHEMA = t.TABLE_SCHEMA
AND k.TABLE_NAME = t.TABLE_NAME
AND k.INDEX_NAME = 'PRIMARY'
WHERE t.TABLE_SCHEMA = DATABASE()
AND t.TABLE_NAME LIKE '%\_texts'
AND t.TABLE_NAME NOT LIKE 'bak\_%'
GROUP BY t.TABLE_NAME, c.COLUMN_NAME
HAVING COALESCE(GROUP_CONCAT(k.COLUMN_NAME), '')
NOT LIKE '%cms_system_languages_language_id%'
ORDER BY t.TABLE_NAME;
3. Úplnost jazyků#
Také generátor dotazů — spočítá řádky na jazyk pro každou překladovou tabulku.
SELECT CONCAT(
'SELECT ''', TABLE_NAME, ''' AS tabulka, ',
'SUM(cms_system_languages_language_id=1) AS cs, ',
'SUM(cms_system_languages_language_id=2) AS en, ',
'SUM(cms_system_languages_language_id=3) AS sk ',
'FROM `', TABLE_NAME, '`'
) AS spust_tohle
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND COLUMN_NAME = 'cms_system_languages_language_id'
AND TABLE_NAME LIKE '%\_texts'
AND TABLE_NAME NOT LIKE 'bak\_%'
ORDER BY TABLE_NAME;
Pořadí kroků není libovolné
Nejdřív ukliďte duplicity (krok 2), teprve pak počítejte chybějící překlady (krok 3). Tabulka s duplicitami vypadá, jako by v ní chyběl jazyk — ve skutečnosti je v ní jiný jazyk navíc.
Živý příklad z revize 2026-09-03: typy notifikací hlásily 12/12/9, což vypadá
na tři chybějící slovenské překlady. Po odstranění duplicit vyšlo 9/9/9 —
nechyběl žádný, jen tam čeština a angličtina ležely čtyřikrát.
Jak číst výsledek kroku 3#
Nerovnost počtů není sama o sobě vada. Rozlišujte dva druhy tabulek:
| Druh | Tabulky | Nerovnost znamená |
|---|---|---|
| systémové číselníky | privilegia, skupiny privilegií, typy notifikací, uživatelské skupiny, routy | díra — v administraci se místo textu zobrazí prázdno |
| redakční obsah | cms_system_image_texts, cms_system_file_texts, cms_mod_blog_intro_texts |
normální stav — popisek obrázku prostě nikdo nepřeložil |
Co dělat s nálezem#
- Zálohovat dotčené tabulky.
ALTER TABLEnejde vrátit transakcí. - Uklidit duplicity. Většinou jsou to buď bajtově shodné kopie (nechte jednu), nebo prázdný neaktivní řádek vedle vyplněného (smažte prázdný).
- Doplnit primární klíč.
- Ověřit, že
ON DUPLICATE KEY UPDATEteď opravdu aktualizuje — pusťte týž seed dvakrát, počet řádků se nesmí změnit.
Vzor obojího je sql/2026-09-03_04_texts_tables_missing_primary_keys.sql
(oprava) a sql/2026-09-03_96_..._rollback.sql (návrat ze záloh).
Duplicita, která se nedá vyřešit automaticky
Když má jedna entita v jednom jazyce dvě různé neprázdné hodnoty, není to technické rozhodnutí — někdo musí říct, která je správná.
V revizi 2026-09-03 měla routa 13 (expressExecute) dvě anglické hodnoty,
zatímco routa 12 (execute) neměla angličtinu žádnou, ačkoli češtinu
i slovenštinu ano. Text tam přistál omylem a patřil k dvanáctce — přesunem se
odstranila duplicita i díra v překladu naráz. Bez pohledu na sousední
záznamy by to nešlo poznat.
Související#
- Překlady — obě překladové vrstvy a čím se liší
- Checklist nasazení — kam tenhle audit v postupu patří
- Testy a nástroje — co automatické kontroly pokrývají