অধ্যায় 4 · ফাংশন ও সারাংশ
টেক্সট, সংখ্যা ও তারিখের ফাংশন, CAST আর COALESCE
- পৃষ্ঠা 13 / 22
- 17 মিনিট পড়া
কাঁচা কলাম খুব কমই আপনার দরকারি আকারে থাকে। ইমেইলে থাকে বাড়তি স্পেস আর বড় হাতের অক্ষর, তারিখ রাখা থাকে টেক্সট হিসেবে, দাম দেখাতে হয় হাজারে, শহর না থাকলে সেখানে লেখা উচিত "Unknown"। SQL-এর ফাংশন এসব কোয়েরির ভেতরেই, সারি ধরে ধরে ঠিক করে দেয়, ডেটা pandas বা কোনো মডেলে পৌঁছানোর আগেই। বাস্তব ML প্রজেক্টের "ফিচার ইঞ্জিনিয়ারিং"-এর বড় অংশই ঠিক এটা: ইমেইলের ডোমেইন, রিভিউয়ের দৈর্ঘ্য, অর্ডারের মাস, কেউ কত দিন ধরে কাস্টমার।
SQL-এর ডায়ালেক্টগুলোর মধ্যে সবচেয়ে বেশি পার্থক্যও এই ফাংশনেই। এই পাতায় মূল উদাহরণগুলো SQLite-এ, আর পাশাপাশি MySQL ও PostgreSQL-এর রূপ, যেগুলো আসল সার্ভারে চালিয়ে দেখানো।
যা শিখবেন
- টেক্সট ফাংশন: জোড়া লাগানো,
LENGTH,UPPER/LOWER,SUBSTR,INSTR,TRIM,REPLACE, আর সার্ভারে এদের সমতুল্য। - সংখ্যার ফাংশন:
ROUND,ABS,CEIL/FLOOR, আর পূর্ণসংখ্যার ভাগের ফাঁদ। - তারিখের ফাংশন: SQLite-এ
date(),strftime()আরjulianday(); সার্ভারেEXTRACT,DATEDIFF,INTERVAL। CASTদিয়ে টাইপ বদল, আরCOALESCEওNULLIFদিয়ে NULL সামলানো (নিরাপদ ভাগ)।- Python-এ নিজের SQL ফাংশন লেখা।
ফাংশন কী
ফাংশন কিছু মান নেয় আর একটা মান ফেরত দেয়: UPPER('dhaka') ফেরত দেয় 'DHAKA'। কোনো কলামে ব্যবহার করলে এটা প্রতিটা সারির জন্য একবার করে চলে। এগুলো স্কেলার ফাংশন: এক সারি ঢোকে, একটা মান বেরোয়। যে ফাংশন অনেকগুলো সারি মিলিয়ে একটা মান বানায় (COUNT, SUM, AVG), সেগুলো অ্যাগ্রিগেট, পরের পাতার বিষয়। যেখানে একটা মান বসতে পারে, সেখানেই ফাংশন ব্যবহার করা যায়: SELECT, WHERE, ORDER BY-এ, এমনকি আরেকটা ফাংশনের ভেতরে।
টেক্সট ফাংশন
customers টেবিলে রোজকার ফাংশনগুলো দেখুন। SUBSTR(text, start, length) গোনা শুরু করে ১ থেকে, আর INSTR(text, part) বলে part কোন অবস্থানে শুরু হয়েছে (না থাকলে ০); এ দুটো মিলিয়ে কোনো অক্ষরের জায়গায় স্ট্রিং কাটা যায়:
SELECT name,
UPPER(name) AS upper_name,
LENGTH(name) AS len,
SUBSTR(name, 1, INSTR(name, ' ') - 1) AS first_name,
SUBSTR(email, INSTR(email, '@') + 1) AS domain
FROM customers
WHERE customer_id <= 3
ORDER BY customer_id;+---------------+---------------+-----+------------+-------------+
| name | upper_name | len | first_name | domain |
+---------------+---------------+-----+------------+-------------+
| Nadia Rahman | NADIA RAHMAN | 12 | Nadia | example.com |
| Tanvir Ahmed | TANVIR AHMED | 12 | Tanvir | example.com |
| Farhana Akter | FARHANA AKTER | 13 | Farhana | example.com |
+---------------+---------------+-----+------------+-------------+সবচেয়ে বেশি ব্যবহার এলোমেলো ইনপুট পরিষ্কারে। TRIM দুই প্রান্তের স্পেস সরায় (LTRIM/RTRIM এক প্রান্তের), LOWER টেক্সটকে তুলনাযোগ্য করে, REPLACE একটা স্ট্রিং যতবার আছে, প্রতিবার সেটাকে অন্য একটা দিয়ে বদলে দেয়:
SELECT '[' || TRIM(' Dhaka ') || ']' AS trimmed,
LOWER(TRIM(' Nadia@Example.COM ')) AS clean_email,
REPLACE(REPLACE('+880 1711-223344', ' ', ''), '-', '') AS phone;+---------+-------------------+----------------+
| trimmed | clean_email | phone |
+---------+-------------------+----------------+
| [Dhaka] | nadia@example.com | +8801711223344 |
+---------+-------------------+----------------+স্ট্রিং জোড়া, আর তাতে NULL কী করে
স্ট্যান্ডার্ড অপারেটর হলো ||। কোনো একটা অংশ NULL হলে পুরো ফলাফলই NULL। সাদিয়ার শহর নেই, তাই তাঁর লেবেলটাই উধাও; COALESCE (নিচে) এটা ঠিক করে:
SELECT name,
name || ' (' || city || ')' AS label,
name || ' (' || COALESCE(city, 'Unknown') || ')' AS label_fixed
FROM customers
WHERE customer_id IN (4, 5)
ORDER BY customer_id;+-----------------+----------------------+---------------------------+
| name | label | label_fixed |
+-----------------+----------------------+---------------------------+
| Rafiq Islam | Rafiq Islam (Sylhet) | Rafiq Islam (Sylhet) |
| Sadia Chowdhury | NULL | Sadia Chowdhury (Unknown) |
+-----------------+----------------------+---------------------------+CONCAT() SQLite (3.44+) আর PostgreSQL-এ NULL-কে খালি স্ট্রিং ধরে, কিন্তু MySQL-এ NULL ফেরত দেয়। আর MySQL-এ || জোড়া লাগানোই নয়: ডিফল্টভাবে এর মানে লজিক্যাল OR, তাই 'a' || 'b' হয় ০। MySQL-এ CONCAT ব্যবহার করুন। CONCAT_WS(separator, …) মাঝে একটা বিভাজক (separator) বসিয়ে জোড়ে, আর তিনটা ডেটাবেসেই NULL বাদ দিয়ে যায়:
-- MySQL
SELECT 'a' || 'b' AS pipes,
CONCAT('a', 'b') AS concat_ab,
CONCAT('a', NULL, 'b') AS concat_null,
CONCAT_WS('-', 'a', NULL, 'b') AS with_sep;+-------+-----------+-------------+----------+
| pipes | concat_ab | concat_null | with_sep |
+-------+-----------+-------------+----------+
| 0 | ab | NULL | a-b |
+-------+-----------+-------------+----------+শুধু সার্ভারে থাকা ফাংশন, আর অক্ষর বনাম বাইট
MySQL আর PostgreSQL-এ LEFT আর RIGHT আছে (SQLite-এ নেই; লিখুন SUBSTR(s, 1, n) আর SUBSTR(s, -n))। PostgreSQL অবস্থান খোঁজে INSTR-এর বদলে POSITION(… IN …) বা STRPOS দিয়ে, আর SPLIT_PART এক ধাপেই ভাগ করে:
-- PostgreSQL
SELECT LEFT(name, 5) AS left_5,
RIGHT(email, 11) AS right_11,
SUBSTRING(name FROM 1 FOR 3) AS first_3,
POSITION('@' IN email) AS at_pos,
SPLIT_PART(email, '@', 2) AS domain
FROM customers
WHERE customer_id = 1;+--------+-------------+---------+--------+-------------+
| left_5 | right_11 | first_3 | at_pos | domain |
+--------+-------------+---------+--------+-------------+
| Nadia | example.com | Nad | 6 | example.com |
+--------+-------------+---------+--------+-------------+বাংলায় দৈর্ঘ্য মাপা একটু সূক্ষ্ম ব্যাপার। UTF-8-এ প্রতিটা বাংলা ক্যারেক্টার ৩ বাইট জায়গা নেয়। SQLite আর PostgreSQL-এর LENGTH অক্ষর গোনে, কিন্তু MySQL-এর LENGTH গোনে বাইট; সেখানে CHAR_LENGTH ব্যবহার করুন:
-- MySQL
SELECT LENGTH('ঢাকা') AS length_bytes, CHAR_LENGTH('ঢাকা') AS chars;+--------------+-------+
| length_bytes | chars |
+--------------+-------+
| 12 | 4 |
+--------------+-------+এখানে "অক্ষর" মানে একটা ইউনিকোড ক্যারেক্টার (code point), তাই া-এর মতো কার-চিহ্নও আলাদা করে গোনা হয়: ঢাকা ৪ ক্যারেক্টার, ২টা বর্ণ নয়। কলামের মাপে বা LLM প্রম্পটের সীমার মধ্যে আনতে টেক্সট কাটার সময় এটা জরুরি: বাইটের হিসাবে কাটলে একটা ক্যারেক্টার মাঝখান থেকে ভেঙে যেতে পারে, আর ক্যারেক্টার ধরে কাটলেও ব্যঞ্জন থেকে তার কার-চিহ্ন আলাদা হয়ে যেতে পারে।
সংখ্যার ফাংশন আর পূর্ণসংখ্যার ভাগের ফাঁদ
ROUND(x, digits), ABS(x) আর % (ভাগশেষ) সব জায়গায় চলে। ফাঁদটা ভাগে: দুই দিকই পূর্ণসংখ্যা হলে SQLite, PostgreSQL আর SQL Server পূর্ণসংখ্যার ভাগ করে, ভগ্নাংশটা ফেলে দেয়। দাম হাজার টাকায় দেখাতে গেলে:
SELECT name, price,
price / 1000 AS k_wrong,
ROUND(price / 1000.0, 1) AS k_taka
FROM products
WHERE product_id IN (1, 4, 5)
ORDER BY product_id;+---------------------+-------+---------+--------+
| name | price | k_wrong | k_taka |
+---------------------+-------+---------+--------+
| Python Crash Course | 1200 | 1 | 1.2 |
| Wireless Mouse | 900 | 0 | 0.9 |
| 27-inch Monitor | 18500 | 18 | 18.5 |
+---------------------+-------+---------+--------+মাউসের দাম দাঁড়াল "০ হাজার"। একটা দিককে দশমিক বানান (1000.0, price * 1.0 বা CAST(price AS REAL)), তাহলে ভাগে ভগ্নাংশ থাকবে। MySQL এখানে ব্যতিক্রম: / সবসময় দশমিক দেয়, আর DIV হলো তার পূর্ণসংখ্যার ভাগ। CEIL আর FLOOR ওপরে আর নিচে রাউন্ড করে; প্রতিটা সার্ভারে আছে, কিন্তু SQLite-এ থাকে কেবল ম্যাথ ফাংশনসহ কম্পাইল করা হলে, তাই সেখানে এদের ওপর ভরসা করবেন না:
-- MySQL
SELECT 7 / 2 AS divide, 7 DIV 2 AS int_divide, 7 % 2 AS remainder,
CEIL(2.1) AS ceil_, FLOOR(-2.1) AS floor_, ROUND(AVG(price), 2) AS avg_price
FROM products;+--------+------------+-----------+-------+--------+-----------+
| divide | int_divide | remainder | ceil_ | floor_ | avg_price |
+--------+------------+-----------+-------+--------+-----------+
| 3.5000 | 3 | 1 | 3 | -3 | 5254.55 |
+--------+------------+-----------+-------+--------+-----------+-- PostgreSQL
SELECT 7 / 2 AS divide, 7 / 2.0 AS decimal_divide, 7 % 2 AS remainder,
CEIL(2.1) AS ceil_, FLOOR(-2.1) AS floor_, ROUND(AVG(price), 2) AS avg_price
FROM products;+--------+--------------------+-----------+-------+--------+-----------+
| divide | decimal_divide | remainder | ceil_ | floor_ | avg_price |
+--------+--------------------+-----------+-------+--------+-----------+
| 3 | 3.5000000000000000 | 1 | 3 | -3 | 5254.55 |
+--------+--------------------+-----------+-------+--------+-----------+PostgreSQL-এ আরও একটা ফাঁদ: ROUND(x, 2) NUMERIC-এ চলে, কিন্তু ফ্লোটিং-পয়েন্ট মানে নয়। আগে কাস্ট করুন, ROUND(x::numeric, 2):
-- PostgreSQL
SELECT ROUND(CAST(2.345 AS DOUBLE PRECISION), 2);Error: function round(double precision, integer) does not existতারিখের ফাংশন
SQLite-এ আসল কোনো তারিখ টাইপ নেই: তারিখ হলো ISO ফরম্যাটের টেক্সট 'YYYY-MM-DD' (দেখুন ডেটা টাইপ, NULL আর ঠিক টাইপ বাছাই), আর ফাংশনগুলো সেই টেক্সটকে তারিখ হিসেবে পড়ে নেয়। date(value, modifier, …) তারিখের হিসাব করে, strftime(format, value) অংশ আলাদা করে (%Y বছর, %m মাস, %d দিন, %w সপ্তাহের দিন, ০ = রবিবার):
SELECT order_id, order_date,
strftime('%Y-%m', order_date) AS month,
strftime('%w', order_date) AS weekday,
date(order_date, '+7 days') AS due_date,
date(order_date, 'start of month') AS month_start
FROM orders
WHERE order_id IN (1, 7, 13)
ORDER BY order_id;+----------+------------+---------+---------+------------+-------------+
| order_id | order_date | month | weekday | due_date | month_start |
+----------+------------+---------+---------+------------+-------------+
| 1 | 2026-01-12 | 2026-01 | 1 | 2026-01-19 | 2026-01-01 |
| 7 | 2026-03-15 | 2026-03 | 0 | 2026-03-22 | 2026-03-01 |
| 13 | 2026-06-02 | 2026-06 | 2 | 2026-06-09 | 2026-06-01 |
+----------+------------+---------+---------+------------+-------------+দুই তারিখের মধ্যে কত দিন, তা বের করতে তাদের julianday() মান (একটা দিনের নম্বর) বিয়োগ করুন। এটা একটা ক্লাসিক ML ফিচার, কাস্টমারের টেনিওর (tenure)। নির্দিষ্ট "as of" তারিখটা খেয়াল করুন: date('now') আছে ঠিকই, কিন্তু যে ফিচার প্রতিবার কোয়েরি চালালে বদলে যায়, পরে সেটা হুবহু আর পাওয়া যায় না, আর ট্রেনিং ডেটায় ভবিষ্যতের তথ্যও ঢুকে পড়তে পারে। তাই ফিচার সবসময় নির্দিষ্ট একটা তারিখ ধরে হিসাব করুন:
SELECT customer_id, joined_on,
CAST(julianday('2026-06-30') - julianday(joined_on) AS INTEGER) AS tenure_days
FROM customers
ORDER BY tenure_days DESC, customer_id
LIMIT 4;+-------------+------------+-------------+
| customer_id | joined_on | tenure_days |
+-------------+------------+-------------+
| 1 | 2025-11-03 | 239 |
| 2 | 2025-12-14 | 198 |
| 3 | 2026-01-09 | 172 |
| 4 | 2026-01-22 | 159 |
+-------------+------------+-------------+সার্ভারে তারিখ আসল টাইপ, নিজস্ব ফাংশনসহ। মাসের হিসাবটা খেয়াল করুন: ৩১ জানুয়ারির এক মাস পর MySQL আর PostgreSQL-এ ২৮ ফেব্রুয়ারি, কিন্তু SQLite-এ বাড়তি দিনগুলো গড়িয়ে মার্চে চলে যায়:
SELECT date('2026-01-31', '+1 month') AS sqlite_plus_month;+-------------------+
| sqlite_plus_month |
+-------------------+
| 2026-03-03 |
+-------------------+-- MySQL
SELECT DATEDIFF('2026-06-30', '2026-01-12') AS days_between,
DATE_ADD('2026-01-31', INTERVAL 1 MONTH) AS plus_month,
'2026-06-02' + INTERVAL 7 DAY AS plus_week,
EXTRACT(MONTH FROM '2026-06-02') AS month_no,
DATE_FORMAT('2026-06-02', '%Y-%m') AS ym;+--------------+------------+------------+----------+---------+
| days_between | plus_month | plus_week | month_no | ym |
+--------------+------------+------------+----------+---------+
| 169 | 2026-02-28 | 2026-06-09 | 6 | 2026-06 |
+--------------+------------+------------+----------+---------+-- PostgreSQL
SELECT DATE '2026-06-30' - DATE '2026-01-12' AS days_between,
DATE '2026-01-31' + INTERVAL '1 month' AS plus_month,
DATE '2026-06-02' + 7 AS plus_week,
EXTRACT(MONTH FROM DATE '2026-06-02') AS month_no,
TO_CHAR(DATE '2026-06-02', 'YYYY-MM') AS ym;+--------------+---------------------+------------+----------+---------+
| days_between | plus_month | plus_week | month_no | ym |
+--------------+---------------------+------------+----------+---------+
| 169 | 2026-02-28 00:00:00 | 2026-06-09 | 6 | 2026-06 |
+--------------+---------------------+------------+----------+---------+PostgreSQL-এ তারিখ থেকে তারিখ বিয়োগ করলে পাওয়া যায় দিনের পূর্ণসংখ্যা, তারিখে পূর্ণসংখ্যা যোগ করলে তারিখ, আর তারিখে INTERVAL যোগ করলে টাইমস্ট্যাম্প। DATE_PART('month', d) হলো EXTRACT-এর PostgreSQL-এর পুরোনো রূপ।
CAST: মানের টাইপ বদলানো
CSV ফাইল বা API থেকে লোড করা ডেটা প্রায়ই টেক্সট হিসেবে আসে। CAST(value AS type) একে রূপান্তর করে; PostgreSQL-এ ছোট রূপ value::type-ও আছে, MySQL আর SQL Server-এ আছে CONVERT। SQLite-এর typeof() দেখায় কী পেলেন। ভুল ইনপুট নিয়ে ডায়ালেক্টগুলোর মত একেবারে আলাদা:
SELECT CAST('42' AS INTEGER) + 1 AS ok,
CAST('12abc' AS INTEGER) AS partly_number,
CAST('abc' AS INTEGER) AS not_number,
CAST(3.99 AS INTEGER) AS truncated,
typeof(CAST(1200 AS REAL)) AS type_after;+----+---------------+------------+-----------+------------+
| ok | partly_number | not_number | truncated | type_after |
+----+---------------+------------+-----------+------------+
| 43 | 12 | 0 | 3 | real |
+----+---------------+------------+-----------+------------+-- PostgreSQL
SELECT CAST('12abc' AS INTEGER);Error: invalid input syntax for type integer: "12abc"SQLite (আর MySQL, শুধু একটা সতর্কবার্তা দিয়ে) চুপচাপ '12abc'-কে ১২ আর 'abc'-কে ০ বানিয়ে দেয়। PostgreSQL সোজা এরর দেয়। যে ডেটায় মডেল ট্রেন করবেন, সেখানে স্পষ্ট এররই ভালো: চুপচাপ বসে যাওয়া একটা ০ হয়ে যায় নকল ডেটা পয়েন্ট। আরও খেয়াল করুন, CAST(3.99 AS INTEGER) কেটে ৩ বানায়; ৪ চাইলে ROUND ব্যবহার করুন।
COALESCE আর NULLIF
COALESCE(a, b, c, …) প্রথম যে মানটা NULL নয়, সেটা ফেরত দেয়। অনুপস্থিত ডেটার জন্য ডিফল্ট দেওয়ার উপায় এটাই (MySQL-এর IFNULL আর SQL Server-এর ISNULL এর দুই-আর্গুমেন্টের রূপ)। ভেবেচিন্তে ব্যবহার করুন: COALESCE(city, 'Unknown') সৎ, কিন্তু COALESCE(stock, 0) দাবি করবে কোর্সগুলো শেষ হয়ে গেছে, অথচ সেখানে NULL-এর আসল মানে "স্টকের হিসাব রাখা হয় না"।
NULLIF(a, b) উল্টো কাজ করে: a আর b সমান হলে NULL দেয়, নইলে a। এর মূল কাজ নিরাপদ ভাগ। ধরুন আপনি মডেলের ইভ্যালুয়েশন লগ করছেন, আর একটা মডেল এখনো কোনো প্রশ্নেই চালানো হয়নি:
-- PostgreSQL
CREATE TABLE eval_runs (model TEXT PRIMARY KEY, correct INTEGER, attempted INTEGER);
INSERT INTO eval_runs VALUES ('small-v1', 41, 50), ('large-v2', 47, 50), ('new-v3', 0, 0);
SELECT model, correct * 100.0 / attempted AS accuracy_pct FROM eval_runs;Error: division by zero-- PostgreSQL
SELECT model,
ROUND(correct * 100.0 / NULLIF(attempted, 0), 1) AS accuracy_pct
FROM eval_runs
ORDER BY model;+----------+--------------+
| model | accuracy_pct |
+----------+--------------+
| large-v2 | 94.0 |
| new-v3 | NULL |
| small-v1 | 82.0 |
+----------+--------------+প্রথম শূন্যেই PostgreSQL পুরো কোয়েরি থামিয়ে দেয়। NULLIF থাকলে শূন্য হর হয়ে যায় NULL, আর NULL দিয়ে কিছু ভাগ করলে ফল NULL: "এখনো কোনো অ্যাকুরেসি নেই", যা সত্যি কথা। SQLite আর MySQL না চাইতেই শূন্য দিয়ে ভাগে NULL দেয়, তাই সেখানে সমস্যাটা লুকিয়ে থাকে; তবু NULLIF লিখুন, যাতে কোয়েরি সব জায়গায় চলে আর উদ্দেশ্যটা চোখে পড়ে।
Python থেকে নিজের ফাংশন
যা দরকার তার জন্য SQL-এ কোনো ফাংশন না থাকলে, Python-এর sqlite3 দিয়ে একটা রেজিস্টার করে নিতে পারেন। নিচে একটা শব্দ গোনার ফাংশন; রিভিউ বা প্রম্পট থেকে এমন ছোটখাটো টেক্সট ফিচার প্রায়ই বের করতে হয়:
from sqlhelp import con, show
def word_count(text):
return None if text is None else len(text.split())
con.create_function("word_count", 1, word_count, deterministic=True)
show("""
SELECT name, word_count(name) AS words, LENGTH(name) AS chars
FROM products
WHERE category_id = 1 OR product_id = 5
ORDER BY product_id
""")+---------------------------+-------+-------+
| name | words | chars |
+---------------------------+-------+-------+
| Python Crash Course | 3 | 19 |
| Hands-On Machine Learning | 3 | 25 |
| 27-inch Monitor | 2 | 15 |
+---------------------------+-------+-------+ফাংশনটা থাকে শুধু ওই Python কানেকশনে: DB Browser বা sqlite3 শেল একে চিনবে না। deterministic=True SQLite-কে জানিয়ে দেয় যে একই ইনপুটে সবসময় একই আউটপুট আসবে, ফলে SQLite একে আরও বেশি জায়গায় (যেমন ইনডেক্সে) ব্যবহার করতে পারে। MySQL আর PostgreSQL-এ ডেটাবেসের ভেতরেই ফাংশন রাখার জন্য আছে CREATE FUNCTION (অ্যাডভান্সড টিউটোরিয়ালে দেখবেন)।
ডায়ালেক্টের চিট শিট
| কাজ | SQLite | MySQL | PostgreSQL |
|---|---|---|---|
| টেক্সট জোড়া | a || b, concat() (3.44+) | CONCAT(a, b) | a || b, CONCAT(a, b) |
| অক্ষরে দৈর্ঘ্য | LENGTH | CHAR_LENGTH | LENGTH |
| অবস্থান খোঁজা | INSTR(s, x) | INSTR(s, x), LOCATE(x, s) | POSITION(x IN s), STRPOS(s, x) |
| প্রথম n অক্ষর | SUBSTR(s, 1, n) | LEFT(s, n) | LEFT(s, n) |
7 / 2 | 3 | 3.5000 | 3 |
| ৭ দিন যোগ | date(d, '+7 days') | d + INTERVAL 7 DAY | d + 7, d + INTERVAL '7 days' |
| দুই তারিখের মাঝে কত দিন | julianday(a) - julianday(b) | DATEDIFF(a, b) | a - b |
| বছর-মাস টেক্সট | strftime('%Y-%m', d) | DATE_FORMAT(d, '%Y-%m') | TO_CHAR(d, 'YYYY-MM') |
| সংখ্যা নয় এমন টেক্সট | চুপচাপ ০ বা শুরুর অংশ | শুরুর অংশ, সতর্কবার্তাসহ | এরর |
বাস্তব সিস্টেমে কোথায় দেখবেন
- ফিচার বের করা: ইমেইল ডোমেইন, টেক্সটের দৈর্ঘ্য, টেনিওর (দিনে), অর্ডারের মাস আর বার SQL-এই হিসাব করা হয়, যাতে প্রতিটা মডেল আর ড্যাশবোর্ড একই সংজ্ঞা ব্যবহার করে।
- জয়েন কি: দুই সিস্টেমের কাস্টমার তালিকা মেলানোর আগে দুই দিকেই
LOWER(TRIM(email))। - AI অ্যাপ নজরদারি: LLM কলগুলো দিন ধরে ভাগ করতে
strftime('%Y-%m-%d', created_at), আর যেদিন কোনো ট্রাফিক নেই সেদিনের এরর রেট হিসাবেNULLIF।
সাধারণ ভুল
- পূর্ণসংখ্যার ভাগ।
price / 1000মাউসের জন্য ০ দেয়। সমাধান:price / 1000.0বাCAST(price AS REAL) / 1000। - একটা NULL-এ পুরো জোড়া লাগানো টেক্সটই NULL। সাদিয়ার জন্য
name || ' (' || city || ')'হয় NULL। সমাধান: ভেতরেCOALESCE(city, 'Unknown'), বাCONCAT_WS। - MySQL-এ
||বাLENGTH।||মানে OR, আরLENGTHবাইট গোনে। সমাধান:CONCAT(a, b)আরCHAR_LENGTH(s)। - ফিচারে
date('now')ব্যবহার। ফল প্রতিদিন বদলায়, পরে একই ফল আর পাওয়া যায় না। সমাধান: নির্দিষ্ট as-of তারিখ, যেমনjulianday('2026-06-30') - julianday(joined_on)। - চুপচাপ CAST-এর ওপর ভরসা। SQLite-এ
CAST('abc' AS INTEGER)হলো ০। সমাধান: রূপান্তরের আগে টেক্সট যাচাই করুন, যেমনWHERE value GLOB '[0-9]*' AND value NOT GLOB '*[^0-9]*', অথবা PostgreSQL-এ লোড করুন, যা ভুল মান ফিরিয়ে দেয়।
নিজে চেষ্টা করুন
- সহজ: প্রতিটা কাস্টমারের নাম বড় হাতের অক্ষরে আর ইমেইলের
@-এর আগের অংশ দেখান।customer_idঅনুযায়ী সাজান। - মাঝারি: প্রতিটা প্রোডাক্টের দাম ১৫% ভ্যাটসহ, পূর্ণ টাকায় রাউন্ড করে, আর হাজারে এক দশমিক ঘর পর্যন্ত দাম দেখান। শুধু ৩০০০ টাকার কম প্রোডাক্ট, সস্তাগুলো আগে (সমান হলে id অনুযায়ী)।
- কঠিন: ২০২৬-০৬-৩০ তারিখ ধরে প্রতি কাস্টমারের একটা ফিচার সারি বানান:
customer_id,first_name,email_domain,city(বা'Unknown'),joined_month(YYYY-MM) আরtenure_days। তারপরmask_emailনামে একটা Python ফাংশন রেজিস্টার করুন, যাnadia@example.com-কে বানায়n***@example.com(ডেটা ডেটাবেসের বাইরে যাওয়ার আগে গোপনীয়তা জরুরি), আর ১ থেকে ৩ নম্বর কাস্টমারের জন্য দেখান।
উত্তর
-- সহজ
SELECT customer_id, UPPER(name) AS name, SUBSTR(email, 1, INSTR(email, '@') - 1) AS user
FROM customers
ORDER BY customer_id;+-------------+-----------------+---------+
| customer_id | name | user |
+-------------+-----------------+---------+
| 1 | NADIA RAHMAN | nadia |
| 2 | TANVIR AHMED | tanvir |
| 3 | FARHANA AKTER | farhana |
| 4 | RAFIQ ISLAM | rafiq |
| 5 | SADIA CHOWDHURY | sadia |
| 6 | IMRAN HOSSAIN | imran |
| 7 | MITU DAS | mitu |
| 8 | KARIM UDDIN | karim |
+-------------+-----------------+---------+-- মাঝারি
SELECT product_id, name, price,
CAST(ROUND(price * 1.15) AS INTEGER) AS price_with_vat,
ROUND(price / 1000.0, 1) AS price_k
FROM products
WHERE price < 3000
ORDER BY price, product_id;+------------+---------------------------+-------+----------------+---------+
| product_id | name | price | price_with_vat | price_k |
+------------+---------------------------+-------+----------------+---------+
| 4 | Wireless Mouse | 900 | 1035 | 0.9 |
| 11 | Gift Card | 1000 | 1150 | 1.0 |
| 1 | Python Crash Course | 1200 | 1380 | 1.2 |
| 8 | Laptop Stand | 1500 | 1725 | 1.5 |
| 9 | USB-C Hub | 2200 | 2530 | 2.2 |
| 2 | Hands-On Machine Learning | 2500 | 2875 | 2.5 |
+------------+---------------------------+-------+----------------+---------+-- কঠিন, অংশ ১: ফিচার সারি
SELECT customer_id,
SUBSTR(name, 1, INSTR(name, ' ') - 1) AS first_name,
SUBSTR(email, INSTR(email, '@') + 1) AS email_domain,
COALESCE(city, 'Unknown') AS city,
strftime('%Y-%m', joined_on) AS joined_month,
CAST(julianday('2026-06-30') - julianday(joined_on) AS INTEGER) AS tenure_days
FROM customers
ORDER BY customer_id;+-------------+------------+--------------+------------+--------------+-------------+
| customer_id | first_name | email_domain | city | joined_month | tenure_days |
+-------------+------------+--------------+------------+--------------+-------------+
| 1 | Nadia | example.com | Dhaka | 2025-11 | 239 |
| 2 | Tanvir | example.com | Chattogram | 2025-12 | 198 |
| 3 | Farhana | example.com | Dhaka | 2026-01 | 172 |
| 4 | Rafiq | example.com | Sylhet | 2026-01 | 159 |
| 5 | Sadia | example.com | Unknown | 2026-02 | 145 |
| 6 | Imran | example.com | Khulna | 2026-02 | 132 |
| 7 | Mitu | example.com | Dhaka | 2026-03 | 92 |
| 8 | Karim | example.com | Rajshahi | 2026-04 | 80 |
+-------------+------------+--------------+------------+--------------+-------------+# কঠিন, অংশ ২: SQL থেকে ব্যবহার করা একটা Python ফাংশন
from sqlhelp import con, show
def mask_email(email):
if email is None or "@" not in email:
return email
user, domain = email.split("@", 1)
return user[0] + "***@" + domain
con.create_function("mask_email", 1, mask_email, deterministic=True)
show("SELECT customer_id, mask_email(email) AS email FROM customers WHERE customer_id <= 3 ORDER BY customer_id")+-------------+------------------+
| customer_id | email |
+-------------+------------------+
| 1 | n***@example.com |
| 2 | t***@example.com |
| 3 | f***@example.com |
+-------------+------------------+সারসংক্ষেপ
- স্কেলার ফাংশন প্রতিটা সারির মান থেকে নতুন একটা মান বানায়;
SELECT,WHEREআরORDER BY-এ ব্যবহার করুন। - টেক্সট:
||/CONCAT,LENGTH,UPPER/LOWER,SUBSTR,INSTR,TRIM,REPLACE; MySQL-এ লাগেCONCATআরCHAR_LENGTH। - সংখ্যা:
ROUND,ABS,%; MySQL ছাড়া পূর্ণসংখ্যা ÷ পূর্ণসংখ্যা ভগ্নাংশ ফেলে দেয়।CEIL/FLOORসার্ভারে। - তারিখ: SQLite-এ
date(),strftime(),julianday(); MySQL-এDATEDIFF/INTERVAL/DATE_FORMAT; PostgreSQL-এ তারিখ বিয়োগ,INTERVAL,EXTRACT,TO_CHAR। ফিচারের জন্য নির্দিষ্ট as-of তারিখ ব্যবহার করুন। CASTটাইপ বদলায় (SQLite উদার, PostgreSQL কড়া);COALESCENULL পূরণ করে;NULLIF(x, 0)ভাগকে নিরাপদ করে; Python দিয়ে নিজস্ব ফাংশন যোগ করা যায়।
এরপর: অ্যাগ্রিগেট, GROUP BY ও HAVING। এতক্ষণ প্রতিটা ফাংশন একবারে একটা সারিতে কাজ করেছে। এবার অনেকগুলো সারি মিলিয়ে মোট, গড় আর গণনা বের করবেন, প্রতি কাস্টমার, প্রতি প্রোডাক্ট আর প্রতি স্ট্যাটাস ধরে; কাঁচা অর্ডার এভাবেই আয়ের হিসাবে পরিণত হয়।