JSON Toolkit

JSON به SQL: دستورهای INSERT، طراحی جدول و تله‌های نگاشت نوع

جمع‌بندی: تبدیل JSON به INSERT SQL شکستن جدول‌های تودرتو، NULL در برابر غایب، escape و شکاف گویش‌ها را پنهان می‌کند. دستورهای قابل اجرا و جدول مقایسه برای هر تله.

متوسطحدود 10 دقیقه مطالعهانتشار 2026-10-31به‌روزرسانی 2026-10-31
تصویر جلد راهنمای JSON به SQL INSERT و طراحی جدول

جمع‌بندی: پیش از طراحی جدول‌ها تصمیم بگیرید یک سطر نماینده کدام موجودیت کسب‌وکار است، سپس VALUES صریح چندسطری را درون یک تراکنش بنویسید؛ نام‌گذاری فیلدها و نگاشت نوع را پیش از ورود تثبیت کنید نه اینکه در SQL وصله بزنید.

JSON شکل را توصیف می‌کند؛ SQL قیدها را هم توصیف می‌کند. وقتی اولی را به دومی منتقل می‌کنید، هر قیدی که ننوشته‌اید بعداً به‌صورت داده کثیف برمی‌گردد.

۱. نخست تصمیم بگیرید: یک جدول یا یک رکورد برای هر سطر JSON

پیش از طراحی هر جدولی به بیرونی‌ترین لایه داده نگاه کنید. این تصمیم را اشتباه بگیرید و هر INSERT بعدی باید بازنویسی شود.

ساختار JSON طراحی جدول پیشنهادی خطر اصلی
آرایه تخت از objectها [{...},{...}] یک جدول، چند سطر کلیدهای ناهمگون میان رکوردها بیشتر ستون‌های اجتماع را NULL می‌گذارند
objectی که آرایه را می‌پیچد {"data":[...],"total":100} یک جدول، فقط data را بگیرید نوشتن total در هر سطر معنای فیلد را جابه‌جا می‌کند
object تودرتو {"user":{"name":"x"}} جدول والد به‌همراه ستون‌های تخت، یا والد به‌همراه جدول مرتبط گسترش کنترل‌نشده، نام ستون‌های تکراری
فیلد آرایه {"tags":["a","b"]} جدول فرزند، یا ستون JSON/TEXT رابطه یک‌به‌چند در یک سلول چپانده و دیگر با tag قابل پرس‌وجو نیست
کلیدهای ناهمگون ستون‌های اجتماع، یا شکستن جدول بر اساس نوع «کلید غایب» و «مقدار null» در همان NULL فرو می‌ریزند

تنها یک معیار وجود دارد: یک سطر نماینده کدام موجودیت کسب‌وکار است؟ اگر یک سطر یک سفارش است، اقلام باید در جدول خودشان زندگی کنند. اگر یک سطر یک کاربر است، tagها به جدول فرزند یا ستون JSON تعلق دارند، نه به یک رشته با ویرگول به هم پیوسته.

۲. از آرایه به INSERT: کوچک‌ترین قالب قابل اجرا

امن‌ترین شکل همیشه نام ستون‌ها را صریح فهرست می‌کند و هرگز به ترتیب ستون‌ها تکیه نمی‌کند:

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);

سه جزئیات اهمیت دارد:

  • هر نام ستون را بنویسید. وقتی schema بعداً ستونی می‌گیرد، چیزی به فیلد اشتباه جابه‌جا نمی‌شود.
  • VALUES چندسطری در SQLite، PostgreSQL و MySQL کار می‌کند و در مقایسه با حلقه INSERTهای تک‌سطری، رفت‌وبرگشت‌های زیادی صرفه‌جویی می‌کند.
  • NULL یک کلمه کلیدی است؛ آن را در گیومه نگذارید. نوشتن 'NULL' یک رشته چهارکاراکتری ذخیره می‌کند.

اگر کلیدهای JSON با نام ستون‌ها نمی‌خوانند (userId در برابر user_id)، یک لایه نگاشت بیفزایید. با COALESCE درون SQL حدس نزنید. اصلاح نام فیلدها در لایه داده بسیار ارزان‌تر از وصله زدن در پرس‌وجوهاست.

۳. objectهای تودرتو: جدول والد به‌همراه جدول فرزند

سفارش‌ها مورد کلاسیک یک‌به‌چندند:

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);

