JSON Toolkit

JSON do SQL: příkazy INSERT, návrh tabulek a úskalí mapování typů

Shrnutí: Převod JSON na SQL INSERT skrývá rozdělování vnořených tabulek, NULL versus chybějící hodnotu, escapování a rozdíly mezi dialekty. Spustitelné příkazy a srovnávací tabulky pro každou past.

Středně pokročilý~10 min čteníPublikováno 2026-10-31Aktualizováno 2026-10-31
Titulní obrázek průvodce JSON do SQL INSERT a návrhem tabulek

Stručně: než navrhnete tabulky, rozhodněte, jakou obchodní entitu představuje jeden řádek, a poté napište explicitní víceřádkový VALUES uvnitř jedné transakce; názvy polí a mapování typů vyřešte před importem, místo abyste je záplatovali v SQL.

JSON popisuje tvar; SQL popisuje také omezení. Když přenesete první do druhého, každé omezení, které jste nezapsali, se později vrátí jako špinavá data.

1. Rozhodněte nejprve: jedna tabulka, nebo jeden záznam na řádek JSON

Podívejte se na nejvyšší vrstvu dat před návrhem jakékoli tabulky. Špatné rozhodnutí znamená, že každý následující INSERT bude nutné přepsat.

Struktura JSON Navržený design tabulky Hlavní riziko
Ploché pole objektů [{...},{...}] Jedna tabulka, mnoho řádků Nekonzistentní klíče mezi záznamy nechají většinu sloupců sjednocení NULL
Objekt obalující pole {"data":[...],"total":100} Jedna tabulka, vezměte jen data Zápis total do každého řádku posouvá význam pole
Vnořený objekt {"user":{"name":"x"}} Rodičovská tabulka plus zploštěné sloupce, nebo rodič plus související tabulka Nekontrolovaná expanze, duplikované názvy sloupců
Pole jako pole {"tags":["a","b"]} Podřízená tabulka, nebo sloupec JSON/TEXT Vztah jedna k mnoha nacpaný do jedné buňky už nelze dotazovat podle tagu
Nekonzistentní klíče Sloupce sjednocení, nebo rozdělení tabulek podle typu „Chybějící klíč“ a „hodnota je null“ se slijí do stejného NULL

Kritérium je jen jedno: jakou obchodní entitu představuje jeden řádek? Pokud je řádek objednávkou, položky musí žít ve vlastní tabulce. Pokud je řádek uživatelem, tagy patří do podřízené tabulky nebo sloupce JSON, nikoli do řetězce spojeného čárkami.

2. Od pole k INSERT: nejmenší spustitelná šablona

Nejbezpečnější forma vždy uvádí názvy sloupců explicitně a nikdy se nespoléhá na pořadí sloupců:

CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT UNIQUE
);

INSERT INTO users (id, name, email) VALUES
  (1, 'Ada', 'ada@example.com'),
  (2, 'Linus', NULL);

Důležité jsou tři detaily:

  • Napište každý název sloupce. Když schéma později získá nový sloupec, nic se neposune do špatného pole.
  • Víceřádkový VALUES funguje v SQLite, PostgreSQL i MySQL a ve srovnání se smyčkou jednorádkových insertů ušetří velké množství round tripů.
  • NULL je klíčové slovo; nedávejte ho do uvozovek. Zápis 'NULL' uloží čtyřznakový řetězec.

Pokud klíče JSON neodpovídají názvům sloupců (userId versus user_id), přidejte mapovací vrstvu. Nehádejte pomocí COALESCE uvnitř SQL. Oprava názvů polí v datové vrstvě je mnohem levnější než záplatování v dotazech.

3. Vnořené objekty: rodičovská tabulka plus podřízená tabulka

Objednávky jsou klasickým případem jedna k mnoha:

INSERT INTO orders (id, customer_name, total_cents) VALUES
  (1001, 'Ada', 12900);

INSERT INTO order_items (order_id, sku, quantity) VALUES
  (1001, 'A-1', 2),
  (1001, 'B-7', 1);

To odpovídá JSON {"id":1001,"customer":"Ada","total":129.00,"items":[{"sku":"A-1","quantity":2},{"sku":"B-7","quantity":1}]}.

Klíčové body:

  • Délka pole items určuje počet podřízených řádků; rodičovská tabulka dostane přesně jeden řádek.
  • Peníze ukládejte jako celé centy (total_cents), nikdy jako FLOAT.
  • Podřízené tabulce dejte cizí klíč order_id a oba inserty potvrďte v jedné transakci, jinak může rodič existovat, zatímco jeho položky chybí.
  • Pokud jsou data jen pro čtení a nikdo nefiltruje podle položky, je přijatelný sloupec JSON (PostgreSQL jsonb, MySQL JSON nebo SQLite TEXT s funkcemi json1), ale přijměte cenu: žádné indexování a neohrabané dotazy.

Pevné vnořené objekty jako user.address zploštěte do sloupců (address_city, address_zip). Rozdělte do tabulky jen tehdy, když vnoření může dál růst; dělení pevného tvaru jen přidává JOINy.

