অধ্যায় 3 · ডেটা তৈরি ও বদলানো
CREATE, ALTER, DROP আর কনস্ট্রেইন্ট
- পৃষ্ঠা 11 / 22
- 17 মিনিট পড়া
এতক্ষণ আপনি আগে থেকে বানানো টেবিল পড়েছেন। এবার নিজের টেবিল বানাবেন। টেবিল মানে শুধু কয়েকটা কলামের তালিকা নয়: এটা কিছু নিয়মেরও সমষ্টি, যা ডেটাবেস প্রতিটা সারির ওপর সবসময় খাটায়। এই নিয়মগুলোকে বলে কনস্ট্রেইন্ট (constraint), আর ডেটার কোয়ালিটি ঠিক রাখার এর চেয়ে সস্তা উপায় আর নেই: ৯ রেটিংয়ের রিভিউ, "postive" বানানের লেবেল, কিংবা যে কাস্টমার আসলে নেই তার অর্ডার দরজাতেই আটকে যায়; মাসখানেক পরে চুপচাপ কোনো রিপোর্ট বা ট্রেনিং ডেটা নষ্ট করার সুযোগ পায় না।
এই পাতায় আপনি BitByte Shop-এর জন্য একটা reviews টেবিল বানাবেন (এ ধরনের টেবিলই পরে সেন্টিমেন্ট অ্যানালাইসিসের ট্রেনিং ডেটা হয়), ইচ্ছে করে প্রতিটা নিয়ম ভাঙবেন, ALTER TABLE দিয়ে টেবিল বদলাবেন, আর টেবিল খালি করা আর মুছে ফেলার পার্থক্য শিখবেন।
যা শিখবেন
PRIMARY KEY,FOREIGN KEY,UNIQUE,NOT NULL,CHECKআরDEFAULTদিয়েCREATE TABLEলেখা।- SQLite, MySQL আর PostgreSQL-এ প্রতিটা কনস্ট্রেইন্ট ভাঙলে কেমন এরর আসে।
- নিজে থেকে নম্বর পাওয়া id:
INTEGER PRIMARY KEY/AUTOINCREMENT,AUTO_INCREMENT,IDENTITYআরSERIAL। ALTER TABLEকী পারে আর কী পারে না, বিশেষ করে SQLite-এ।DROP,TRUNCATEআরDELETE-এর পার্থক্য, আর ফরেন কি কেন তিনটাকেই আটকে দিতে পারে।
কনস্ট্রেইন্টসহ CREATE TABLE
প্রতিটা কলামের থাকে একটা নাম আর একটা টাইপ (দেখুন ডেটা টাইপ, NULL আর ঠিক টাইপ বাছাই), তারপর তার কনস্ট্রেইন্ট। যে নিয়মে একাধিক কলাম জড়িত, সেটা লেখা হয় শেষে, টেবিল কনস্ট্রেইন্ট হিসেবে; ফরেন কি-ও আমরা শেষেই লিখি, কারণটা একটু পরে দেখবেন (কলামের পাশে লেখা REFERENCES MySQL উপেক্ষা করে):
CREATE TABLE IF NOT EXISTS reviews (
review_id INTEGER PRIMARY KEY AUTOINCREMENT, -- ইউনিক id, নিজে থেকেই বসে
product_id INTEGER NOT NULL, -- আসল কোনো প্রোডাক্ট হতে হবে (নিচের FOREIGN KEY)
customer_id INTEGER, -- NULL = নামহীন রিভিউ
rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5),
body TEXT NOT NULL,
label TEXT CONSTRAINT valid_label
CHECK (label IN ('positive', 'neutral', 'negative')),
status TEXT NOT NULL DEFAULT 'new',
UNIQUE (product_id, customer_id), -- এক কাস্টমার এক প্রোডাক্টে একটাই রিভিউ
FOREIGN KEY (product_id) REFERENCES products (product_id),
FOREIGN KEY (customer_id) REFERENCES customers (customer_id)
);
INSERT INTO reviews (product_id, customer_id, rating, body, label)
VALUES (3, 2, 5, 'Keys feel great, a bit loud.', 'positive'),
(4, 7, 2, 'Stopped working after a week.', 'negative');
SELECT * FROM reviews;+-----------+------------+-------------+--------+-------------------------------+----------+--------+
| review_id | product_id | customer_id | rating | body | label | status |
+-----------+------------+-------------+--------+-------------------------------+----------+--------+
| 1 | 3 | 2 | 5 | Keys feel great, a bit loud. | positive | new |
| 2 | 4 | 7 | 2 | Stopped working after a week. | negative | new |
+-----------+------------+-------------+--------+-------------------------------+----------+--------+খেয়াল করুন, কী কী আপনি লেখেননি: review_id নেই (ডেটাবেস নিজেই সারিগুলোর নম্বর দিয়েছে), status-ও নেই (DEFAULT সেখানে 'new' বসিয়েছে)। IF NOT EXISTS থাকায় স্টেটমেন্টটা দুবার চালালেও সমস্যা নেই: দ্বিতীয়বার table reviews already exists এরর না দিয়ে কিছুই করে না। তবে আগে থেকে থাকা টেবিলকে আপনার নতুন সংজ্ঞা অনুযায়ী বদলেও দেয় না।
| কনস্ট্রেইন্ট | যা নিশ্চিত করে | ডেটা/AI কাজে সাধারণ ব্যবহার |
|---|---|---|
PRIMARY KEY | প্রতিটা সারির একটা ইউনিক, খালি-নয় এমন পরিচয় | এমন id, যা দিয়ে জয়েন, ডুপ্লিকেট বাছাই আর উৎস খোঁজা যায় |
FOREIGN KEY / REFERENCES | মানটা অন্য টেবিলে আছে | প্রতিটা প্রেডিকশন কোনো আসল মডেল রানের |
UNIQUE | দুটো সারির মান (বা মানের জোড়া) এক হবে না | প্রতি (মডেল, টেস্ট প্রশ্ন)-এ একটাই ইভ্যালুয়েশন স্কোর |
NOT NULL | কলামে সবসময় মান থাকবে | টেক্সট ছাড়া কোনো ট্রেনিং উদাহরণ নয় |
CHECK | প্রতিটা সারিতে একটা শর্ত সত্য | নির্দিষ্ট সেট থেকে লেবেল, ০ থেকে ১-এর মধ্যে প্রোবাবিলিটি |
DEFAULT | INSERT কলামটা বাদ দিলে যে মান বসবে | স্ট্যাটাস 'new', ০ থেকে শুরু হওয়া কাউন্টার, তৈরির সময় |
ইচ্ছে করে নিয়ম ভাঙা
যে কনস্ট্রেইন্টকে কখনো ব্যর্থ হতে দেখেননি, সেটা আপনি আসলে পুরোপুরি বোঝেননি। নিচের প্রতিটা INSERT একটা করে নিয়ম ভাঙে; SQLite সারিটা ফিরিয়ে দেয় আর বলে দেয় কোন নিয়ম:
INSERT INTO reviews (product_id, customer_id, rating) VALUES (4, 2, 4);Error: NOT NULL constraint failed: reviews.bodyINSERT INTO reviews (product_id, customer_id, rating, body) VALUES (4, 2, 9, 'Amazing!');Error: CHECK constraint failed: rating BETWEEN 1 AND 5INSERT INTO reviews (product_id, customer_id, rating, body, label)
VALUES (4, 2, 4, 'Good value.', 'postive');Error: CHECK constraint failed: valid_labelINSERT INTO reviews (product_id, customer_id, rating, body) VALUES (3, 2, 4, 'Changed my mind.');Error: UNIQUE constraint failed: reviews.product_id, reviews.customer_idINSERT INTO reviews (product_id, customer_id, rating, body) VALUES (99, 2, 4, 'Which product?');Error: FOREIGN KEY constraint failedINSERT INTO reviews (review_id, product_id, customer_id, rating, body) VALUES (1, 5, 2, 5, 'Sharp.');Error: UNIQUE constraint failed: reviews.review_idযা খেয়াল করবেন:
- যে
CHECK-এর নাম দিয়েছি (CONSTRAINT valid_label), এরর মেসেজে তার নামটাই আসে; নামহীনগুলোর বেলায় আসে তাদের এক্সপ্রেশন (SQLite) বা একটা বানানো নাম (অন্য ডেটাবেস, নিচে দেখবেন)। যে নিয়মগুলো জরুরি, সেগুলোর নাম দিন। - SQLite-এর ফরেন কি এরর বলে না কোন কি ব্যর্থ হলো। ডেটা আছে এমন টেবিলে ফরেন কি ভাঙা সারিগুলোর তালিকা পেতে
PRAGMA foreign_key_check;চালান। - ব্যর্থ স্টেটমেন্ট কিছুই বদলায় না। টেবিলে এখনও আগের সেই ২টা সারিই আছে।
NULL ঢুকে পড়ে CHECK আর UNIQUE-এর ফাঁক দিয়ে
CHECK একটা সারি তখনই ফেরায়, যখন শর্তটা মিথ্যা। মান NULL হলে শর্তের ফল অজানা, আর অজানা পাস করে যায়। তাই label NULL হতে পারে (লেবেল না দেওয়া রিভিউ), আর এ কারণেই শপের stock কলামে CHECK (stock >= 0) থাকা সত্ত্বেও কোর্সগুলোর stock NULL। একইভাবে UNIQUE সব NULL-কে আলাদা ধরে (SQLite, MySQL আর PostgreSQL-এ; SQL Server অবশ্য একটার বেশি NULL নেয় না), তাই একই প্রোডাক্টে যত খুশি নামহীন রিভিউ দেওয়া যায়:
INSERT INTO reviews (product_id, customer_id, rating, body) VALUES (5, NULL, 4, 'Great colours.');
INSERT INTO reviews (product_id, customer_id, rating, body) VALUES (5, NULL, 3, 'Too big for my desk.');
SELECT review_id, product_id, customer_id, rating, label FROM reviews ORDER BY review_id;+-----------+------------+-------------+--------+----------+
| review_id | product_id | customer_id | rating | label |
+-----------+------------+-------------+--------+----------+
| 1 | 3 | 2 | 5 | positive |
| 2 | 4 | 7 | 2 | negative |
| 3 | 5 | NULL | 4 | NULL |
| 4 | 5 | NULL | 3 | NULL |
+-----------+------------+-------------+--------+----------+মান বাধ্যতামূলক হলে সেটা NOT NULL দিয়ে বলুন; শুধু CHECK যথেষ্ট নয়।
ফরেন কি: চালু, বন্ধ, আর MySQL-এর ফাঁদ
SQLite ফরেন কি যাচাই করে কেবল তখন, যখন কানেকশন PRAGMA foreign_keys = ON দিয়ে চায়। sqlhelp.py আপনার হয়ে এটা করে দেয়, DB Browser for SQLite-ও ডিফল্টভাবে করে, কিন্তু সাধারণ sqlite3.connect() আর sqlite3 শেল শুরু হয় এটা বন্ধ রেখে:
import sqlite3
raw = sqlite3.connect("shop.db") # সাধারণ কানেকশন: কোনো PRAGMA নেই
print("foreign_keys =", raw.execute("PRAGMA foreign_keys").fetchone()[0])
raw.execute("INSERT INTO reviews (product_id, rating, body) VALUES (999, 5, 'Orphan review')")
print("orphans:", raw.execute("SELECT COUNT(*) FROM reviews WHERE product_id = 999").fetchone()[0])
raw.rollback() # বাতিল করে তারপর বন্ধ করুন
raw.close()foreign_keys = 0
orphans: 1৯৯৯ নম্বর প্রোডাক্টের রিভিউ কোনো আপত্তি ছাড়াই ঢুকে গেল। তাই যে প্রোগ্রামই SQLite-এ ডেটা লেখে, কানেক্ট করার ঠিক পরেই তাতে PRAGMA-টা চালান।
MySQL-এর আবার নিজস্ব ফাঁদ আছে। কলামের পাশে লেখা REFERENCES MySQL 8.0 পড়ে, তারপর চুপচাপ উপেক্ষা করে (এর সাপোর্ট এসেছে কেবল MySQL 9.0-তে)। এই ইনলাইন ধরনে লেখা একটা পরীক্ষামূলক টেবিল দেখুন:
-- MySQL 8.0: ইনলাইন REFERENCES মেনে নেয়, তারপর উপেক্ষা করে
CREATE TABLE wishlist (
wish_id INT PRIMARY KEY,
product_id INT NOT NULL REFERENCES products (product_id) -- কোনো ফরেন কি তৈরি হয় না
);
INSERT INTO wishlist (wish_id, product_id) VALUES (1, 999);
SELECT * FROM wishlist;+---------+------------+
| wish_id | product_id |
+---------+------------+
| 1 | 999 |
+---------+------------+৯৯৯ নম্বর প্রোডাক্ট নেই, তবু সারিটা জমা হয়ে গেল, কোনো এরর বা সতর্কবার্তা ছাড়াই। ঠিক এ কারণেই shop.sql প্রতিটা ফরেন কি লিখেছে টেবিল কনস্ট্রেইন্ট হিসেবে, প্রতিটা CREATE TABLE-এর শেষে। MySQL-এ সবসময় লিখুন FOREIGN KEY (col) REFERENCES other (col); এটা সব ডেটাবেসেই কাজ করে।
প্রতিটা ডেটাবেসে নিজে থেকে নম্বর পাওয়া id
| ডেটাবেস | কীভাবে লিখবেন | মন্তব্য |
|---|---|---|
| SQLite | id INTEGER PRIMARY KEY | পরের id = সর্বোচ্চ + ১, তাই শেষ সারি মুছলে তার id আবার ব্যবহার হতে পারে |
| SQLite | id INTEGER PRIMARY KEY AUTOINCREMENT | একবার ব্যবহার হওয়া id আর কখনো ফিরে আসে না (sqlite_sequence-এ মনে রাখে); একটু ধীর |
| MySQL | id INT AUTO_INCREMENT PRIMARY KEY | টেবিলে একটাই |
| PostgreSQL | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY | SQL স্ট্যান্ডার্ড; পুরোনো সংক্ষিপ্ত রূপ SERIAL-ও এখনো দেখবেন |
| SQL Server | id INT IDENTITY(1,1) PRIMARY KEY | ১ থেকে শুরু, ১ করে বাড়ে |
একই টেবিল দুই সার্ভারে। SQLite-এর মতো MySQL সংস্করণেও ফরেন কি লেখা হয়েছে টেবিল কনস্ট্রেইন্ট হিসেবে, যাতে সেটা সত্যিই তৈরি হয়:
-- MySQL
CREATE TABLE reviews (
review_id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
customer_id INT,
rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),
body TEXT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'new',
UNIQUE (product_id, customer_id),
FOREIGN KEY (product_id) REFERENCES products (product_id)
);
INSERT INTO reviews (product_id, customer_id, rating, body)
VALUES (3, 2, 5, 'Great keys'), (4, 7, 2, 'Broke fast');
SELECT review_id, product_id, rating, status FROM reviews ORDER BY review_id;
INSERT INTO reviews (product_id, customer_id, rating, body) VALUES (4, 2, 9, 'Amazing!');+-----------+------------+--------+--------+
| review_id | product_id | rating | status |
+-----------+------------+--------+--------+
| 1 | 3 | 5 | new |
| 2 | 4 | 2 | new |
+-----------+------------+--------+--------+
Error: ERROR 3819: Check constraint 'reviews_chk_1' is violated.-- PostgreSQL
CREATE TABLE reviews (
review_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
product_id INTEGER NOT NULL,
customer_id INTEGER,
rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5),
body TEXT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'new',
UNIQUE (product_id, customer_id),
FOREIGN KEY (product_id) REFERENCES products (product_id),
FOREIGN KEY (customer_id) REFERENCES customers (customer_id)
);
INSERT INTO reviews (product_id, customer_id, rating, body)
VALUES (3, 2, 5, 'Great keys'), (4, 7, 2, 'Broke fast');
SELECT review_id, product_id, rating, status FROM reviews ORDER BY review_id;
INSERT INTO reviews (product_id, customer_id, rating, body) VALUES (4, 2, 9, 'Amazing!');+-----------+------------+--------+--------+
| review_id | product_id | rating | status |
+-----------+------------+--------+--------+
| 1 | 3 | 5 | new |
| 2 | 4 | 2 | new |
+-----------+------------+--------+--------+
Error: new row for relation "reviews" violates check constraint "reviews_rating_check"একই নিয়ম, তিন রকম মেসেজ; নামহীন কনস্ট্রেইন্ট পেয়েছে বানানো নাম reviews_chk_1 আর reviews_rating_check। GENERATED ALWAYS হাতে লেখা id-ও ফিরিয়ে দেয়, ফলে নম্বরগুলো এলোমেলো হয় না:
-- PostgreSQL
INSERT INTO reviews (review_id, product_id, customer_id, rating, body) VALUES (7, 5, 2, 4, 'Sharp.');Error: cannot insert a non-DEFAULT value into column "review_id"ALTER TABLE: ডেটা আছে এমন টেবিল বদলানো
চাহিদা বদলায়: প্রোডাক্ট টিম একটা "helpful" ভোটের কাউন্টার চায়, আর body-র নাম আসলে review_text হওয়া উচিত। SQLite কলাম যোগ, নাম বদল, কলাম বাদ আর টেবিলের নাম বদল সাপোর্ট করে:
ALTER TABLE reviews ADD COLUMN helpful_votes INTEGER NOT NULL DEFAULT 0;
ALTER TABLE reviews RENAME COLUMN body TO review_text; -- SQLite 3.25+
ALTER TABLE reviews DROP COLUMN status; -- SQLite 3.35+
SELECT review_id, rating, review_text, helpful_votes FROM reviews ORDER BY review_id;+-----------+--------+-------------------------------+---------------+
| review_id | rating | review_text | helpful_votes |
+-----------+--------+-------------------------------+---------------+
| 1 | 5 | Keys feel great, a bit loud. | 0 |
| 2 | 2 | Stopped working after a week. | 0 |
| 3 | 4 | Great colours. | 0 |
| 4 | 3 | Too big for my desk. | 0 |
+-----------+--------+-------------------------------+---------------+আগের সারিগুলো ডিফল্ট ০ পেয়েছে। ডিফল্ট না দিলে NOT NULL কলাম যোগ করা যায় না, কারণ পুরোনো সারিগুলোতে বসানোর মতো কিছু থাকবে না:
ALTER TABLE reviews ADD COLUMN verified INTEGER NOT NULL;Error: Cannot add a NOT NULL column with default value NULLSQLite-এর ALTER TABLE এখানেই শেষ। এটা কলামের টাইপ বদলাতে পারে না, আগে থেকে থাকা কলামে কনস্ট্রেইন্ট যোগ বা বাদ দিতে পারে না, আর কি, UNIQUE নিয়ম বা ইনডেক্সের অংশ এমন কলামও বাদ দিতে পারে না:
ALTER TABLE reviews ALTER COLUMN review_text TYPE VARCHAR(500);Error: near "ALTER": syntax errorএ ধরনের বদলের জন্য SQLite-এ টেবিলটা নতুন করে বানাতে হয়: যে সংজ্ঞা চান সেই অনুযায়ী নতুন টেবিল বানান, INSERT … SELECT দিয়ে সারিগুলো কপি করুন, পুরোনো টেবিল ড্রপ করে নতুনটার নাম বদলান, সবটা একটা ট্রানজ্যাকশনের ভেতরে। সার্ভারগুলো অবশ্য টেবিল নতুন করে না বানিয়েই কলাম বদলাতে পারে:
-- PostgreSQL
ALTER TABLE reviews ALTER COLUMN body TYPE VARCHAR(500);
ALTER TABLE reviews ADD COLUMN helpful_votes INTEGER NOT NULL DEFAULT 0;
ALTER TABLE reviews ADD CONSTRAINT votes_not_negative CHECK (helpful_votes >= 0);
UPDATE reviews SET helpful_votes = -1 WHERE review_id = 1;Error: new row for relation "reviews" violates check constraint "votes_not_negative"-- MySQL
ALTER TABLE reviews MODIFY COLUMN body VARCHAR(500) NOT NULL; -- MySQL-এ পুরো কলামটা আবার লিখতে হয়
ALTER TABLE reviews RENAME COLUMN status TO review_status;
ALTER TABLE reviews ADD COLUMN helpful_votes INT NOT NULL DEFAULT 0;
SELECT review_id, body, review_status, helpful_votes FROM reviews ORDER BY review_id;+-----------+------------+---------------+---------------+
| review_id | body | review_status | helpful_votes |
+-----------+------------+---------------+---------------+
| 1 | Great keys | new | 0 |
| 2 | Broke fast | new | 0 |
+-----------+------------+---------------+---------------+ডেটা আছে এমন টেবিলে কনস্ট্রেইন্ট যোগ করলে পুরোনো সারিগুলোও যাচাই হয়; কোনোটা নিয়ম ভাঙলে ALTER ব্যর্থ হয়, আগে ডেটা পরিষ্কার করতে হবে। প্রোডাকশনের বড় টেবিলে স্কিমা বদলাতে সাবধান হতে হয় (অ্যাডভান্সড টিউটোরিয়ালে মাইগ্রেশন নিয়ে আছে)।
DROP, TRUNCATE আর DELETE
| স্টেটমেন্ট | কী মুছে ফেলে | টেবিল থাকে? | মন্তব্য |
|---|---|---|---|
DELETE FROM t WHERE … | মিলে যাওয়া সারিগুলো | হ্যাঁ | সারি ধরে ধরে, ট্রানজ্যাকশনের ভেতরে রোলব্যাক করা যায়; WHERE না দিলে সব সারি মুছে যায় |
TRUNCATE TABLE t | সব সারি, দ্রুত | হ্যাঁ | MySQL, PostgreSQL, SQL Server। SQLite-এ TRUNCATE নেই: DELETE FROM t; লিখুন (SQLite এটা দ্রুত করে নেয়)। MySQL-এ TRUNCATE সঙ্গে সঙ্গে কমিট হয়ে যায়, রোলব্যাক করা যায় না |
DROP TABLE t | সারি আর টেবিলের সংজ্ঞা দুটোই | না | যে স্ক্রিপ্ট দুবার চলতে পারে, সেখানে IF EXISTS দিন |
ফরেন কি তিনটাকেই পাহারা দেয়। যে টেবিলের সারিকে অন্য টেবিল এখনো রেফার করছে, সেটা সরানো যায় না:
DROP TABLE customers;Error: FOREIGN KEY constraint failed-- PostgreSQL
TRUNCATE TABLE customers;Error: cannot truncate a table referenced in a foreign key constraintঝটপট একটা ব্যাকআপ কপি বানানো লোভনীয়: CREATE TABLE … AS SELECT সারিগুলো কপি করে, কিন্তু কনস্ট্রেইন্ট কপি করে না। আসল টেবিল আর কপির জন্য SQLite যে কলাম-সংজ্ঞা রেখেছে, তুলনা করুন (pragma_table_info একটা টেবিলের কলামগুলোর তালিকা দেয়):
CREATE TABLE products_copy AS SELECT * FROM products;
SELECT name, type, "notnull", pk FROM pragma_table_info('products') ORDER BY cid;
SELECT name, type, "notnull", pk FROM pragma_table_info('products_copy') ORDER BY cid;+-------------+--------------+---------+----+
| name | type | notnull | pk |
+-------------+--------------+---------+----+
| product_id | INTEGER | 0 | 1 |
| name | VARCHAR(100) | 1 | 0 |
| category_id | INTEGER | 0 | 0 |
| price | INTEGER | 1 | 0 |
| stock | INTEGER | 0 | 0 |
+-------------+--------------+---------+----+
+-------------+------+---------+----+
| name | type | notnull | pk |
+-------------+------+---------+----+
| product_id | INT | 0 | 0 |
| name | TEXT | 0 | 0 |
| category_id | INT | 0 | 0 |
| price | INT | 0 | 0 |
| stock | INT | 0 | 0 |
+-------------+------+---------+----+প্রাইমারি কি নেই, NOT NULL নেই, CHECK (price > 0)-ও নেই। ফেলে দেওয়ার মতো একটা স্ন্যাপশট হিসেবে চলে, আসল টেবিল হিসেবে ভুল। এবার গুছিয়ে নিন। কোনো পাতা শেষ হলে নিজের বানানো টেবিলগুলো ড্রপ করুন: sqlhelp.reset() শুধু শপের টেবিলগুলো নতুন করে বানায়, তাই reviews থেকে যাবে, আর সেটা তখনো এমন products-কে রেফার করবে, যা তার নিচে ড্রপ করে আবার বানানো হয়েছে:
DELETE FROM products_copy; -- SQLite-এর "truncate"
SELECT COUNT(*) AS rows_left FROM products_copy;
DROP TABLE products_copy;
DROP TABLE IF EXISTS reviews;
DROP TABLE IF EXISTS reviews; -- দ্বিতীয়বার: IF EXISTS থাকায় কিছুই হয় না+-----------+
| rows_left |
+-----------+
| 0 |
+-----------+বাস্তব সিস্টেমে কোথায় দেখবেন
- লেবেলিং টুল অ্যানোটেশন রাখে
CHECK (label IN (…))দেওয়া টেবিলে, যাতে লেবেলের টাইপো ট্রেনিং ডেটায় পৌঁছাতে না পারে। - ইভ্যালুয়েশন লগ
UNIQUE (model_name, question_id)ব্যবহার করে, যাতে ইভ্যাল স্ক্রিপ্ট আবার চালালে স্কোর দুবার গোনা না হয়। - ডেটা পাইপলাইন ইচ্ছে করেই কনস্ট্রেইন্টওয়ালা টেবিলে লোড করে: খারাপ একটা ব্যাচ ভুল ড্যাশবোর্ড বানানোর আগেই, লোডের সময়ে স্পষ্ট এরর দিয়ে থেমে যায়।
সাধারণ ভুল
- ফরেন কি আছে, কিন্তু খাটানো হচ্ছে না। PRAGMA ছাড়া SQLite, বা ইনলাইন
REFERENCES-সহ MySQL 8.0 অনাথ (orphan) সারি চুপচাপ মেনে নেয়। সমাধান: SQLite-এর প্রতিটা কানেকশনেPRAGMA foreign_keys = ON, আর MySQL-এ টেবিল-লেভেলেরFOREIGN KEY (…) REFERENCES …। - NULL আটকাতে CHECK বা UNIQUE-এর ওপর ভরসা করা। দুটোই NULL ঢুকতে দেয়। সমাধান: মান বাধ্যতামূলক হলে
NOT NULLযোগ করুন, যেমনrating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5)। CREATE TABLE … AS SELECTদিয়ে কপি করে সেটাকে আসল টেবিল ভাবা। কপিতে কোনো কি বা নিয়ম থাকে না। সমাধান: পুরোCREATE TABLEলিখুন, তারপরINSERT INTO new_table SELECT … FROM old_table;।- নামহীন কনস্ট্রেইন্ট।
reviews_chk_1 is violatedসহকর্মীকে কিছুই বলে না। সমাধান:CONSTRAINT rating_1_to_5 CHECK (rating BETWEEN 1 AND 5)। - সারি আছে এমন টেবিলে বাধ্যতামূলক কলাম যোগ করা। এটা ব্যর্থ হয় (বা সার্ভারে প্রতিটা পুরোনো সারির জন্য একটা মান লাগে)। সমাধান: একটা
DEFAULTদিন, অথবা NULL-যোগ্য কলাম হিসেবে যোগ করেUPDATEদিয়ে ভরুন, তারপর নিয়মটা যোগ করুন।
নিজে চেষ্টা করুন
- সহজ:
newsletterনামে একটা টেবিল বানান: নিজে থেকে নম্বর পাওয়াsubscriber_id, বাধ্যতামূলক আর ইউনিকemail, আরlanguage, যার ডিফল্ট'bn'।languageছাড়া একজন সাবস্ক্রাইবার ইনসার্ট করে সারিটা দেখুন। - মাঝারি: ML এক্সপেরিমেন্টের জন্য
model_runsটেবিল বানান:run_id,model_name(বাধ্যতামূলক),accuracy(০ থেকে ১-এর মধ্যে একটাREAL, বাধ্যতামূলক) আরstatus, যা হতে হবে'running','finished'বা'failed', ডিফল্ট'running'। একটা বৈধ রান ইনসার্ট করুন, তারপরaccuracy-তে ১.৭ দিয়ে চেষ্টা করে এররটা পড়ুন। - কঠিন: SQLite আগে থেকে থাকা কলামে
CHECKযোগ করতে পারে না। "languageহতে হবে'bn'বা'en'" নিয়মটাnewsletter-এ যোগ করুন: একটা ট্রানজ্যাকশনের ভেতরে টেবিলটা নতুন করে বানিয়ে, সারিগুলো রেখে।
উত্তর
-- সহজ
CREATE TABLE newsletter (
subscriber_id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
language TEXT NOT NULL DEFAULT 'bn'
);
INSERT INTO newsletter (email) VALUES ('rafiq@example.com');
SELECT * FROM newsletter;+---------------+-------------------+----------+
| subscriber_id | email | language |
+---------------+-------------------+----------+
| 1 | rafiq@example.com | bn |
+---------------+-------------------+----------+-- মাঝারি
CREATE TABLE model_runs (
run_id INTEGER PRIMARY KEY,
model_name TEXT NOT NULL,
accuracy REAL NOT NULL CONSTRAINT accuracy_0_to_1 CHECK (accuracy BETWEEN 0 AND 1),
status TEXT NOT NULL DEFAULT 'running'
CHECK (status IN ('running', 'finished', 'failed'))
);
INSERT INTO model_runs (model_name, accuracy) VALUES ('logreg-v1', 0.87);
INSERT INTO model_runs (model_name, accuracy) VALUES ('logreg-v2', 1.7);Error: CHECK constraint failed: accuracy_0_to_1-- কঠিন: SQLite-এ টেবিল নতুন করে বানানোর রেসিপি
BEGIN;
CREATE TABLE newsletter_new (
subscriber_id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
language TEXT NOT NULL DEFAULT 'bn' CHECK (language IN ('bn', 'en'))
);
INSERT INTO newsletter_new (subscriber_id, email, language)
SELECT subscriber_id, email, language FROM newsletter;
DROP TABLE newsletter;
ALTER TABLE newsletter_new RENAME TO newsletter;
COMMIT;
SELECT * FROM newsletter;
INSERT INTO newsletter (email, language) VALUES ('mitu@example.com', 'fr');+---------------+-------------------+----------+
| subscriber_id | email | language |
+---------------+-------------------+----------+
| 1 | rafiq@example.com | bn |
+---------------+-------------------+----------+
Error: CHECK constraint failed: language IN ('bn', 'en')সারসংক্ষেপ
- কনস্ট্রেইন্ট হলো এমন নিয়ম, যা ডেটাবেস প্রতিটা সারিতে খাটায়:
PRIMARY KEY,FOREIGN KEY,UNIQUE,NOT NULL,CHECK, আর অনুপস্থিত মানের জন্যDEFAULT। CHECKআরUNIQUENULL ঢুকতে দেয়; মান বাধ্যতামূলক হলেNOT NULLদিন। জরুরি কনস্ট্রেইন্টের নাম দিন।- ফরেন কি-র জন্য SQLite-এ লাগে
PRAGMA foreign_keys = ON, আর MySQL 8.0-এ টেবিল-লেভেলের লেখা। - নিজে থেকে id:
INTEGER PRIMARY KEY(SQLite),AUTO_INCREMENT(MySQL),GENERATED … AS IDENTITYবাSERIAL(PostgreSQL),IDENTITY(SQL Server)। - SQLite-এর
ALTER TABLEকলাম যোগ, নাম বদল আর বাদ দিতে পারে; এর বাইরে সবকিছুর জন্য টেবিল নতুন করে বানাতে হয়।DELETEসারি মোছে,TRUNCATEদ্রুত খালি করে (SQLite-এ নেই),DROPটেবিলটাই সরিয়ে দেয়।
এরপর: INSERT, UPDATE, DELETE আর আপসার্ট, নিরাপদে। টেবিল আর নিয়ম তৈরি হয়ে গেছে; এবার সারি যোগ, বদল আর মুছবেন, আগে থেকে থাকতে পারে এমন ডেটা আপসার্ট করবেন, আর এমন একটা রুটিন মেনে চলবেন যাতে WHERE ছাড়া একটা UPDATE আপনার দিনটা নষ্ট করতে না পারে।