JSON kepada SQL: Pernyataan INSERT, Reka Bentuk Jadual, dan Perangkap Pemetaan Jenis
Inti pati: Menukar JSON menjadi SQL INSERT menyembunyikan pemecahan jadual bersarang, NULL berbanding tiada, escaping, dan jurang dialek. Pernyataan yang boleh dijalankan serta jadual perbandingan bagi setiap perangkap.

Kesimpulan utama: tentukan entiti perniagaan yang diwakili oleh satu baris sebelum mereka bentuk jadual, kemudian tulis VALUES berbilang baris yang eksplisit di dalam satu transaksi; selesaikan penamaan medan dan pemetaan jenis sebelum import dan bukannya menampal-nampal dalam SQL.
JSON menerangkan bentuk; SQL juga menerangkan kekangan. Apabila anda memindahkan yang pertama ke dalam yang kedua, setiap kekangan yang tidak anda tuliskan akan kembali kemudian sebagai data kotor.
1. Tentukan dahulu: satu jadual atau satu rekod bagi setiap baris JSON
Lihat lapisan paling luar data sebelum mereka bentuk apa-apa jadual. Salah keputusan ini dan setiap INSERT selepasnya perlu ditulis semula.
| Struktur JSON | Cadangan reka bentuk jadual | Risiko utama |
|---|---|---|
Tatasusunan objek rata [{...},{...}] |
Satu jadual, banyak baris | Kunci tidak konsisten merentas rekod meninggalkan kebanyakan lajur gabungan sebagai NULL |
Objek membaluti tatasusunan {"data":[...],"total":100} |
Satu jadual, ambil data sahaja |
Menulis total ke dalam setiap baris menyalah tempat makna medan itu |
Objek bersarang {"user":{"name":"x"}} |
Jadual induk ditambah lajur yang diratakan, atau induk ditambah jadual berkaitan | Pengembangan tidak terkawal, nama lajur berganda |
Medan tatasusunan {"tags":["a","b"]} |
Jadual anak, atau lajur JSON/TEXT | Hubungan satu-ke-banyak yang dihimpit ke dalam satu sel dan tidak lagi boleh ditanya mengikut tag |
| Kunci tidak konsisten | Lajur gabungan, atau pecahkan jadual mengikut jenis | "Kunci tiada" dan "nilai null" runtuh menjadi NULL yang sama |
Hanya ada satu kriteria: entiti perniagaan apa yang diwakili oleh satu baris? Jika satu baris ialah pesanan, item baris mesti berada dalam jadualnya sendiri. Jika satu baris ialah pengguna, tag tergolong dalam jadual anak atau lajur JSON, bukan dalam rentetan yang digabungkan dengan koma.
2. Daripada tatasusunan kepada INSERT: templat terkecil yang boleh dijalankan
Bentuk paling selamat sentiasa menyenaraikan nama lajur secara eksplisit dan tidak sekali-kali bergantung pada susunan lajur:
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);
Tiga perincian penting:
- Tulis setiap nama lajur. Apabila skema kemudian mendapat lajur baharu, tiada apa yang beralih ke medan yang salah.
VALUESberbilang baris berfungsi dalam SQLite, PostgreSQL, dan MySQL, dan menjimatkan sejumlah besar perjalanan pergi balik berbanding gelung sisipan satu baris.NULLialah kata kunci; jangan petiknya. Menulis'NULL'menyimpan rentetan empat aksara.
Jika kunci JSON tidak sepadan dengan nama lajur (userId berbanding user_id), tambah lapisan pemetaan. Jangan meneka dengan COALESCE di dalam SQL. Membetulkan nama medan dalam lapisan data jauh lebih murah daripada menampal-nampal dalam pertanyaan.
3. Objek bersarang: jadual induk ditambah jadual anak
Pesanan ialah kes satu-ke-banyak klasik:
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);
Ini sepadan dengan JSON {"id":1001,"customer":"Ada","total":129.00,"items":[{"sku":"A-1","quantity":2},{"sku":"B-7","quantity":1}]}.
Perkara penting:
- Panjang tatasusunan
itemsmenentukan bilangan baris anak; jadual induk mendapat tepat satu baris. - Simpan wang sebagai sen integer (
total_cents), jangan sekali-kali sebagaiFLOAT. - Berikan jadual anak kunci asing
order_iddan commit kedua-dua sisipan dalam satu transaksi, jika tidak induk boleh wujud manakala itemnya hilang. - Jika data itu baca-sahaja dan tiada siapa menapis mengikut item baris, lajur JSON boleh diterima (PostgreSQL
jsonb, MySQLJSON, atau SQLiteTEXTdengan fungsi json1), tetapi terima kosnya: tiada pengindeksan dan pertanyaan yang janggal.
Ratakan objek bersarang tetap seperti user.address menjadi lajur (address_city, address_zip). Pecahkan kepada jadual hanya apabila sarang boleh terus berkembang; memecahkan bentuk tetap hanya menambah JOIN.
4. NULL berbanding medan tiada: SQL tiada "kunci tidak hadir"
JSON membenarkan kunci tidak hadir sepenuhnya. Lajur SQL adalah tetap. Selepas import kedua-dua kes kelihatan serupa — NULL — walaupun ia boleh bermakna perkara berbeza:
| Situasi JSON | Apa yang pangkalan data tunjukkan | Pengendalian yang disyorkan |
|---|---|---|
| Kunci tidak hadir | NULL |
Tambah nilai lalai pada definisi jadual, atau lajur penanda _present |
Kunci wujud dengan null |
NULL |
Bermaksud pengguna telah mengosongkan nilai itu secara eksplisit |
| Kunci wujud dengan rentetan kosong | '' |
Boleh dibezakan daripada NULL, tetapi mudah dikelirukan dalam pertanyaan |
| Medan berangka tiada | NULL |
Jangan sekali-kali biarkan 0 berpura-pura sebagai "tiada" |
Dua kesilapan lazim:
- Menggunakan rentetan kosong dan bukan NULL. Kemudian,
WHERE email IS NULLterlepas baris tersebut, dan metrik "pengguna tanpa e-mel" anda mengira kurang secara senyap. - Menguji dengan
col = NULL. Dalam logik tiga nilai SQL,NULL = NULLdinilai sebagaiNULL, bukanTRUE. Anda mesti menulisIS NULL:
SELECT COUNT(*) FROM users WHERE email IS NULL;
Jika anda perlu mengekalkan sama ada kunci itu wujud sejak awal, tambah lajur penanda boolean dan jangan simpulkan daripada NULL. Kontrak API juga harus menyatakan perkara ini; lihat Reka bentuk API JSON dan penamaan medan.
5. Petikan dan escaping: jangan sekali-kali menggabungkan rentetan SQL
Rentetan JSON boleh mengandungi petikan tunggal, petikan berganda, garis miring terbalik, baris baharu, dan juga \u0000. Dengan rentetan yang dibina dengan tangan, satu escape yang terlepas mengalihkan seluruh pernyataan, dan dalam kes terburuk menjadi suntikan SQL.
Dalam SQL standard, petikan tunggal di-escape dengan menggandakannya:
INSERT INTO notes (id, content) VALUES (1, 'It''s a ''quoted'' value');
Kod pengeluaran harus menggunakan pengikatan parameter dan biarkan pemacu mengendalikan escaping serta jenis:
// PostgreSQL uses $1/$2, MySQL uses ?; this example uses node-postgres
await client.query('INSERT INTO users (id, name) VALUES ($1, $2)', [row.id, row.name]);
Escape dengan tangan hanya apabila anda mesti mengeksport fail .sql, dan pada masa itu sekurang-kurangnya:
- ganti
'dengan''; - jika baris baharu dikekalkan, sahkan pangkalan data sasaran menerima literal rentetan berbilang baris;
- buang
\u0000, kerana kebanyakan pangkalan data menolak NUL di dalam rentetan; - MySQL melayan garis miring terbalik sebagai aksara escape secara lalai, jadi
\mesti menjadi\\, atau beralih kepada mod standard.
Tanda petikan berganda daripada JSON tidak memerlukan escaping di dalam rentetan SQL berpetikan tunggal. Menambah lapisan escaping tambahan ialah cara garis miring terbalik yang terpesong berakhir dalam data anda.
6. Pemetaan jenis: di mana enam nilai JSON mendarat dalam SQL
JSON hanya mempunyai rentetan, nombor, boolean, null, objek, dan tatasusunan. SQL mempunyai sistem jenis yang jauh lebih halus. Kesilapan biasanya berlaku dalam ketepatan dan semantik dan bukan sintaks:
| Nilai JSON | Cadangan jenis SQL | Di mana ia rosak |
|---|---|---|
| Integer | INTEGER / BIGINT |
Melebihi 2^53 bahagian JavaScript sudah kehilangan ketepatan, jadi nilai yang disimpan juga salah |
| Perpuluhan, wang | NUMERIC / DECIMAL |
FLOAT ialah titik terapung binari; 0.1 + 0.2 tidak tepat |
| Rentetan | TEXT / VARCHAR(n) |
Had panjang memotong; penyusunan mengubah perbandingan |
| Boolean | BOOLEAN / TINYINT(1) |
SQLite tiada jenis boolean dan menyimpan 0/1 |
| null | NULL |
Tidak dapat dibezakan daripada medan yang tiada |
| Rentetan tarikh | DATE / TIMESTAMP / TIMESTAMPTZ |
Zon waktu hilang, milisaat dipotong, format tidak sah |
| Rentetan enum | TEXT dengan CHECK, atau jadual enum |
Perbezaan huruf besar-kecil dan nilai kotor tiada kekangan |
| Objek / tatasusunan | Jadual anak, atau JSON / JSONB |
Disimpan sebagai rentetan ia tidak boleh diindeks atau ditanya |
Boolean menyembunyikan bug paling halus: false JSON menjadi 0 dalam MySQL TINYINT(1), perjalanan pergi balik kelihatan baik, dan tiada siapa perasan. Tetapi sebaik sahaja sesuatu kod melakukan if (value) pada rentetan "false", ia dinilai sebagai benar. Skrip penukaran harus memanggil Boolean(value) secara eksplisit.
JSONB PostgreSQL menghurai dan menyahduplikasi kunci, mengekalkan kejadian terakhir, yang sesuai untuk pertanyaan; JSON mengekalkan teks asal dan susunan kunci. SQLite JSON hanyalah TEXT tambah satu set fungsi, tanpa kekangan jenis sebenar. Sebelum memilih, tanya sama ada jadual ini memerlukan penapisan pada medan di dalam JSON.
7. Sisipan pukal dan transaksi: kelompokkan, commit, jadikan boleh dijalankan semula
Apabila volum bertambah, tiga perkara menentukan sama ada import kekal terkawal:
- Kelompokkan. Gelung N pernyataan
INSERTsatu baris berharga N perjalanan pergi balik rangkaian. Gabungkan 500 hingga 1000 baris menjadi satuVALUESberbilang baris, atau gunakan pemuat pukal pangkalan data itu sendiri (COPYPostgreSQL,LOAD DATAMySQL, sisipan berbilang baris di dalam transaksi SQLite). - Gunakan transaksi. Semua atau tiada, supaya jadual yang separuh dimuatkan tidak pernah bertahan selepas kesilapan:
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;
- Jadikan ia idempoten. Skrip import sentiasa dijalankan semula. Gunakan upsert dan bukan padam-kemudian-sisip:
| Pangkalan data | Bentuk idempoten |
|---|---|
| PostgreSQL / SQLite | INSERT ... ON CONFLICT (id) DO UPDATE SET ... |
| MySQL | INSERT ... ON DUPLICATE KEY UPDATE ... |
ON CONFLICT memerlukan kekangan unik atau kunci utama, jika tidak ia tidak pernah tercetus. Apabila tiada kunci semula jadi, bina indeks unik pada kunci perniagaan seperti email. Apabila tiada kunci sama sekali, tambah id yang stabil di sebelah JSON — jangan sekali-kali bergantung pada id auto-increment untuk idempotensi, kerana jalankan semula akan menyisipkan pendua.
8. Perbezaan dialek secara ringkas
| Keupayaan | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
VALUES berbilang baris |
Ya | Ya | Ya |
| Upsert | ON CONFLICT |
ON CONFLICT |
ON DUPLICATE KEY UPDATE |
| Kunci auto-increment | INTEGER PRIMARY KEY |
GENERATED ... AS IDENTITY |
AUTO_INCREMENT |
| Jenis boolean | Tiada, menyimpan 0/1 | boolean |
TINYINT(1) |
| Import pukal | Sisipan berbilang baris dalam transaksi | COPY |
LOAD DATA |
| Sokongan JSON | TEXT ditambah fungsi json1 |
json / jsonb |
JSON |
| Petikan pengecam | Tanda petikan berganda | Tanda petikan berganda | Tanda petikan belakang (lalai) |
Untuk memastikan satu fail SQL berfungsi merentas ketiga-tiganya, sekurang-kurangnya elakkan ON CONFLICT, tanda petikan belakang, dan SERIAL. Bagi projek merentas pangkalan data, jana skema dan pernyataan upsert secara berasingan; jangan jangkakan satu skrip sesuai untuk semua.
9. Tiga semakan selepas import
Sebelum memadam fail sumber, sahkan tiga perkara:
- Bilangan baris.
SELECT COUNT(*) FROM users;harus sama dengan panjang tatasusunan JSON. Satu baris yang hilang biasanya bermaksud rekod terakhir dipotong. - Lajur dengan kadar NULL tertinggi. Itu menunjuk kepada nama kunci yang tidak konsisten atau sarang yang tidak dikembangkan. Kembali dan semak sama ada sumber menggunakan kedua-dua
userIddanuser_id. - Wang dan pengecam panjang. Ambil nilai maksimum dan minimum dan sahkan tiada titik terapung atau penukaran integer memotongnya.
Jika anda perlu memahami bentuk jadual dahulu, gunakan pendekatan perataan dalam Penukaran JSON, CSV, dan data jadual. Jika audiens ialah rakan sekerja yang hanya mahu membaca data, empat cara menukar JSON kepada Excel lebih sedikit kerja daripada menyediakan pangkalan data.
Mengapa nombor kehilangan ketepatan selepas mengimport JSON ke dalam SQL?
Integer melebihi 2^53 sudah kehilangan ketepatan semasa JSON.parse JavaScript, dan menulisnya ke dalam BIGINT tidak dapat memulihkannya. ID seperti itu harus rentetan dalam JSON dan ditukar kepada BIGINT atau TEXT semasa import.
Apa yang harus saya gunakan sebagai kunci utama apabila JSON tiada id?
Biarkan pangkalan data menjana satu, atau cipta UUID yang stabil sebelum mengimport. Jangan sekali-kali menggunakan indeks tatasusunan sebagai id: jalankan semula menyusun semula data dan menunjuk baris yang salah kepada rekod yang salah. Indeks unik pada kunci perniagaan (e-mel, nombor pesanan) juga membolehkan upsert.
Apa yang perlu saya lakukan apabila INSERT pukal menemui konflik kunci unik?
Ganti INSERT biasa dengan upsert yang sepadan untuk dialek anda, dan sahkan lajur yang bercanggah mempunyai kekangan unik. Memadam dahulu dan menyisip kemudian juga berkesan, tetapi ia mesti berlaku di dalam satu transaksi dan anda menerima tetingkap singkat tanpa baris.
Adakah tatasusunan bersarang sentiasa memerlukan jadual anak?
Tidak. Untuk paparan baca-sahaja tanpa pertanyaan mengikut sub-item, lajur JSON lebih mudah. Sebaik sahaja anda perlu menapis atau mengagregat mengikut tag atau SKU, jadual anak dengan indeks ialah pilihan yang mampan. Faktor penentu ialah cara anda membuat pertanyaan, bukan cara data itu dibentuk.