4. NULL versus chybějící pole: SQL nemá „chybějící klíč“

JSON umožňuje, aby klíč úplně chyběl. Sloupce SQL jsou pevné. Po importu oba případy vypadají stejně – NULL – i když mohou znamenat něco jiného:

Situace v JSON Co databáze ukazuje Doporučené zacházení
Klíč chybí NULL Přidejte výchozí hodnotu při definici tabulky, nebo sloupec s příznakem _present
Klíč existuje s null NULL Znamená, že uživatel hodnotu explicitně vymazal
Klíč existuje s prázdným řetězcem '' Rozlišitelné od NULL, ale v dotazech snadno zaměnitelné
Chybí číselné pole NULL Nikdy nenechte 0 předstírat „chybějící“

Dvě časté chyby:

  1. Použití prázdného řetězce místo NULL. Později WHERE email IS NULL tyto řádky mine a vaše metrika „uživatelé bez e-mailu“ tiše podpočítá.
  2. Testování pomocí col = NULL. V tříhodnotové logice SQL se NULL = NULL vyhodnotí jako NULL, nikoli jako TRUE. Musíte napsat IS NULL:
SELECT COUNT(*) FROM users WHERE email IS NULL;

Pokud potřebujete uchovat, zda klíč vůbec existoval, přidejte booleovský sloupec s příznakem, místo abyste to odvozovali z NULL. Smlouva API by to měla uvádět také; viz Návrh JSON API a pojmenování polí.

5. Citování a escapování: nikdy neskládejte SQL řetězce

Řetězce JSON mohou obsahovat jednoduché uvozovky, dvojité uvozovky, zpětná lomítka, zalomení řádků a dokonce \u0000. U ručně sestavených řetězců jeden vynechaný escape posune zbytek příkazu a v nejhorším případě se stane SQL injection.

Ve standardním SQL se jednoduchá uvozovka escapuje zdvojením:

INSERT INTO notes (id, content) VALUES (1, 'It''s a ''quoted'' value');

Produkční kód by měl používat vazbu parametrů a nechat ovladač řešit escapování a typy:

// PostgreSQL používá $1/$2, MySQL ?; tento příklad používá node-postgres
await client.query('INSERT INTO users (id, name) VALUES ($1, $2)', [row.id, row.name]);

Ručně escapujte jen tehdy, když musíte exportovat soubor .sql, a to minimálně:

  • nahraďte ' za '';
  • pokud zachováváte zalomení řádků, ověřte, že cílový databázový systém přijímá víceřádkové řetězcové literály;
  • odstraňte \u0000, protože většina databází NUL uvnitř řetězců odmítá;
  • MySQL ve výchozím nastavení považuje zpětné lomítko za escape znak, takže \ se musí stát \\, nebo přepněte do standardního režimu.

Dvojité uvozovky z JSON nepotřebují uvnitř SQL řetězce v jednoduchých uvozovkách žádné escapování. Přidání další vrstvy escapování je způsob, jak do dat dostanete bloudící zpětná lomítka.

6. Mapování typů: kam v SQL přistanou šest hodnot JSON

JSON má jen řetězec, číslo, boolean, null, objekt a pole. SQL má mnohem jemnější typový systém. Chyby se obvykle stávají v přesnosti a sémantice, nikoli v syntaxi:

Hodnota JSON Navržený typ SQL Kde se to láme
Celé číslo INTEGER / BIGINT Nad 2^53 už strana JavaScriptu přesnost ztratila, takže i uložená hodnota je špatná
Desetinné číslo, peníze NUMERIC / DECIMAL FLOAT je binární plovoucí desetinná čárka; 0.1 + 0.2 není přesné
Řetězec TEXT / VARCHAR(n) Limity délky zkracují; kolace mění porovnávání
Boolean BOOLEAN / TINYINT(1) SQLite nemá booleovský typ a ukládá 0/1
null NULL Nerozlišitelné od chybějícího pole
Řetězec s datem DATE / TIMESTAMP / TIMESTAMPTZ Ztracené časové pásmo, zkrácené milisekundy, neplatné formáty
Řetězec s výčtem TEXT s CHECK, nebo tabulka výčtu Rozdíly ve velikosti písmen a špinavé hodnoty nemají omezení
Objekt / pole Podřízená tabulka, nebo JSON / JSONB Uložené jako řetězec nelze indexovat ani dotazovat

Booleany skrývají nejjemnější chybu: JSON false se v MySQL TINYINT(1) stane 0, obousměrný převod vypadá dobře a nikdo si nevšimne. Ale ve chvíli, kdy nějaký kód udělá if (value) na řetězci "false", vyhodnotí se jako pravda. Převodní skripty by měly volat Boolean(value) explicitně.

PostgreSQL JSONB parsuje a odstraňuje duplicitní klíče, přičemž ponechá poslední výskyt, což se hodí pro dotazování; JSON zachovává původní text a pořadí klíčů. SQLite JSON je jen TEXT plus sada funkcí, bez skutečného typového omezení. Před volbou se zeptejte, zda tato tabulka potřebuje filtrování podle polí uvnitř JSON.

