অধ্যায় 6 · প্রজেক্ট ও পরের ধাপ
সারসংক্ষেপ, চিট শিট আর এরপর কী
- পৃষ্ঠা 22 / 22
- 11 মিনিট পড়া
এই টিউটোরিয়াল শুরুর সময় আপনি কখনো ডেটাবেস ব্যবহার করেননি। এখন আপনি একটা স্কিমা পড়তে পারেন, কাস্টমার, প্রোডাক্ট, অর্ডার আর পেমেন্ট নিয়ে প্রায় যেকোনো প্রশ্ন করতে পারেন, নিরাপদে ডেটা বদলাতে পারেন, আর উত্তরগুলোকে Python থেকে একটা যাচাই করা রিপোর্টে পরিণত করতে পারেন। এই পাতায় সবকিছু এক জায়গায়: কাজের সময় খুলে রাখার মতো একটা চিট শিট, SQL আসলে কোন ক্রমে একটা কোয়েরি চালায়, যে ডায়ালেক্ট-পার্থক্যগুলো ভোগায়, একটা শব্দকোষ, আর এরপর কী।
যা শিখবেন
- পুরো বিগিনার টিউটোরিয়ালের সিনট্যাক্স এক পাতায়, কাজ অনুযায়ী সাজানো।
- কোয়েরির লজিক্যাল ক্রম, আর কেন এটা দিয়েই বিগিনারদের বেশিরভাগ এরর বোঝা যায়।
- SQLite, MySQL আর PostgreSQL-এর যে পার্থক্যগুলোর মুখোমুখি সবচেয়ে বেশি হবেন।
- অ্যাডভান্সড টিউটোরিয়ালে কী আছে, আর ততদিন কীভাবে অনুশীলন চালিয়ে যাবেন।
এখন আপনি যা পারেন
- টেবিল, সারি, কলাম, প্রাইমারি আর ফরেন কি ব্যাখ্যা করা, আর one-to-many ও many-to-many রিলেশনশিপ কীভাবে রাখা হয় তা বোঝা।
SELECTদিয়ে ডেটা পড়া,WHEREদিয়ে ছাঁকা (NULL,IN,LIKE,BETWEEN-সহ), সাজানো আর পেজিং করা।- ঠিক টাইপ আর কনস্ট্রেইন্ট দিয়ে টেবিল বানানো, আর নিরাপদে সারি যোগ করা, বদলানো, মোছা ও আপসার্ট করা।
- অ্যাগ্রিগেট,
GROUP BY,HAVINGআরCASE WHENদিয়ে সারাংশ করা। - INNER, OUTER, CROSS আর সেলফ জয়েন, সাবকোয়েরি,
EXISTSআর সেট অপারেশন দিয়ে টেবিল জোড়া লাগানো — দুবার না গুনে। - এরর মেসেজ পড়া, ভাঙা কোয়েরি ছোট করে আনা আর বাগ খুঁজে বের করা।
চিট শিট
| কাজ | সিনট্যাক্স |
|---|---|
| কলাম বাছাই, হিসাব, নতুন নাম | SELECT name, price * 2 AS double_price FROM products; |
| ডুপ্লিকেট ছাড়া মান | SELECT DISTINCT city FROM customers; |
| সারি ছাঁকা | WHERE price >= 1000 AND (stock > 0 OR stock IS NULL) |
| পরিসর, তালিকা, প্যাটার্ন | BETWEEN 1000 AND 3000, IN ('Dhaka', 'Sylhet'), LIKE 'Py%' |
| অনুপস্থিত মান | IS NULL, IS NOT NULL, COALESCE(city, 'Unknown') |
| সাজানো আর পেজিং | ORDER BY price DESC, product_id LIMIT 5 OFFSET 10 |
| টেক্সট, সংখ্যা, তারিখ | LOWER, LENGTH, SUBSTR, REPLACE, ROUND(x, 2), strftime('%Y-%m', d), CAST(x AS INTEGER) |
| নিরাপদ ভাগ | a * 1.0 / NULLIF(b, 0) |
| সারাংশ | COUNT(*), COUNT(col), COUNT(DISTINCT col), SUM, AVG, MIN, MAX |
| গ্রুপ | GROUP BY category_id HAVING COUNT(*) > 1 |
| মানের ভেতরে শর্ত | CASE WHEN price < 2000 THEN 'cheap' ELSE 'premium' END |
| শর্তসাপেক্ষ গণনা | SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) |
| শুধু মেলা সারি | FROM orders AS o JOIN customers AS c ON c.customer_id = o.customer_id |
| না-মেলা সারিও রাখা | LEFT JOIN … WHERE right_table.key IS NULL দিয়ে অনুপস্থিতগুলো খোঁজা |
| সব সম্ভাব্য জোড়া | CROSS JOIN; টেবিলের সাথে নিজেরই জয়েন: employees AS e JOIN employees AS m ON m.employee_id = e.manager_id |
| কোয়েরির ভেতরে কোয়েরি | WHERE price > (SELECT AVG(price) FROM products), FROM (SELECT …) AS t |
| মেলে এমন কিছু আছে কি? | WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.customer_id) |
| ফলাফল একটার নিচে আরেকটা | UNION (ডুপ্লিকেট বাদ দেয়), UNION ALL, INTERSECT, EXCEPT |
| টেবিল বানানো | CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, qty INTEGER CHECK (qty > 0), cat_id INTEGER, FOREIGN KEY (cat_id) REFERENCES categories (category_id)); |
| টেবিল বদলানো | ALTER TABLE t ADD COLUMN note TEXT;, DROP TABLE IF EXISTS t; |
| সারি যোগ, বদল, মোছা | INSERT INTO t (a, b) VALUES (1, 'x');, UPDATE t SET b = 'y' WHERE id = 1;, DELETE FROM t WHERE id = 1; |
| INSERT, নয়তো UPDATE (আপসার্ট) | INSERT … ON CONFLICT (id) DO UPDATE SET b = excluded.b (SQLite, PostgreSQL) |
| Python থেকে | con.execute("SELECT … WHERE id = ?", (5,)), pd.read_sql_query(sql, con) |
SQL কোন ক্রমে কোয়েরি চালায়
আপনি লেখেন SELECT দিয়ে, কিন্তু ডেটাবেস কোয়েরিটা পড়ে অন্য ক্রমে। "এটা কেন কাজ করছে না?" ধরনের বেশিরভাগ মুহূর্ত এই একটা ছবি দিয়েই বোঝা যায়:
লেখার ক্রম লজিক্যাল ক্রম (আগে যা ঘটে)
------------- ----------------------------------
SELECT 5 1 FROM / JOIN কোন সারিগুলো আছে
FROM 1 2 WHERE কিছু সারি রাখা
WHERE 2 3 GROUP BY গ্রুপ বানানো
GROUP BY 3 4 HAVING কিছু গ্রুপ রাখা
HAVING 4 5 SELECT কলাম আর অ্যালিয়াস হিসাব
ORDER BY 6 6 ORDER BY সাজানো (অ্যালিয়াস এখানে চেনা)
LIMIT 7 7 LIMIT ফলাফল কেটে ছোট করাতাই SELECT-এ বানানো অ্যালিয়াস WHERE-এ তখনো জন্মায়নি, SUM-এর ওপর ফিল্টার বসবে HAVING-এ, আর ORDER BY-এ অ্যালিয়াসগুলো ব্যবহার করা যায়। নিচের কোয়েরিটা প্রতিটা ধাপ ব্যবহার করে; কমেন্টে ক্রমটা দেওয়া আছে:
SELECT c.name AS category, -- 5
COUNT(DISTINCT o.order_id) AS orders,
SUM(oi.quantity * oi.unit_price) AS revenue
FROM order_items AS oi -- 1
JOIN orders AS o ON o.order_id = oi.order_id
JOIN products AS p ON p.product_id = oi.product_id
JOIN categories AS c ON c.category_id = p.category_id
WHERE o.status IN ('delivered', 'shipped') -- 2
GROUP BY c.category_id, c.name -- 3
HAVING COUNT(DISTINCT o.order_id) >= 4 -- 4
ORDER BY revenue DESC, c.category_id -- 6
LIMIT 2; -- 7+-------------+--------+---------+
| category | orders | revenue |
+-------------+--------+---------+
| Electronics | 6 | 31550 |
| Accessories | 4 | 8900 |
+-------------+--------+---------+প্রতিটা ধাপের ছাপ ফলাফলে দেখা যায়: গ্রুপ বানানোর আগেই WHERE বাতিল আর pending অর্ডার বাদ দেয়, HAVING বাদ দেয় Courses-কে (মাত্র ৩টা অর্ডার), Furniture কখনো আসেই না কারণ INNER JOIN শুরু হয় বিক্রি হওয়া আইটেম থেকে, আর সাজানোর পর LIMIT কেটে দেয় Books-কে (৪টা অর্ডার, ৮,৬০০ টাকা)।
যে ডায়ালেক্ট-পার্থক্যগুলো মনে রাখবেন
| বিষয় | SQLite | MySQL 8 | PostgreSQL |
|---|---|---|---|
| টেক্সট জোড়া | a || b | CONCAT(a, b) (|| মানে OR) | a || b বা CONCAT |
| তারিখের মাস | strftime('%Y-%m', d) | DATE_FORMAT(d, '%Y-%m') | to_char(d, 'YYYY-MM') |
| বড়-ছোট হাতের অক্ষর না মেনে মেলানো | LIKE বড়-ছোট হাত মানে না (শুধু A–Z) | কোলেশনের ওপর নির্ভর করে (সাধারণত মানে না) | LIKE বড়-ছোট হাত মানে; ILIKE ব্যবহার করুন |
| আপসার্ট | ON CONFLICT … DO UPDATE | ON DUPLICATE KEY UPDATE | ON CONFLICT … DO UPDATE |
| নিজে থেকে নম্বর পাওয়া কি | INTEGER PRIMARY KEY | AUTO_INCREMENT | GENERATED ALWAYS AS IDENTITY |
| বুলিয়ান | 0 / 1 | TINYINT(1) | BOOLEAN |
| পূর্ণসংখ্যার ভাগ | 7 / 2 = 3 | 7 / 2 = 3.5000 | 7 / 2 = 3 |
ORDER BY … ASC-এ NULL | শুরুতে | শুরুতে | শেষে |
| প্রথম nটা সারি | LIMIT n | LIMIT n | LIMIT n বা FETCH FIRST n ROWS ONLY |
RIGHT / FULL OUTER JOIN | দুটোই 3.39 থেকে | RIGHT আছে; FULL নেই (UNION ALL দিয়ে বানাতে হয়) | দুটোই আছে |
INTERSECT / EXCEPT | আছে | 8.0.31 থেকে | আছে |
| টাইপ | নমনীয় (affinity); চাইলে STRICT টেবিল | টাইপ মানা হয় | টাইপ মানা হয়, সবচেয়ে কড়াভাবে |
SQL Server (এই টিউটোরিয়ালে যার শুধু উল্লেখ আছে) LIMIT-এর বদলে লেখে SELECT TOP n, আর SQLite ও MySQL-এর মতো NULL-কে শুরুতে রাখে। টেবিলের প্রথম সারিটাই মানুষকে সবচেয়ে বেশি অবাক করে। একই এক্সপ্রেশন, আলাদা উত্তর:
SELECT 'Nadia' || ' Rahman' AS joined;+--------------+
| joined |
+--------------+
| Nadia Rahman |
+--------------+-- MySQL
SELECT 'Nadia' || ' Rahman' AS joined, CONCAT('Nadia', ' Rahman') AS concatenated;+--------+--------------+
| joined | concatenated |
+--------+--------------+
| 0 | Nadia Rahman |
+--------+--------------+-- PostgreSQL
SELECT 'Nadia' || ' Rahman' AS joined, 7 / 2 AS int_division, ROUND(7 / 2.0, 2) AS decimal_division;+--------------+--------------+------------------+
| joined | int_division | decimal_division |
+--------------+--------------+------------------+
| Nadia Rahman | 3 | 3.50 |
+--------------+--------------+------------------+MySQL ||-কে পড়ে লজিক্যাল OR হিসেবে, দুটো স্ট্রিংকেই সংখ্যায় (০) বদলায় আর ০ ফেরত দেয়: কোনো এরর নেই, শুধু একটা ওয়ার্নিং, যা বেশিরভাগ ক্লায়েন্ট দেখায়ই না। এক ডেটাবেসের কোয়েরি আরেকটায় নিলে, যে ডেটাবেসে চলবে সেখানেই পরীক্ষা করে নিন; "চলেছে" আর "ঠিক আছে" এক কথা নয়।
শব্দকোষ
| পরিভাষা | মানে |
|---|---|
| ডেটাবেস / DBMS | গোছানো ডেটার ভাণ্ডার / যে সফটওয়্যার সেটা চালায় (SQLite, MySQL, PostgreSQL) |
| টেবিল, সারি, কলাম | এক ধরনের রেকর্ডের সমষ্টি; একটা রেকর্ড; প্রতিটা রেকর্ডের একটা বৈশিষ্ট্য |
| প্রাইমারি কি | যে কলাম (বা কলামগুলো) প্রতিটা সারিকে আলাদাভাবে চেনায় |
| ফরেন কি | যে কলাম অন্য টেবিলের প্রাইমারি কি-র দিকে নির্দেশ করে |
| স্কিমা | একটা ডেটাবেসের টেবিল, কলাম, টাইপ আর রিলেশনশিপ |
| কনস্ট্রেইন্ট | ডেটাবেস যে নিয়ম মানতে বাধ্য করে: NOT NULL, UNIQUE, CHECK, কি |
| NULL | "অজানা বা অনুপস্থিত" — শূন্যও নয়, ফাঁকা টেক্সটও নয়; তুলনা করুন IS NULL দিয়ে |
| অ্যাগ্রিগেট | যে ফাংশন অনেক সারিকে একটা মানে পরিণত করে: COUNT, SUM, AVG |
| জয়েন | একটা শর্তে মেলে এমন দুই টেবিলের সারি জোড়া লাগানো |
| ফ্যান-আউট | one-to-many টেবিলের সাথে জয়েনে সারি গুণ হয়ে যাওয়া, যাতে যোগফল ফুলে ওঠে |
| সাবকোয়েরি / কোরিলেটেড সাবকোয়েরি | কোয়েরির ভেতরে কোয়েরি / যেটা বাইরের সারির উল্লেখ করে আর প্রতি সারিতে চলে |
| আপসার্ট | সারি insert করা, অথবা কি আগে থেকেই থাকলে update করা |
| নরমালাইজেশন | প্রতিটা তথ্য কপি না করে একবার রাখা আর কি দিয়ে যুক্ত করা |
| ট্রানজ্যাকশন | কয়েকটা পরিবর্তন, যা হয় সবগুলো একসাথে সফল হয়, নয়তো সবগুলো একসাথে বাতিল হয় (BEGIN … COMMIT বা ROLLBACK) |
| ডায়ালেক্ট | একটা নির্দিষ্ট ডেটাবেসের SQL-এর রূপ |
সাধারণ ভুল
IS NULL-এর বদলে= NULL।WHERE city = NULLচুপচাপ কিছুই মেলায় না। সমাধান:WHERE city IS NULL।- তালিকায় NULL থাকা অবস্থায়
NOT IN। একটা NULL-ই পুরো পরীক্ষাকে "অজানা" করে দেয়, ফলাফল খালি আসে। সমাধান:NOT EXISTS, অথবা সাবকোয়েরি থেকে NULL বাদ দিন। - one-to-many জয়েনের পর যোগ করা। অর্ডার ৬-এর দুটো পেমেন্ট, তাই এর আইটেমগুলো দুবার গোনা হয়। সমাধান: আগে প্রতিটা পাশ অ্যাগ্রিগেট করুন, তারপর জয়েন।
WHEREছাড়াUPDATEবাDELETE। সব সারি বদলে যায়। সমাধান: একইWHEREআগেSELECTহিসেবে চালান, আর ট্রানজ্যাকশনের ভেতরে কাজ করুন।ORDER BY-তে টাই ভাঙার কলাম না দেওয়া। সমান মানের সারিগুলো যেকোনো ক্রমে আসতে পারে, তাই পেজিংয়ের পাতা আর "সেরা ৩" তালিকা এক রান থেকে আরেক রানে বদলে যায়। সমাধান: শেষে একটা ইউনিক কলাম দিন, যেমনORDER BY price DESC, product_id।
নিজে চেষ্টা করুন
সব মিলিয়ে একটু ঝালিয়ে নেওয়া। উত্তর দেখার আগে প্রতিটা নিজে চেষ্টা করুন।
- সহজ: যে প্রোডাক্টগুলোর দাম ১,০০০ থেকে ৩,০০০ টাকার মধ্যে আর স্টকে আছে, সেগুলো সবচেয়ে সস্তা থেকে দেখান।
- মাঝারি: প্রতিটা শহরের জন্য (শহর না থাকলে
Unknown) কাস্টমারের সংখ্যা আর তাঁদের দেওয়া অর্ডারের সংখ্যা দেখান — যে শহরে কোনো অর্ডার নেই, সেটাও। - কঠিন: বিক্রি হওয়া অর্ডারে কোন প্রোডাক্টগুলো একাধিক আলাদা কাস্টমার কিনেছেন? প্রোডাক্টের নাম আর কাস্টমারের সংখ্যা দেখান।
উত্তর
-- 1. একটা ফিল্টার, একটা পরিসর আর সাজানো
SELECT name, price, stock
FROM products
WHERE price BETWEEN 1000 AND 3000
AND stock > 0
ORDER BY price, product_id;
-- 2. LEFT JOIN, যাতে অর্ডারহীন কাস্টমারও গোনা হয়; COUNT(o.order_id) NULL বাদ দেয়
SELECT COALESCE(c.city, 'Unknown') AS city,
COUNT(DISTINCT c.customer_id) AS customers,
COUNT(o.order_id) AS orders
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.customer_id
GROUP BY COALESCE(c.city, 'Unknown')
ORDER BY orders DESC, city;
-- 3. জয়েন, ফিল্টার, গ্রুপ, তারপর গ্রুপ ছাঁকা
SELECT p.name, COUNT(DISTINCT o.customer_id) AS customers
FROM order_items AS oi
JOIN orders AS o ON o.order_id = oi.order_id
JOIN products AS p ON p.product_id = oi.product_id
WHERE o.status IN ('delivered', 'shipped')
GROUP BY p.product_id, p.name
HAVING COUNT(DISTINCT o.customer_id) > 1
ORDER BY customers DESC, p.product_id;এরপর কী: অ্যাডভান্সড টিউটোরিয়াল
AI-এর জন্য SQL ও ডেটা হ্যান্ডলিং: অ্যাডভান্সড ধরে নেয় যে এই পাতার সবকিছু আপনি জানেন, আর একই শপ ডেটাবেস নিয়েই এগোয়:
| অধ্যায় | যা যোগ হবে |
|---|---|
| অ্যাডভান্সড কোয়েরি | CTE আর রিকার্সিভ কোয়েরি, উইন্ডো ফাংশন (র্যাংকিং, রানিং টোটাল, মুভিং অ্যাভারেজ), গ্রোথ, কোহর্ট, রিটেনশন, গ্যাপ আর আইল্যান্ড |
| ডিজাইন ও ডেটার শুদ্ধতা | ER ডায়াগ্রাম আর নরমালাইজেশন, আসল স্কিমা ডিজাইন আর নিরাপদে মাইগ্রেশন, ট্রানজ্যাকশন, আইসোলেশন আর লকিং |
| পারফরম্যান্স ও প্রোডাকশন | ইনডেক্স, EXPLAIN আর কোয়েরি অপ্টিমাইজেশন, ভিউ, প্রসিডিওর আর ট্রিগার, নিরাপত্তা, SQL ইনজেকশন আর ব্যাকআপ |
| Python ও ডেটা হ্যান্ডলিং | DB-API বিস্তারিত, pandas আর SQLAlchemy, ডেটা পরিষ্কার, ইমপোর্ট-এক্সপোর্ট (CSV, JSON, Excel, Parquet) |
| পাইপলাইন ও AI | ETL পাইপলাইন, লিকেজ ছাড়া ML-এর উপযোগী ডেটাসেট, AI অ্যাপে SQL (LLM লগ, JSON, ফুল-টেক্সট সার্চ, pgvector আর RAG), ওয়্যারহাউস আর স্কেল |
| অনুশীলন ও ক্যাপস্টোন | ইন্টারভিউ ধাঁচের সমস্যা আর ক্যাপস্টোন প্রজেক্ট |
কীভাবে অনুশীলন চালিয়ে যাবেন
- শপকে নতুন প্রশ্ন করুন। কোন শহর সবচেয়ে বেশি কোর্স কেনে? কোন কাস্টমার সবচেয়ে বেশি দিন ধরে অর্ডার করেননি? আগে প্রশ্নটা কথায় লিখুন, তারপর কোয়েরি, তারপর কয়েকটা সারিতে হাতে হিসাব করে ফলাফল মিলিয়ে নিন।
- নিজের ডেটা ব্যবহার করুন। যে CSV নিয়ে আপনার আগ্রহ আছে (নিজের খরচের হিসাব, একটা Kaggle ডেটাসেট, আপনার অ্যাপের লগ), pandas-এর
to_sqlদিয়ে SQLite-এ তুলুন আর SQL দিয়ে ঘেঁটে দেখুন। - আসল সার্ভারে অনুশীলন করুন। আপনার কয়েকটা কোয়েরি MySQL বা PostgreSQL-এ চালান (Docker দিয়ে এক লাইনেই হয়) আর কী কী বদলায় লিখে রাখুন।
- প্রতিদিন একটু করে। SQLBolt, LeetCode-এর SQL সমস্যা, HackerRank আর DataLemur-এ ধাপে ধাপে কঠিন হওয়া অনুশীলনী আছে; মাসে একবার লম্বা সময়ের চেয়ে দিনে ১৫ মিনিট অনেক বেশি কাজের।
সারসংক্ষেপ
- কোয়েরি লেখা হয়
SELECT … FROM … WHERE … GROUP BY … HAVING … ORDER BY … LIMITক্রমে, কিন্তু চলে আগেFROM, আরSELECTপ্রায় শেষে। NULL-এর জন্য লাগেIS NULLআরCOALESCE; অ্যাগ্রিগেট একে বাদ দেয়;NOT INএতে হোঁচট খায়।- জয়েন সারি মেলায়;
LEFT JOINনা-মেলাগুলোও রাখে; one-to-many দিকের টেবিলগুলো জয়েনের আগে অ্যাগ্রিগেট করুন। - ডেটা বদলান এমন
WHEREদিয়ে, যা আগেSELECTহিসেবে পরীক্ষা করেছেন, আর ডেটা পাহারার কাজ কনস্ট্রেইন্টকে দিন। - মূল অংশ সব জায়গায় এক, কিন্তু টেক্সট জোড়া, তারিখ, আপসার্ট আর ভাগ ডায়ালেক্টভেদে আলাদা: যেখানে চালাবেন, সেখানেই পরীক্ষা করুন।
এরপর: AI-এর জন্য SQL ও ডেটা হ্যান্ডলিং: অ্যাডভান্সড। এর শুরু CTE আর রিকার্সিভ কোয়েরি দিয়ে, যা দিয়ে লম্বা কোয়েরির — যেমন প্রজেক্টের বিক্রির রিপোর্টের — প্রতিটা ধাপকে আলাদা নাম দেওয়া যায়, আর ক্যালেন্ডার বা অর্গ চার্টের মতো সারি তৈরি করা যায়।