این معادل JSON {"id":1001,"customer":"Ada","total":129.00,"items":[{"sku":"A-1","quantity":2},{"sku":"B-7","quantity":1}]} است.

نکات کلیدی:

  • طول آرایه items تعداد سطرهای فرزند را تعیین می‌کند؛ جدول والد دقیقاً یک سطر می‌گیرد.
  • پول را به‌صورت سنت صحیح (total_cents) ذخیره کنید، هرگز به‌صورت FLOAT.
  • به جدول فرزند یک کلید خارجی order_id بدهید و هر دو insert را در یک تراکنش ثبت کنید، وگرنه والد می‌تواند وجود داشته باشد در حالی که اقلامش غایب‌اند.
  • اگر داده فقط‌خواندنی است و کسی بر اساس قلم فیلتر نمی‌کند، ستون JSON قابل قبول است (PostgreSQL jsonb، MySQL JSON یا SQLite TEXT با توابع json1)، اما هزینه را بپذیرید: بدون ایندکس و پرس‌وجوهای دشوار.

objectهای تودرتوی ثابت مانند user.address را در ستون‌ها تخت کنید (address_city، address_zip). فقط وقتی به جدول بشکنید که تودرتویی بتواند مدام رشد کند؛ شکستن یک شکل ثابت فقط JOIN اضافه می‌کند.

۴. NULL در برابر فیلد غایب: SQL «کلید غایب» ندارد

JSON اجازه می‌دهد یک کلید کاملاً غایب باشد. ستون‌های SQL ثابت‌اند. پس از ورود هر دو حالت یکسان به نظر می‌رسند — NULL — هرچند می‌توانند معنای متفاوتی داشته باشند:

وضعیت JSON آنچه پایگاه‌داده نشان می‌دهد مدیریت پیشنهادی
کلید غایب است NULL در تعریف جدول مقدار پیش‌فرض بگذارید، یا ستون نشانگر _present
کلید با null وجود دارد NULL یعنی کاربر صریحاً مقدار را پاک کرده است
کلید با رشته خالی وجود دارد '' از NULL قابل تشخیص است، اما در پرس‌وجوها به‌آسانی اشتباه می‌شود
فیلد عددی غایب است NULL هرگز نگذارید ۰ نقش «غایب» را بازی کند

دو اشتباه رایج:

۱. استفاده از رشته خالی به‌جای NULL. بعداً WHERE email IS NULL آن سطرها را از دست می‌دهد و شاخص «کاربران بدون ایمیل» شما بی‌صدا کم‌شماری می‌کند. ۲. آزمون با col = NULL. در منطق سه‌مقداری SQL، NULL = NULL به NULL ارزیابی می‌شود نه TRUE. باید IS NULL بنویسید:

SELECT COUNT(*) FROM users WHERE email IS NULL;

اگر باید حفظ کنید که آیا کلید اصلاً وجود داشته یا نه، به‌جای استنتاج از NULL یک ستون نشانگر بولین بیفزایید. قرارداد API هم باید همین را بگوید؛ طراحی API JSON و نام‌گذاری فیلد را ببینید.

۵. نقل‌قول و escape: هرگز رشته‌های SQL را به هم نچسبانید

رشته‌های JSON می‌توانند تک‌گیومه، گیومه دوتایی، بک‌اسلش، خط جدید و حتی \u0000 داشته باشند. با رشته‌های دست‌ساز، یک escape جاافتاده بقیه دستور را جابه‌جا می‌کند و در بدترین حالت به تزریق SQL تبدیل می‌شود.

در SQL استاندارد، تک‌گیومه با دو برابر کردن escape می‌شود:

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

کد محیط عملیاتی باید از اتصال پارامتر استفاده کند و بگذارد درایور escape و نوع‌ها را مدیریت کند:

// 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]);

فقط وقتی باید یک فایل .sql صادر کنید دستی escape کنید و آن هم حداقل:

  • ' را با '' جایگزین کنید؛
  • اگر خطوط جدید حفظ می‌شوند، تأیید کنید پایگاه‌داده هدف لیترال‌های رشته چندخطی را می‌پذیرد؛
  • \u0000 را حذف کنید، چون بیشتر پایگاه‌داده‌ها NUL را درون رشته‌ها رد می‌کنند؛
  • MySQL به‌طور پیش‌فرض بک‌اسلش را کاراکتر escape می‌داند، پس \ باید \\ شود یا به حالت استاندارد تغییر دهید.