7. Hromadné inserty a transakce: dávkujte, potvrďte, udělejte opakovatelné

S rostoucím objemem rozhodují o kontroleelnosti importu tři věci:

  1. Dávkování. Smyčka N jednorázových INSERT stojí N síťových round tripů. Spojte 500 až 1000 řádků do jednoho víceřádkového VALUES, nebo použijte vlastní hromadný zavaděč databáze (PostgreSQL COPY, MySQL LOAD DATA, víceřádkové inserty uvnitř transakce SQLite).
  2. Použijte transakci. Vše, nebo nic, aby polovičně načtená tabulka nikdy nepřežila chybu:
BEGIN;
INSERT INTO users (id, name) VALUES (1, 'Ada'), (2, 'Linus');
INSERT INTO order_items (order_id, sku, quantity) VALUES (1, 'A-1', 2);
COMMIT;
  1. Udělejte ji idempotentní. Importní skripty se neustále spouštějí znovu. Místo delete-then-insert použijte upsert:
Databáze Idempotentní forma
PostgreSQL / SQLite INSERT ... ON CONFLICT (id) DO UPDATE SET ...
MySQL INSERT ... ON DUPLICATE KEY UPDATE ...

ON CONFLICT vyžaduje jedinečné omezení nebo primární klíč, jinak se nikdy nespustí. Pokud neexistuje přirozený klíč, vytvořte jedinečný index na obchodním klíči, například email. Když není žádný klíč, přidejte na straně JSON stabilní id – nikdy se pro idempotenci nespoléhejte na auto-increment id, protože opakované spuštění vloží duplikáty.

8. Rozdíly mezi dialekty na jednom místě

Možnost SQLite PostgreSQL MySQL
Víceřádkový VALUES Ano Ano Ano
Upsert ON CONFLICT ON CONFLICT ON DUPLICATE KEY UPDATE
Auto-increment klíč INTEGER PRIMARY KEY GENERATED ... AS IDENTITY AUTO_INCREMENT
Booleovský typ Žádný, ukládá 0/1 boolean TINYINT(1)
Hromadný import Víceřádkové inserty v transakci COPY LOAD DATA
Podpora JSON TEXT plus funkce json1 json / jsonb JSON
Citování identifikátorů Dvojité uvozovky Dvojité uvozovky Zpětné apostrofy (výchozí)

Aby jeden SQL soubor fungoval ve všech třech, vyhněte se alespoň ON CONFLICT, zpětným apostrofům a SERIAL. U projektů napříč databázemi generujte schéma a upsert příkazy zvlášť; nečekejte, že jeden skript bude sedět všude.

9. Tři kontroly po importu

Než smažete zdrojový soubor, ověřte tři věci:

  1. Počet řádků. SELECT COUNT(*) FROM users; se má rovnat délce pole JSON. Chybějící řádek obvykle znamená, že poslední záznam byl zkrácen.
  2. Sloupec s nejvyšším podílem NULL. Ukazuje na nekonzistentní názvy klíčů nebo nerozbalené vnoření. Vraťte se a zkontrolujte, zda zdroj nepoužívá userId i user_id.
  3. Peníze a dlouhé identifikátory. Vyberte maximální a minimální hodnotu a potvrďte, že je nezkrátil ani plovoucí desetinná čárka, ani převod na celé číslo.

Pokud nejprve potřebujete pochopit tabulkový tvar, použijte postup zploštění v článku Převod JSON, CSV a tabulkových dat. Pokud jsou publikem kolegové, kteří chtějí data jen číst, čtyři způsoby převodu JSON do Excelu dají méně práce než postavit databázi.

Proč čísla po importu JSON do SQL ztratí přesnost?

Celá čísla nad 2^53 už přesnost ztratila během JSON.parse v JavaScriptu a zápis do BIGINT ji nemůže obnovit. Taková id mají být v JSON řetězce a během importu se mají převést na BIGINT nebo TEXT.

Co použít jako primární klíč, když JSON nemá id?

Nechte databázi vygenerovat jeden, nebo před importem vytvořte stabilní UUID. Nikdy nepoužívejte index pole jako id: opakované spuštění změní pořadí dat a namíří špatné řádky na špatné záznamy. Jedinečný index na obchodním klíči (e-mail, číslo objednávky) také umožní upserty.

Co dělat, když hromadný INSERT narazí na konflikt jedinečného klíče?

Nahraďte prostý INSERT odpovídajícím upsertem pro váš dialekt a ověřte, že konfliktní sloupec má jedinečné omezení. Smazání a následné vložení také funguje, ale musí proběhnout v jedné transakci a přijímáte krátké období bez řádků.

Potřebují vnořená pole vždy podřízenou tabulku?

Ne. Pro zobrazení jen pro čtení bez dotazování podle podřízených položek je sloupec JSON jednodušší. Jakmile potřebujete filtrovat nebo agregovat podle tagu či SKU, udržitelnou volbou je podřízená tabulka s indexy. Rozhodující je, jak dotazujete, nikoli jak jsou data tvarovaná.