JSON থেকে SQL: INSERT স্টেটমেন্ট, টেবিল ডিজাইন ও টাইপ ম্যাপিংয়ের ফাঁদ
সংক্ষেপে: JSON-কে SQL INSERT বানাতে নেস্টেড টেবিল বিভাজন, NULL বনাম অনুপস্থিত, এস্কেপিং ও ডায়ালেক্টের ফাঁক লুকিয়ে থাকে। প্রতিটি ফাঁদের জন্য চালানোযোগ্য স্টেটমেন্ট ও তুলনা টেবিল।

মূল কথা: টেবিল ডিজাইন করার আগে ঠিক করুন এক সারি কোন ব্যবসায়িক সত্তাকে বোঝায়, তারপর এক লেনদেনের ভেতরে স্পষ্ট মাল্টি-রো VALUES লিখুন; ফিল্ডের নাম ও টাইপ ম্যাপিং ইমপোর্টের আগেই ঠিক করুন, SQL-এ পরে প্যাচ করবেন না।
JSON আকৃতি বর্ণনা করে; SQL বাধ্যবাধকতাও বর্ণনা করে। প্রথমটিকে দ্বিতীয়টিতে নেওয়ার সময় আপনি যে বাধ্যবাধকতাগুলো লেখেননি, সেগুলো পরে নোংরা ডেটা হিসেবে ফিরে আসে।
১. আগে ঠিক করুন: JSON সারি-প্রতি একটি টেবিল নাকি একটি রেকর্ড
কোনো টেবিল ডিজাইন করার আগে ডেটার সবচেয়ে বাইরের স্তর দেখুন। এই সিদ্ধান্ত ভুল হলে পরের প্রতিটি INSERT আবার লিখতে হবে।
| JSON কাঠামো | প্রস্তাবিত টেবিল ডিজাইন | মূল ঝুঁকি |
|---|---|---|
সমতল অবজেক্টের অ্যারে [{...},{...}] |
একটি টেবিল, অনেক সারি | রেকর্ডের মধ্যে কি-অসামঞ্জস্য থাকলে ইউনিয়ন কলামের বেশিরভাগ NULL থাকে |
অ্যারে মোড়া অবজেক্ট {"data":[...],"total":100} |
একটি টেবিল, কেবল data নিন |
প্রতি সারিতে total লিখলে ফিল্ডের অর্থ ভুল জায়গায় পড়ে |
নেস্টেড অবজেক্ট {"user":{"name":"x"}} |
প্যারেন্ট টেবিল প্লাস ফ্ল্যাট কলাম, বা প্যারেন্ট প্লাস সংশ্লিষ্ট টেবিল | অনিয়ন্ত্রিত প্রসারণ, কলাম-নামের পুনরাবৃত্তি |
অ্যারে ফিল্ড {"tags":["a","b"]} |
চাইল্ড টেবিল, বা JSON/TEXT কলাম | এক-থেকে-অনেক সম্পর্ক এক সেলে চুপসে গিয়ে ট্যাগ দিয়ে আর কুয়েরি করা যায় না |
| অসামঞ্জস্যপূর্ণ কি | ইউনিয়ন কলাম, বা টাইপ অনুযায়ী টেবিল ভাগ | "কি অনুপস্থিত" ও "মান null" একই NULL-এ মিলে যায় |
মাত্র একটি মানদণ্ড: এক সারি কোন ব্যবসায়িক সত্তাকে বোঝায়? সারি যদি অর্ডার হয়, লাইন আইটেমকে নিজের টেবিলে থাকতে হবে। সারি যদি ব্যবহারকারী হয়, ট্যাগ চাইল্ড টেবিলে বা 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);
তিনটি খুঁটিনাটি গুরুত্বপূর্ণ:
- প্রতিটি কলামের নাম লিখুন। পরে স্কিমায় নতুন কলাম এলে কিছুই ভুল ফিল্ডে সরে যাবে না।
- মাল্টি-রো
VALUESSQLite, PostgreSQL ও MySQL-এ কাজ করে, আর একক-সারি insert-এর লুপের তুলনায় অনেক রাউন্ড ট্রিপ বাঁচায়। NULLএকটি কীওয়ার্ড; এটি কোট করবেন না।'NULL'লিখলে চার-অক্ষরের স্ট্রিং স্টোর হয়।
JSON-এর কি কলামের নামের সঙ্গে না মিললে (userId বনাম user_id), একটি ম্যাপিং স্তর যোগ করুন। SQL-এর ভেতরে COALESCE দিয়ে অনুমান করবেন না। ডেটা স্তরে ফিল্ডের নাম ঠিক করা কুয়েরিতে প্যাচ করার চেয়ে অনেক সস্তা।
৩. নেস্টেড অবজেক্ট: প্যারেন্ট টেবিল প্লাস চাইল্ড টেবিল
অর্ডার হলো এক-থেকে-অনেকের ক্লাসিক কেস:
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 এক লেনদেনে commit করুন, নাহলে প্যারেন্ট থাকতে পারে অথচ তার আইটেম missing। - ডেটা যদি read-only হয় এবং কেউ লাইন আইটেম দিয়ে ফিল্টার না করে, JSON কলাম গ্রহণযোগ্য (PostgreSQL
jsonb, MySQLJSON, বা json1 ফাংশনসহ SQLiteTEXT), তবে মূল্য মেনে নিন: ইনডেক্সিং নেই, কুয়েরি বিশ্রী।
user.address-এর মতো স্থির নেস্টেড অবজেক্ট কলামে (address_city, address_zip) ফ্ল্যাট করুন। কেবল তখনই টেবিলে ভাগ করুন যখন নেস্টিং বাড়তেই থাকতে পারে; স্থির আকৃতি ভাগ করলে শুধু JOIN বাড়ে।
৪. NULL বনাম অনুপস্থিত ফিল্ড: SQL-এ "অনুপস্থিত কি" নেই
JSON-এ একটি কি একেবারে অনুপস্থিত থাকতে পারে। SQL কলাম স্থির। ইমপোর্টের পর দুই ক্ষেত্রেই একই দেখায় — NULL — যদিও অর্থ আলাদা হতে পারে:
| JSON পরিস্থিতি | ডেটাবেস যা দেখায় | প্রস্তাবিত হ্যান্ডলিং |
|---|---|---|
| কি অনুপস্থিত | NULL |
টেবিল ডেফিনিশনে ডিফল্ট দিন, বা _present মার্কার কলাম |
কি আছে, মান null |
NULL |
বোঝায় ব্যবহারকারী স্পষ্টভাবে মান মুছেছেন |
| কি আছে, মান খালি স্ট্রিং | '' |
NULL থেকে আলাদা, তবে কুয়েরিতে গুলিয়ে ফেলা সহজ |
| সাংখ্যিক ফিল্ড অনুপস্থিত | NULL |
কখনো 0-কে "অনুপস্থিত" সাজতে দেবেন না |
দুটি সাধারণ ভুল:
- NULL-এর বদলে খালি স্ট্রিং ব্যবহার। পরে
WHERE email IS NULLওই সারিগুলো মিস করে, আর আপনার "ইমেইলহীন ব্যবহারকারী" মেট্রিক নীরবে কম গণনা করে। col = NULLদিয়ে টেস্ট করা। SQL-এর তিন-মানের লজিকেNULL = NULLমূল্যায়িত হয়NULL,TRUEনয়। লিখতে হবেIS NULL:
SELECT COUNT(*) FROM users WHERE email IS NULL;
কি আদৌ উপস্থিত ছিল কি না সংরক্ষণ করতে হলে NULL থেকে অনুমান না করে বুলিয়ান মার্কার কলাম যোগ করুন। API চুক্তিতেও এটি বলা উচিত; দেখুন JSON API ডিজাইন ও ফিল্ড নামকরণ।
৫. কোটিং ও এস্কেপিং: কখনো SQL স্ট্রিং জোড়া লাগাবেন না
JSON স্ট্রিংয়ে সিঙ্গেল কোট, ডাবল কোট, ব্যাকস্ল্যাশ, নিউলাইন ও এমনকি \u0000 থাকতে পারে। হাতে বানানো স্ট্রিংয়ে একটি এস্কেপ বাদ পড়লে বাকি স্টেটমেন্ট সরে যায়, আর সবচেয়ে খারাপ ক্ষেত্রে SQL ইনজেকশন হয়।
স্ট্যান্ডার্ড SQL-এ সিঙ্গেল কোট দুবার লিখে এস্কেপ করা হয়:
INSERT INTO notes (id, content) VALUES (1, 'It''s a ''quoted'' value');
প্রোডাকশন কোডে প্যারামিটার বাইন্ডিং ব্যবহার করুন, ড্রাইভারকে এস্কেপিং ও টাইপ হ্যান্ডল করতে দিন:
// PostgreSQL $1/$2 ব্যবহার করে, MySQL ?; এই উদাহরণে node-postgres
await client.query('INSERT INTO users (id, name) VALUES ($1, $2)', [row.id, row.name]);
হাতে এস্কেপ কেবল যখন .sql ফাইল এক্সপোর্ট করতেই হবে, আর তখন অন্তত:
'-কে''দিয়ে প্রতিস্থাপন করুন;- নিউলাইন সংরক্ষণ করলে লক্ষ্য ডেটাবেস মাল্টি-লাইন স্ট্রিং লিটেরাল গ্রহণ করে কি না নিশ্চিত করুন;
\u0000সরান, কারণ বেশিরভাগ ডেটাবেস স্ট্রিংয়ের ভেতরে NUL প্রত্যাখ্যান করে;- MySQL ডিফল্টভাবে ব্যাকস্ল্যাশকে এস্কেপ অক্ষর ধরে, তাই
\-কে\\করতে হবে, বা স্ট্যান্ডার্ড মোডে যেতে হবে।
JSON-এর ডাবল কোট SQL-এর সিঙ্গেল-কোটেড স্ট্রিংয়ের ভেতরে এস্কেপ করার দরকার নেই। বাড়তি এস্কেপিং স্তর যোগ করলেই ডেটায় এলোমেলো ব্যাকস্ল্যাশ ঢোকে।
৬. টাইপ ম্যাপিং: ছয় JSON মান SQL-এ কোথায় পড়ে
JSON-এ কেবল স্ট্রিং, সংখ্যা, বুলিয়ান, null, অবজেক্ট ও অ্যারে। SQL-এর টাইপ সিস্টেম অনেক সূক্ষ্ম। ভুল সাধারণত সিনট্যাক্সে নয়, নির্ভুলতা ও সেমান্টিক্সে হয়:
| JSON মান | প্রস্তাবিত SQL টাইপ | কোথায় ভাঙে |
|---|---|---|
| ইন্টিজার | INTEGER / BIGINT |
2^53-এর উপরে JavaScript দিক ইতিমধ্যে নির্ভুলতা হারিয়েছে, তাই স্টোর করা মানও ভুল |
| দশমিক, টাকা | NUMERIC / DECIMAL |
FLOAT বাইনারি ফ্লোটিং পয়েন্ট; 0.1 + 0.2 নিখুঁত নয় |
| স্ট্রিং | TEXT / VARCHAR(n) |
দৈর্ঘ্যের সীমা কাটে; কলেশন তুলনা বদলায় |
| বুলিয়ান | BOOLEAN / TINYINT(1) |
SQLite-এ বুলিয়ান টাইপ নেই, 0/1 স্টোর করে |
| null | NULL |
অনুপস্থিত ফিল্ড থেকে আলাদা করা যায় না |
| তারিখ স্ট্রিং | DATE / TIMESTAMP / TIMESTAMPTZ |
হারানো টাইম জোন, কাটা মিলিসেকেন্ড, অবৈধ ফরম্যাট |
| এনাম স্ট্রিং | CHECK সহ TEXT, বা এনাম টেবিল |
কেস-পার্থক্য ও নোংরা মানের কোনো বাধ্যবাধকতা নেই |
| অবজেক্ট / অ্যারে | চাইল্ড টেবিল, বা JSON / JSONB |
স্ট্রিং হিসেবে স্টোর করলে ইনডেক্স বা কুয়েরি করা যায় না |
বুলিয়ান সবচেয়ে সূক্ষ্ম বাগ লুকায়: MySQL TINYINT(1)-এ JSON false হয় 0, রাউন্ড-ট্রিপ ঠিক দেখায়, কেউ খেয়াল করে না। কিন্তু কিছু কোড যখন স্ট্রিং "false"-এর উপর if (value) করে, তখন সেটি truthy হিসেবে মূল্যায়িত হয়। রূপান্তর স্ক্রিপ্টে স্পষ্টভাবে Boolean(value) কল করা উচিত।
PostgreSQL JSONB কি পার্স ও ডুপ্লিকেট-মুক্ত করে, শেষটি রাখে, যা কুয়েরির জন্য উপযুক্ত; JSON মূল টেক্সট ও কি-ক্রম সংরক্ষণ করে। SQLite JSON আসলে TEXT প্লাস কিছু ফাংশন, কোনো প্রকৃত টাইপ বাধ্যবাধকতা নেই। বাছাইয়ের আগে জিজ্ঞাসা করুন এই টেবিলের JSON-এর ভেতরের ফিল্ডে ফিল্টার দরকার কি না।
৭. বাল্ক insert ও লেনদেন: ব্যাচ, commit, পুনরায় চালানোর উপযোগী করুন
আয়তন বাড়লে তিনটি বিষয় ঠিক করে ইমপোর্ট নিয়ন্ত্রণে থাকবে কি না:
- ব্যাচ। N একক-সারি
INSERT-এর লুপে N নেটওয়ার্ক রাউন্ড ট্রিপ লাগে। ৫০০ থেকে ১০০০ সারি এক মাল্টি-রোVALUES-এ মিলান, বা ডেটাবেসের নিজের বাল্ক লোডার ব্যবহার করুন (PostgreSQLCOPY, MySQLLOAD DATA, SQLite লেনদেনের ভেতরে মাল্টি-রো insert)। - লেনদেন ব্যবহার করুন। সব বা কিছুই না, যাতে আধা-লোডেড টেবিল কোনো এররের পর বেঁচে না থাকে:
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;
- ইডেম্পোটেন্ট করুন। ইমপোর্ট স্ক্রিপ্ট বারবার চালানো হয়। delete-then-insert-এর বদলে upsert ব্যবহার করুন:
| ডেটাবেস | ইডেম্পোটেন্ট রূপ |
|---|---|
| PostgreSQL / SQLite | INSERT ... ON CONFLICT (id) DO UPDATE SET ... |
| MySQL | INSERT ... ON DUPLICATE KEY UPDATE ... |
ON CONFLICT-এর জন্য ইউনিক বাধ্যবাধকতা বা প্রাইমারি কি দরকার, নাহলে এটি কখনো চলে না। প্রাকৃতিক কি না থাকলে email-এর মতো ব্যবসায়িক কি-তে ইউনিক ইনডেক্স বানান। একেবারে কি না থাকলে JSON দিকে স্থির id যোগ করুন — ইডেম্পোটেন্সির জন্য কখনো অটো-ইনক্রিমেন্ট id-র উপর নির্ভর করবেন না, কারণ পুনরায় চালালে ডুপ্লিকেট ঢুকে পড়বে।
৮. ডায়ালেক্টের পার্থক্য এক নজরে
| সামর্থ্য | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
মাল্টি-রো VALUES |
হ্যাঁ | হ্যাঁ | হ্যাঁ |
| Upsert | ON CONFLICT |
ON CONFLICT |
ON DUPLICATE KEY UPDATE |
| অটো-ইনক্রিমেন্ট কি | INTEGER PRIMARY KEY |
GENERATED ... AS IDENTITY |
AUTO_INCREMENT |
| বুলিয়ান টাইপ | নেই, 0/1 স্টোর করে | boolean |
TINYINT(1) |
| বাল্ক ইমপোর্ট | লেনদেনে মাল্টি-রো insert | COPY |
LOAD DATA |
| JSON সমর্থন | TEXT প্লাস json1 ফাংশন |
json / jsonb |
JSON |
| আইডেন্টিফায়ার কোটিং | ডাবল কোট | ডাবল কোট | ব্যাকটিক (ডিফল্ট) |
তিনটিতেই একই SQL ফাইল চালাতে হলে অন্তত ON CONFLICT, ব্যাকটিক ও SERIAL এড়ান। ক্রস-ডেটাবেস প্রকল্পে স্কিমা ও upsert স্টেটমেন্ট আলাদাভাবে জেনারেট করুন; এক স্ক্রিপ্ট সবখানে মানাবে ভাববেন না।
৯. ইমপোর্টের পর তিনটি স্পট চেক
উৎস ফাইল মোছার আগে তিনটি বিষয় যাচাই করুন:
- সারির সংখ্যা।
SELECT COUNT(*) FROM users;JSON অ্যারের দৈর্ঘ্যের সমান হওয়া উচিত। একটি সারি কম মানে সাধারণত শেষ রেকর্ড কাটা পড়েছে। - সর্বোচ্চ NULL-হারের কলাম। এটি অসামঞ্জস্যপূর্ণ কি-নাম বা না-খোলা নেস্টিংয়ের দিকে ইশারা করে। ফিরে গিয়ে দেখুন উৎসে
userIdওuser_idদুটোই আছে কি না। - টাকা ও লম্বা আইডেন্টিফায়ার। সর্বোচ্চ ও সর্বনিম্ন মান নিন এবং নিশ্চিত করুন ফ্লোটিং পয়েন্ট বা ইন্টিজার রূপান্তর সেগুলো কাটেনি।
আগে ট্যাবুলার আকৃতি বুঝতে হলে JSON, CSV ও ট্যাবুলার ডেটা রূপান্তর-এর ফ্ল্যাটেনিং পদ্ধতি ব্যবহার করুন। পাঠক যদি শুধু ডেটা পড়তে চান এমন সহকর্মী হন, JSON থেকে Excel রূপান্তরের চারটি উপায় ডেটাবেস দাঁড় করানোর চেয়ে কম পরিশ্রম।
JSON SQL-এ ইমপোর্টের পর সংখ্যা নির্ভুলতা হারায় কেন?
2^53-এর বেশি ইন্টিজার JavaScript-এর JSON.parse-এর সময়ই নির্ভুলতা হারিয়েছে, আর BIGINT-এ লিখলে তা ফেরানো যায় না। এমন id JSON-এ স্ট্রিং হওয়া উচিত এবং ইমপোর্টের সময় BIGINT বা TEXT-এ রূপান্তর করা উচিত।
JSON-এ id না থাকলে প্রাইমারি কি হিসেবে কী ব্যবহার করব?
ডেটাবেসকে একটি জেনারেট করতে দিন, বা ইমপোর্টের আগে স্থির UUID বানিয়ে নিন। কখনো অ্যারের ইনডেক্সকে id হিসেবে ব্যবহার করবেন না: পুনরায় চালালে ডেটা পুনর্বিন্যস্ত হয় আর ভুল সারি ভুল রেকর্ডে যায়। ব্যবসায়িক কি-তে (ইমেইল, অর্ডার নম্বর) ইউনিক ইনডেক্সও upsert সম্ভব করে।
বাল্ক INSERT ইউনিক-কি কনফ্লিক্টে পড়লে কী করব?
সাধারণ INSERT-এর বদলে আপনার ডায়ালেক্টের উপযুক্ত upsert বসান, এবং কনফ্লিক্ট করা কলামে ইউনিক বাধ্যবাধকতা আছে নিশ্চিত করুন। আগে মুছে পরে insert করাও কাজ করে, তবে তা এক লেনদেনের ভেতরে হতে হবে আর কিছুক্ষণ সারিহীন অবস্থা মেনে নিতে হবে।
নেস্টেড অ্যারেতে কি সবসময় চাইল্ড টেবিল দরকার?
না। read-only প্রদর্শনে যেখানে উপ-আইটেম দিয়ে কুয়েরি করা হয় না, JSON কলাম সরল। যেই না ট্যাগ বা SKU দিয়ে ফিল্টার বা অ্যাগ্রিগেট দরকার, ইনডেক্সসহ চাইল্ড টেবিলই টেকসই পছন্দ। নির্ধারক হলো আপনি কীভাবে কুয়েরি করেন, ডেটার আকৃতি নয়।