گیومه‌های دوتایی JSON درون رشته تک‌گیومه‌ای SQL نیازی به escape ندارند. افزودن یک لایه escape اضافه همان راهی است که بک‌اسلش‌های سرگردان در داده شما ظاهر می‌شوند.

۶. نگاشت نوع: شش مقدار JSON کجای SQL فرود می‌آیند

JSON فقط رشته، عدد، بولین، null، object و array دارد. SQL نظام نوع بسیار ریزتری دارد. اشتباهات معمولاً در دقت و معنا رخ می‌دهند نه در نحو:

مقدار JSON نوع SQL پیشنهادی کجا می‌شکند
عدد صحیح INTEGER / BIGINT بالاتر از 2^53 سمت JavaScript از پیش دقت را از دست داده، پس مقدار ذخیره‌شده هم اشتباه است
اعشاری، پول NUMERIC / DECIMAL FLOAT ممیز شناور دودویی است؛ 0.1 + 0.2 دقیق نیست
رشته TEXT / VARCHAR(n) محدودیت طول می‌برد؛ collation مقایسه‌ها را تغییر می‌دهد
بولین BOOLEAN / TINYINT(1) SQLite نوع بولین ندارد و ۰/۱ ذخیره می‌کند
null NULL از فیلد غایب قابل تشخیص نیست
رشته تاریخ DATE / TIMESTAMP / TIMESTAMPTZ منطقه زمانی از دست رفته، میلی‌ثانیه بریده، قالب‌های نامعتبر
رشته enum TEXT با CHECK، یا جدول enum تفاوت حروف و مقدارهای کثیف قیدی ندارند
object / array جدول فرزند، یا JSON / JSONB ذخیره به‌صورت رشته قابل ایندکس یا پرس‌وجو نیست

بولین‌ها ظریف‌ترین باگ را پنهان می‌کنند: false در JSON در TINYINT(1) MySQL به ۰ تبدیل می‌شود، رفت‌وبرگشت درست به نظر می‌رسد و کسی متوجه نمی‌شود. اما لحظه‌ای که کدی if (value) را روی رشته "false" اجرا کند، آن را truthy ارزیابی می‌کند. اسکریپت‌های تبدیل باید صریحاً Boolean(value) را صدا بزنند.

JSONB در PostgreSQL کلیدها را تجزیه و یکتا می‌کند و آخرین تکرار را نگه می‌دارد، که برای پرس‌وجو مناسب است؛ JSON متن اصلی و ترتیب کلیدها را حفظ می‌کند. JSON در SQLite فقط TEXT به‌همراه مجموعه‌ای از توابع است، بدون قید نوع واقعی. پیش از انتخاب بپرسید آیا این جدول به فیلتر روی فیلدهای درون JSON نیاز دارد.

۷. درج انبوه و تراکنش: دسته‌بندی، commit و قابل‌اجرای دوباره کنید

با رشد حجم، سه چیز تعیین می‌کند ورود داده قابل کنترل بماند یا نه:

۱. دسته‌بندی. حلقه‌ای از N دستور INSERT تک‌سطری N رفت‌وبرگشت شبکه هزینه دارد. ۵۰۰ تا ۱۰۰۰ سطر را در یک VALUES چندسطری ترکیب کنید یا از بارگذار انبوه خود پایگاه‌داده استفاده کنید (COPY در PostgreSQL، LOAD DATA در MySQL، insert چندسطری درون یک تراکنش SQLite). ۲. از تراکنش استفاده کنید. همه یا هیچ، تا جدول نیمه‌بار‌شده هرگز پس از خطا زنده نماند:

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;

۳. آن را idempotent کنید. اسکریپت‌های ورود مدام دوباره اجرا می‌شوند. به‌جای حذف-سپس-درج از upsert استفاده کنید:

پایگاه‌داده شکل idempotent
PostgreSQL / SQLite INSERT ... ON CONFLICT (id) DO UPDATE SET ...
MySQL INSERT ... ON DUPLICATE KEY UPDATE ...

ON CONFLICT به یک قید یکتا یا کلید اصلی نیاز دارد، وگرنه هرگز فعال نمی‌شود. وقتی کلید طبیعی نیست، روی یک کلید کسب‌وکار مانند email ایندکس یکتا بسازید. وقتی هیچ کلیدی نیست، در سمت JSON یک id پایدار بیفزایید — هرگز برای idempotency به id خودافزا تکیه نکنید، چون اجرای دوباره رکوردهای تکراری درج می‌کند.

۸. تفاوت گویش‌ها در یک نگاه

قابلیت SQLite PostgreSQL MySQL
VALUES چندسطری بله بله بله
Upsert ON CONFLICT ON CONFLICT ON DUPLICATE KEY UPDATE
کلید خودافزا INTEGER PRIMARY KEY GENERATED ... AS IDENTITY AUTO_INCREMENT
نوع بولین ندارد، ۰/۱ ذخیره می‌کند boolean TINYINT(1)
ورود انبوه insert چندسطری در یک تراکنش COPY LOAD DATA
پشتیبانی JSON TEXT به‌همراه توابع json1 json / jsonb JSON
نقل‌قول شناسه گیومه دوتایی گیومه دوتایی بک‌تیک (پیش‌فرض)

برای اینکه یک فایل SQL در هر سه کار کند، حداقل از ON CONFLICT، بک‌تیک و SERIAL پرهیز کنید. برای پروژه‌های چندپایگاه‌داده‌ای، schema و دستورهای upsert را جداگانه تولید کنید؛ انتظار نداشته باشید یک اسکریپت به همه بخورد.

۹. سه بررسی نمونه پس از ورود

پیش از حذف فایل منبع سه چیز را تأیید کنید:

۱. تعداد سطر. SELECT COUNT(*) FROM users; باید برابر طول آرایه JSON باشد. یک سطر جاافتاده معمولاً یعنی رکورد آخر بریده شده است. ۲. ستون با بالاترین نرخ NULL. این به نام‌های کلید ناهمگون یا تودرتویی گسترش‌نیافته اشاره می‌کند. برگردید و ببینید منبع هم userId و هم user_id استفاده می‌کند. ۳. پول و شناسه‌های بلند. بیشترین و کمترین مقدار را بردارید و تأیید کنید نه تبدیل ممیز شناور و نه عدد صحیح آن‌ها را نبریده است.

اگر نخست باید شکل جدولی را بفهمید، از رویکرد تخت‌سازی در تبدیل JSON، CSV و داده جدولی استفاده کنید. اگر مخاطب همکارانی هستند که فقط می‌خواهند داده را بخوانند، چهار راه تبدیل JSON به Excel کم‌دردسرتر از برپا کردن پایگاه‌داده است.

چرا عددها پس از وارد کردن JSON به SQL دقت را از دست می‌دهند؟

عددهای صحیح بزرگ‌تر از 2^53 از پیش در JSON.parse جاوااسکریپت دقت را از دست داده‌اند و نوشتن آن‌ها در BIGINT نمی‌تواند آن را بازگرداند. چنین idهایی باید در JSON رشته باشند و هنگام ورود به BIGINT یا TEXT تبدیل شوند.

وقتی JSON هیچ id ندارد از چه چیزی به‌عنوان کلید اصلی استفاده کنم؟

بگذارید پایگاه‌داده یکی تولید کند، یا پیش از ورود یک UUID پایدار بسازید. هرگز از ایندکس آرایه به‌عنوان id استفاده نکنید: اجرای دوباره داده را دوباره مرتب می‌کند و سطرهای اشتباه را به رکوردهای اشتباه اشاره می‌دهد. ایندکس یکتا روی یک کلید کسب‌وکار (ایمیل، شماره سفارش) upsert را هم ممکن می‌کند.

وقتی INSERT انبوه به تعارض کلید یکتا می‌خورد چه کنم؟

INSERT ساده را با upsert متناظر گویش خود جایگزین کنید و تأیید کنید ستون متعارض قید یکتا دارد. حذف اول و درج بعد هم کار می‌کند، اما باید درون یک تراکنش رخ دهد و پنجره‌ای کوتاه بدون سطر را می‌پذیرید.

آیا آرایه‌های تودرتو همیشه به جدول فرزند نیاز دارند؟

نه. برای نمایش فقط‌خواندنی بدون پرس‌وجو بر اساس زیرقلم، ستون JSON ساده‌تر است. به‌محض اینکه به فیلتر یا تجمیع بر اساس tag یا SKU نیاز پیدا کنید، جدول فرزند با ایندکس انتخاب پایدار است. عامل تعیین‌کننده نحوه پرس‌وجوی شماست، نه شکل داده.