এই পর্বে যা শিখবেন
- খারাপ ডিজাইনের insert, update আর delete anomaly চিনতে, আর তার মূল কারণ functional dependency-তে খুঁজে পেতে
- X → Y-এর নির্ভুল সংজ্ঞা, trivial বনাম non-trivial FD, আর SQL দিয়ে ডেটায় FD-র লঙ্ঘন খুঁজতে
- Armstrong-এর তিনটি axiom আর union, decomposition, pseudo-transitivity — প্রমাণসহ
- Attribute closure X⁺ হাতে ও কোডে বের করতে, আর তা দিয়ে সব candidate key, prime ও non-prime attribute খুঁজতে
- দুটি FD-সেটের সমতা যাচাই আর canonical (minimal) cover বের করতে — পরীক্ষার ধাঁচের সমস্যায়
১ · খারাপ টেবিলের তিনটি রোগ — insert, update আর delete anomaly
একটি বিশ্ববিদ্যালয়ের পুরোনো অফিসের কথা ভাবুন। সেখানে একটিই মোটা রেজিস্টার খাতা। প্রতিটি লাইনে লেখা: শিক্ষার্থীর রোল, নাম, কোর্স কোড, কোর্সের নাম, শিক্ষকের নাম, শিক্ষকের বিভাগ আর গ্রেড। খাতাটি দেখতে সুবিধাজনক — এক লাইনেই সব। কিন্তু কয়েক মাস চালালেই সমস্যা শুরু হয়।
“Database Systems” কোর্সে ৬০ জন থাকলে কোর্সের নাম আর শিক্ষকের নাম ৬০ বার লেখা হয়। একে বলে data redundancy. শুধু কালি আর জায়গার অপচয় নয়, এটি তিনটি গুরুতর রোগের জন্ম দেয়। ডেটাবেস তত্ত্বে এদের বলে anomaly — অস্বাভাবিক আচরণ। নিচের ডায়াগ্রামে একটি ছোট নমুনায় তিনটিই দেখুন।
| যা দেখছি | |
| ফল |
- এক টেবিলে সব। প্রতিটি সারি একটি ভর্তি: কোন শিক্ষার্থী (sid, name) কোন কোর্সে (course, title) আর কে পড়ান (teacher). Key হলো (sid, course) — একজন একটি কোর্সে একবার।
- Redundancy. “CSE301 │ Database Systems │ Tasnim” দুবার লেখা, কারণ দুজন শিক্ষার্থী কোর্সটি নিয়েছেন। ৬০ জন নিলে ৬০ বার। একটি তথ্য (CSE301-এর নাম আর শিক্ষক) অনেক জায়গায় ছড়িয়ে আছে।
- Update anomaly. কোর্সের নাম বদলে “Database Mgmt.” করা হলো, কিন্তু শুধু Arif-এর সারিতে। Bristy-র সারিতে পুরোনো নাম রয়ে গেল। এখন CSE301-এর নাম জিজ্ঞেস করলে দুটি উত্তর — ডেটাবেস নিজের সাথেই অসংগত।
- Insert anomaly. নতুন কোর্স CSE401 খোলা হয়েছে, শিক্ষক Shahana, কিন্তু এখনো কেউ ভর্তি হননি। কোর্সটি ঢোকাতে গেলে sid ফাঁকা (NULL) রাখতে হয়, অথচ sid key-র অংশ। তাই কোর্সের তথ্যই রাখা যায় না।
- Delete anomaly. Dilruba CSE101 থেকে নাম তুলে নিলেন, তাঁর সারিটি মুছলাম। সেটিই ছিল CSE101-এর একমাত্র সারি। ফলে “CSE101-এর নাম Structured Programming, পড়ান Shahana” — এই তথ্যও মুছে গেল।
- মূল কারণ: functional dependency. course জানলেই title আর teacher জানা যায় — এই নির্ভরতা key (sid, course)-এর একটি অংশের ওপর। এমন নির্ভরতা টেবিল ভেঙে আলাদা করলে রোগ সারে। কীভাবে ঠিকভাবে ভাঙতে হয়, তা পর্ব ৩৬ আর ৩৭-এর বিষয়; এই পর্বে শিখব নির্ভরতাগুলো নিখুঁতভাবে চেনা আর হিসাব করা।
এবার তিনটি রোগ — update anomaly, delete anomaly আর insert anomaly — আসল SQL-এ দেখুন। নিচের এডিটরগুলো একটি বিশেষ টেবিল enrol_wide নিজে বানিয়ে নেয়। এর কলাম: student_id, student_name, course_id, course_title, instructor_id, instructor_name, inst_dept (শিক্ষকের বিভাগ) আর grade. Primary key (student_id, course_id). এতে ৬টি সারি — Arif আর Bristy দুজনেই CSE201 ও CSE301-এ, Chayon EEE101-এ, Dilruba CSE101-এ। সরলতার জন্য term কলাম রাখা হয়নি। প্রতিবার চালালে টেবিল নতুন করে তৈরি হয়, তাই নির্ভয়ে বদলান।
-- Update anomaly: কোর্সের নাম বদলালাম, কিন্তু একটি সারিতে
UPDATE enrol_wide SET course_title = 'Database Management'
WHERE student_id = 2101 AND course_id = 'CSE301';
SELECT student_id, course_id, course_title
FROM enrol_wide WHERE course_id = 'CSE301';UPDATE-এর পর “১টি সারি বদলেছে”, তারপর দুটি সারি: 2101 | CSE301 | Database Management আর 2102 | CSE301 | Database Systems. DBMS কোনো ত্রুটি দেয়নি — তার চোখে দুটি আলাদা সারির দুটি আলাদা ঘর বদলানো স্বাভাবিক। কিন্তু বাস্তবে একটি কোর্সের নাম একটিই। “একই course_id মানে একই course_title” — এই নিয়ম টেবিলের কোনো constraint-এ লেখা নেই, তাই ভাঙলেও কেউ আটকায় না। ঠিকভাবে বদলাতে হলে WHERE course_id = 'CSE301' দিয়ে সব সারি একসাথে বদলাতে হতো, আর প্রতিটি অ্যাপ-লেখককে সেটি মনে রাখতে হতো।
-- Delete anomaly: Dilruba CSE101 ছেড়ে দিলেন
DELETE FROM enrol_wide WHERE student_id = 2201;
-- CSE101 কোর্সটির নাম আর শিক্ষক এখন কোথায়?
SELECT course_id, course_title, instructor_name
FROM enrol_wide WHERE course_id = 'CSE101';
SELECT COUNT(*) AS rows_left FROM enrol_wide;DELETE “১টি সারি বদলেছে”। দ্বিতীয় কোয়েরি “(কোনো সারি নেই)” — CSE101 যে একটি কোর্স, তার নাম Structured Programming, আর Shahana Parvin সেটি পড়ান, এই তিনটি তথ্যই ডেটাবেস থেকে উধাও। তৃতীয় কোয়েরিতে 5. আমরা মুছতে চেয়েছিলাম শুধু একটি ভর্তি, কিন্তু সাথে মুছে গেল একটি কোর্স আর একজন শিক্ষকের খবর। কারণ তিনটি আলাদা বিষয়ের তথ্য একটি সারিতে বাঁধা।
-- নতুন ভর্তি: শিক্ষার্থী আছে বলে ঢোকে
INSERT INTO enrol_wide VALUES (2102, 'Bristy Saha', 'CSE302',
'Database Systems Lab', 6, 'Tasnim Chowdhury', 'CSE', NULL);
-- Insert anomaly: নতুন কোর্স, এখনো কোনো শিক্ষার্থী নেই
INSERT INTO enrol_wide (student_id, course_id, course_title,
instructor_id, instructor_name, inst_dept)
VALUES (NULL, 'CSE401', 'Machine Learning', 2, 'Shahana Parvin', 'CSE');প্রথম INSERT “১টি সারি বদলেছে”। দ্বিতীয়টিতে ত্রুটি: NOT NULL constraint failed: enrol_wide.student_id. Key-র অংশ ফাঁকা রাখা যায় না (entity integrity, পর্ব ৬), তাই শিক্ষার্থী ভর্তি না হওয়া পর্যন্ত CSE401 কোর্সটির অস্তিত্বই লেখা যাবে না। কেউ কেউ এখানে একটি “ভুয়া শিক্ষার্থী” (যেমন 0000) বানিয়ে কোর্স ঢোকান — সেটি আরও বড় ভুল, কারণ তখন প্রতিটি গণনায় ভুয়া সারি বাদ দেওয়ার কথা মনে রাখতে হয়। (SQLite-এর একটি অদ্ভুত দিক: এক-কলামের INTEGER PRIMARY KEY ছাড়া অন্য সব primary key-তে — যেমন এই দুই-কলামের key-তে — নিজে NOT NULL না লিখলে NULL ঢুকে যেত। তাই এখানে কলামে NOT NULL লেখা।)
তিনটি রোগেরই মূল এক: টেবিলে এমন একটি নির্ভরতা আছে — “course_id জানলে course_title জানা যায়” — যার বাম দিক পুরো key নয়। এই নির্ভরতাগুলোকে গাণিতিকভাবে লেখার ভাষাই functional dependency (সংক্ষেপে FD).
২ · Functional dependency — সংজ্ঞা আর ডেটা দিয়ে যাচাই
ধরুন R একটি relation schema, আর X, Y ⊆ R দুটি attribute-সেট। R-এর একটি instance r X → Y মেনে চলে, যদি r-এর যেকোনো দুটি tuple t₁, t₂-এর জন্য:
t₁[X] = t₂[X] হলে t₁[Y] = t₂[Y]
আর schema R-এ X → Y একটি FD, যদি R-এর প্রতিটি বৈধ instance এটি মেনে চলে। X-কে বলে determinant (বাম দিক), Y-কে dependent (ডান দিক)। পড়া হয় “X functionally determines Y” বা “Y, X-এর ওপর নির্ভরশীল”।
সহজ ভাষায়: X-এর মান জানলে Y-এর মান একটিই হতে পারে। NID নম্বর জানলে জন্মতারিখ একটিই — NID → জন্মতারিখ। bKash নম্বর জানলে অ্যাকাউন্টধারী একজনই। কিন্তু উল্টোটা সত্য নয়: একই জন্মতারিখে লাখো মানুষ, তাই জন্মতারিখ → NID নয়। “ফাংশন” শব্দটি গণিত থেকে: X থেকে Y-তে একটি ফাংশন, প্রতিটি ইনপুটের একটিই আউটপুট।
enrol_wide-এর ব্যবসার নিয়ম থেকে FD-গুলো লেখা যায়:
student_id → student_name— একটি রোল একজনেরইcourse_id → course_title, instructor_id— একটি কোর্সের একটি নাম, একজন শিক্ষক (এই বিশ্ববিদ্যালয়ের নিয়মে)instructor_id → instructor_name, inst_dept— একজন শিক্ষক একটি বিভাগেরstudent_id, course_id → grade— একটি ভর্তির একটি গ্রেড
লক্ষ করুন, key-র সংজ্ঞাও FD দিয়ে লেখা যায়: K একটি superkey ঠিক তখনই, যখন K → R (K সব attribute নির্ধারণ করে)। আর candidate key হলো এমন minimal K. পর্ব ৬-এর key এভাবে FD-র একটি বিশেষ ঘটনা।
Trivial আর non-trivial FD
কিছু FD সব relation-এ, সব সময় সত্য — কোনো নিয়ম না জেনেই। যেমন student_id, course_id → course_id: দুটি সারি (student_id, course_id)-তে মিললে course_id-তে তো মিলবেই। সাধারণভাবে Y ⊆ X হলে X → Y সবসময় সত্য; একে বলে trivial FD. Y-তে X-এর বাইরের অন্তত একটি attribute থাকলে FD-টি non-trivial — এগুলোই আসল তথ্য বহন করে। আর X ∩ Y = ∅ হলে তাকে কখনো কখনো “completely non-trivial” বলে।
FD আসে নিয়ম থেকে — ডেটা শুধু খণ্ডন করতে পারে
সংজ্ঞার “প্রতিটি বৈধ instance” অংশটি সবচেয়ে গুরুত্বপূর্ণ। FD হলো schema-র ধর্ম, ব্যবসার নিয়মের গাণিতিক রূপ। আজকের ডেটায় একটি FD খাটলেই সেটি নিয়ম হয়ে যায় না। কিন্তু একটি জোড়া সারিও যদি X-এ মেলে আর Y-তে না মেলে, তাহলে নিশ্চিত — এই ডেটার জন্য X → Y সত্য নয় (হয় নিয়মটি নেই, নয়তো ডেটা ভুল)।
GROUP BY X HAVING COUNT(DISTINCT Y) > 1GROUP BY একই X-এর সারিগুলো এক দলে আনে, COUNT(DISTINCT Y) গোনে দলে Y-এর কয়টি আলাদা মান, আর HAVING শুধু সেই দলগুলো রাখে যেখানে মান একাধিক — অর্থাৎ লঙ্ঘন। কোনো সারি না এলে এই ডেটায় FD খাটছে। প্রথমে enrol_wide-এ চারটি সম্ভাব্য FD একসাথে পরীক্ষা করুন:
-- প্রতিটি FD-র জন্য লঙ্ঘনকারী দলের সংখ্যা
SELECT 'course_id -> course_title' AS fd, COUNT(*) AS violations FROM
(SELECT course_id FROM enrol_wide GROUP BY course_id
HAVING COUNT(DISTINCT course_title) > 1)
UNION ALL
SELECT 'instructor_id -> inst_dept', COUNT(*) FROM
(SELECT instructor_id FROM enrol_wide GROUP BY instructor_id
HAVING COUNT(DISTINCT inst_dept) > 1)
UNION ALL
SELECT 'student_id -> student_name', COUNT(*) FROM
(SELECT student_id FROM enrol_wide GROUP BY student_id
HAVING COUNT(DISTINCT student_name) > 1)
UNION ALL
SELECT 'course_id -> grade', COUNT(*) FROM
(SELECT course_id FROM enrol_wide GROUP BY course_id
HAVING COUNT(DISTINCT grade) > 1);ফলে চারটি সারি: course_id -> course_title | 0, instructor_id -> inst_dept | 0, student_id -> student_name | 0 আর course_id -> grade | 2. প্রথম তিনটি এই ডেটায় খাটছে — আর নিয়মেও এগুলো সত্য, তাই এরা আসল FD. চতুর্থটি দুটি দলে ভেঙেছে: CSE201-এ গ্রেড A আর B+, CSE301-এ A- আর B. স্বাভাবিক — একই কোর্সে সবাই একই গ্রেড পান না। এক জোড়া সারিই যথেষ্ট: course_id → grade নিশ্চিতভাবে FD নয়।
এবার একটি “খারাপ update”-এর পর লঙ্ঘন ধরা দেখুন। কেউ Bristy-র CSE301 সারিতে শিক্ষকের নাম হাতে টাইপ করে একটু আলাদা লিখেছেন। চোখে পড়ে না, কিন্তু FD-পরীক্ষা ধরে ফেলে, আর GROUP_CONCAT(DISTINCT …) দেখায় কোন কোন মান সংঘাতে:
-- ভুল হাতে-টাইপ: একটি সারিতে শিক্ষকের নাম আলাদা
UPDATE enrol_wide SET instructor_name = 'Tasnim Chowdhury (CSE)'
WHERE student_id = 2102 AND course_id = 'CSE301';
-- instructor_id -> instructor_name এখনো খাটে?
SELECT instructor_id, COUNT(DISTINCT instructor_name) AS names,
GROUP_CONCAT(DISTINCT instructor_name) AS values_seen
FROM enrol_wide GROUP BY instructor_id
HAVING COUNT(DISTINCT instructor_name) > 1;UPDATE “১টি সারি বদলেছে”, তারপর একটি সারি: 6 | 2 | Tasnim Chowdhury,Tasnim Chowdhury (CSE). শিক্ষক 6-এর দুটি নাম — FD ভেঙেছে, আর ঠিক কোথায়, তাও দেখা যাচ্ছে। পুরোনো ডেটা পরিষ্কার (data cleaning) করার সময় এটিই প্রথম কাজ: যে FD-গুলো নিয়মে সত্য, প্রতিটির জন্য এই কোয়েরি চালিয়ে সংঘাত খুঁজুন। আর স্থায়ী সমাধান হলো টেবিল ভাগ করা, যাতে শিক্ষকের নাম মাত্র এক জায়গায় থাকে।
আসল ডেটাবেসে — “খাটছে” মানেই নিয়ম নয়
এবার কোর্সের সাধারণ বিশ্ববিদ্যালয় ডেটাবেস (db-uni) নিই। এতে আছে departments (dept_id, name, building), instructors (instructor_id, name, dept_id, salary, mentor_id), students (student_id, name, dept_id, batch, cgpa), courses (course_id, title, dept_id, credits, instructor_id) আর enrollments (student_id, course_id, term, grade), যার key (student_id, course_id, term). তিনটি সম্ভাব্য FD পরীক্ষা করুন:
-- ১) একটি বিভাগের সব কোর্সের credit কি সমান? (dept_id -> credits)
SELECT dept_id, COUNT(DISTINCT credits) AS n, GROUP_CONCAT(DISTINCT credits) AS vals
FROM courses GROUP BY dept_id HAVING COUNT(DISTINCT credits) > 1;
-- ২) একজন শিক্ষক কি শুধু এক বিভাগের কোর্স পড়ান? (instructor_id -> dept_id)
SELECT instructor_id, COUNT(DISTINCT dept_id) AS n
FROM courses GROUP BY instructor_id HAVING COUNT(DISTINCT dept_id) > 1;
-- ৩) একজন একটি কোর্স কি একবারই নেন? (student_id, course_id -> term)
SELECT student_id, course_id, COUNT(DISTINCT term) AS n
FROM enrollments GROUP BY student_id, course_id HAVING COUNT(DISTINCT term) > 1;প্রথমটিতে একটি সারি: 1 | 2 | 3.0,1.5 — CSE-র বেশিরভাগ কোর্স ৩ credit, কিন্তু CSE302 (ল্যাব) ১.৫. তাই dept_id → credits নিশ্চিতভাবে FD নয়। দ্বিতীয় আর তৃতীয়টিতে “(কোনো সারি নেই)”। কিন্তু সাবধান! এই ছোট ডেটায় কোনো শিক্ষক অন্য বিভাগের কোর্স পড়াননি, আর কেউ কোর্স আবার নেননি — তাই কোনো লঙ্ঘন নেই। অথচ নিয়মে CSE-র একজন শিক্ষক BBA-র একটি কোর্স পড়াতেই পারেন, আর F পেলে কোর্স আবার নেওয়া যায় (সেজন্যই enrollments-এর key-তে term আছে)। এ দুটি “আজ খাটছে, নিয়মে নেই” — ডেটা দেখে FD ঘোষণা করার বিপদ ঠিক এখানে।
দোকানের ডেটাবেসেও (db-shop: customers, categories, products, orders, আর order_items — order_id, product_id, quantity, unit_price) একটি শিক্ষণীয় উদাহরণ আছে। মনে হতে পারে product_id জানলেই দাম জানা যায়। পরীক্ষা করুন:
-- order_items-এ product_id -> unit_price খাটে?
SELECT product_id, COUNT(DISTINCT unit_price) AS prices,
GROUP_CONCAT(DISTINCT unit_price) AS vals
FROM order_items GROUP BY product_id
HAVING COUNT(DISTINCT unit_price) > 1;
-- (order_id, product_id) -> unit_price খাটে?
SELECT order_id, product_id, COUNT(DISTINCT unit_price) AS n
FROM order_items GROUP BY order_id, product_id
HAVING COUNT(DISTINCT unit_price) > 1;প্রথম কোয়েরিতে দুটি সারি: 101 | 2 | 15000.0,14500.0 আর 102 | 2 | 1200.0,1150.0. Mobile Phone একবার ১৫,০০০ টাকায়, আরেকবার ছাড়ে ১৪,৫০০ টাকায় বিক্রি হয়েছে। দ্বিতীয়টিতে “(কোনো সারি নেই)” — (order_id, product_id) এই টেবিলের key, তাই সেটি সবকিছু নির্ধারণ করে। শিক্ষা: unit_price নির্ভর করে পুরো key-র ওপর, শুধু product_id-র ওপর নয় — “বিক্রির মুহূর্তের দাম”। এটি redundancy নয়, সঠিক ডিজাইন। কোন FD সত্য, তা ঠিক হয় অর্থ বুঝে, কলামের নাম দেখে নয়।
NULL লঙ্ঘন লুকিয়ে ফেলতে পারে। COUNT(DISTINCT Y) NULL গোনে না। একটি সারিতে মান 'CSE' আর আরেকটিতে NULL থাকলে গণনা ১ আসে, লঙ্ঘন চোখ এড়ায়। আগে SELECT DISTINCT X, Y নিয়ে তারপর গুনলে NULL-ও একটি আলাদা মান হিসেবে ধরা পড়ে, কারণ DISTINCT দুটি NULL-কে একই ধরে। নিচে দুই পদ্ধতি পাশাপাশি:
-- একটি সারিতে শিক্ষকের বিভাগ মুছে গেল (NULL)
UPDATE enrol_wide SET inst_dept = NULL
WHERE student_id = 2102 AND course_id = 'CSE201';
-- পদ্ধতি ১: COUNT(DISTINCT) — NULL বাদ পড়ে
SELECT instructor_id, COUNT(DISTINCT inst_dept) AS n
FROM enrol_wide GROUP BY instructor_id HAVING COUNT(DISTINCT inst_dept) > 1;
-- পদ্ধতি ২: আগে আলাদা জোড়া, তারপর গোনা
SELECT instructor_id, COUNT(*) AS n
FROM (SELECT DISTINCT instructor_id, inst_dept FROM enrol_wide)
GROUP BY instructor_id HAVING COUNT(*) > 1;পদ্ধতি ১ দেখায় “(কোনো সারি নেই)” — যেন সব ঠিক আছে। পদ্ধতি ২ দেখায় 1 | 2: শিক্ষক 1-এর জন্য দুটি আলাদা জোড়া, (1, 'CSE') আর (1, NULL). NULL কি সত্যিই “আরেকটি মান”? তত্ত্বে প্রশ্নটি জটিল, কারণ NULL মানে অজানা (পর্ব ১৬)। ব্যবহারিক নিয়ম: FD-পরীক্ষায় NULL-কে সন্দেহজনক ধরুন আর পদ্ধতি ২ ব্যবহার করুন। একাধিক কলামের Y-এর জন্যও পদ্ধতি ২ সুবিধাজনক: SELECT DISTINCT X, Y1, Y2 লিখলেই হয়।
৩ · Armstrong-এর axiom — তিনটি নিয়ম, প্রমাণ আর তিনটি উপ-নিয়ম
ডিজাইনার সাধারণত কয়েকটি FD জানেন — ধরুন সেটটির নাম F. কিন্তু F থেকে আরও অনেক FD যুক্তি দিয়ে বের হয়। course_id → instructor_id আর instructor_id → inst_dept জানলে course_id → inst_dept-ও সত্য, যদিও কেউ সেটি আলাদা করে লেখেননি। F থেকে যত FD যৌক্তিকভাবে বের হয় (F নিজেসহ), তাদের সেটকে বলে F-এর closure, লেখা হয় F⁺. F⁺ বের করার নিয়মগুলোকে বলে inference rule. ১৯৭৪ সালে William W. Armstrong তিনটি নিয়ম দেন, যা আজ Armstrong-এর axiom নামে পরিচিত।
১. Reflexivity
Y ⊆ X হলে X → Y.
trivial FD-র উৎস২. Augmentation
X → Y হলে যেকোনো Z-এর জন্য XZ → YZ.
দুই দিকে একই জিনিস যোগ৩. Transitivity
X → Y আর Y → Z হলে X → Z.
শিকল জোড়াএখানে XZ মানে X ∪ Z (সেটের সংযোগ) — FD-র বইয়ে এভাবেই সংক্ষেপে লেখা হয়।
প্রমাণ — নিয়মগুলো sound (ভুল কিছু বের করে না)
একটি নিয়ম sound মানে: যে instance F মেনে চলে, সেটি নিয়ম থেকে পাওয়া FD-ও মেনে চলবে। প্রতিটির প্রমাণ সংজ্ঞা থেকে সরাসরি আসে। যেকোনো instance r আর তার দুটি tuple t₁, t₂ নিন।
- Reflexivity: ধরুন t₁[X] = t₂[X]. Y ⊆ X, তাই Y-এর প্রতিটি attribute X-এরও attribute, সেখানে দুটি tuple মেলে। অতএব t₁[Y] = t₂[Y]. ∎
- Augmentation: ধরুন t₁[XZ] = t₂[XZ]. তাহলে আলাদাভাবে t₁[X] = t₂[X] আর t₁[Z] = t₂[Z]. প্রথমটি থেকে X → Y দিয়ে t₁[Y] = t₂[Y]. Y-তে আর Z-এ দুটিতেই মেলে, তাই t₁[YZ] = t₂[YZ]. ∎
- Transitivity: ধরুন t₁[X] = t₂[X]. X → Y দিয়ে t₁[Y] = t₂[Y]; তারপর Y → Z দিয়ে t₁[Z] = t₂[Z]. ∎
উল্টো দিকটি আরও গভীর: নিয়মগুলো complete — F থেকে যে FD-ই সত্যিই বের হয়, তা এই তিনটি নিয়ম বারবার প্রয়োগ করে পাওয়া যাবেই। এর প্রমাণ অংশ ৪-এর closure অ্যালগরিদমের সাথে দেখবেন।
তিনটি উপ-নিয়ম (derived rule) — axiom থেকে প্রমাণ
পরীক্ষায় প্রায়ই বলা হয়: “axiom ব্যবহার করে প্রমাণ করুন”। এই তিনটি নিয়ম বারবার কাজে লাগে, তাই এদের প্রমাণ মুখস্থ নয়, বুঝে রাখুন:
Union: X → Y আর X → Z হলে X → YZ.
- X → Y-কে X দিয়ে augment করুন: XX → XY, অর্থাৎ X → XY (কারণ X ∪ X = X).
- X → Z-কে Y দিয়ে augment করুন: XY → YZ.
- ১ আর ২-এ transitivity: X → XY → YZ, তাই X → YZ. ∎
Decomposition: X → YZ হলে X → Y আর X → Z.
- Y ⊆ YZ, তাই reflexivity দিয়ে YZ → Y.
- X → YZ আর YZ → Y-তে transitivity: X → Y.
- একইভাবে YZ → Z থেকে X → Z. ∎
সতর্কতা: বাম দিক এভাবে ভাঙা যায় না। AB → C থেকে A → C বা B → C আসে না — নাম আর বিভাগ মিলে রোল চেনা গেলেও শুধু নাম দিয়ে যায় না।
Pseudo-transitivity: X → Y আর WY → Z হলে WX → Z.
- X → Y-কে W দিয়ে augment করুন: WX → WY.
- WX → WY আর WY → Z-তে transitivity: WX → Z. ∎
উদাহরণ: course_id → instructor_id, আর (term, instructor_id) → room হলে (term, course_id) → room.
একটি ছোট উদাহরণে axiom দিয়ে ধাপে ধাপে প্রমাণ: R(A, B, C, D, E, F), F = {A → B, A → C, CD → E, CD → F, B → E}. প্রমাণ করুন AD → E.
- A → C (দেওয়া)। D দিয়ে augment: AD → CD.
- CD → E (দেওয়া)।
- ১ আর ২-এ transitivity: AD → E. ∎
অন্য পথেও হয়: A → B, B → E থেকে transitivity-তে A → E, তারপর reflexivity-তে AD → A, আবার transitivity-তে AD → E. একটি FD-র একাধিক প্রমাণ থাকতে পারে; পরীক্ষায় যেকোনো সঠিক শিকলই চলে। কিন্তু প্রতিটি FD-র জন্য এমন শিকল খোঁজা ক্লান্তিকর, আর F⁺ নিজেও বিশাল — n টি attribute-এর relation-এ শুধু trivial FD-ই 3ⁿ টি (প্রতিটি attribute X-এ ও Y-তে, শুধু X-এ, বা কোনোটিতেই না — তিন বিকল্প; খালি সেটসহ গুনলে)। তাই বাস্তবে F⁺ কেউ লেখে না; লেখে ছোট একটি জিনিস — X⁺.
৪ · Attribute closure X⁺ — সব FD-প্রশ্নের চাবি
একটি attribute-সেট X-এর attribute closure X⁺ হলো F-এর অধীনে X যতগুলো attribute নির্ধারণ করে, তাদের সেট: X⁺ = { A : X → A ∈ F⁺ }. আর মূল উপপাদ্য:
X → Y ∈ F⁺ ঠিক তখনই, যখন Y ⊆ X⁺.
(যুক্তি: Y ⊆ X⁺ হলে Y-এর প্রতিটি A-র জন্য X → A, union দিয়ে X → Y. উল্টো দিকে X → Y থাকলে decomposition দিয়ে প্রতিটি A ∈ Y-এর জন্য X → A, তাই A ∈ X⁺.) অর্থাৎ 3ⁿ আকারের F⁺ লাগবে না; শুধু X⁺ বের করলেই X → Y সত্য কি না বলা যায়। অ্যালগরিদমটি সহজ, অনেকটা গুজব ছড়ানোর মতো: যাদের জানা আছে তাদের দল দিয়ে শুরু করুন; কোনো FD-র পুরো বাম দিক দলে থাকলে ডান দিককেও দলে নিন; যতক্ষণ দল বাড়ে, চালিয়ে যান।
closure(X, F):
result := X
repeat
changed := false
for each FD (L -> R) in F:
if L ⊆ result and R ⊄ result:
result := result ∪ R
changed := true
until changed = false
return resultএবার ক্লাসিক উদাহরণে চালাই: R(A, B, C, D, E, F), F = {A → B, A → C, CD → E, CD → F, B → E}. বের করি (AD)⁺.
| pass | |
| result-এর আকার | |
| changed |
- শুরু। result = X = {A, D}. X → X তো trivially সত্য (reflexivity), তাই X-এর সব attribute closure-এ থাকবেই।
- A → B. বাম দিক {A} ⊆ result, তাই B যোগ: {A, B, D}. যুক্তি: AD → A (reflexivity), A → B, তাই AD → B (transitivity).
- A → C. একইভাবে C যোগ: {A, B, C, D}.
- CD → E. এখন C আর D দুটিই result-এ — C এসেছে আগের ধাপে। তাই E যোগ: {A, B, C, D, E}. একটি FD-র বাম দিকের সব attribute থাকতে হয়; একটি থাকলে চলে না।
- CD → F. F যোগ: {A, B, C, D, E, F} — এটিই R-এর সব attribute.
- B → E. B আছে, কিন্তু E আগেই এসেছে — কিছু বদলায় না। Pass ১ শেষ; এই pass-এ result বেড়েছে, তাই আরেকবার পুরো F দেখতে হবে।
- Pass ২ — কিছুই বাড়ে না। পাঁচটি FD-র কোনোটিই নতুন attribute আনে না, changed = false, অ্যালগরিদম থামে। (এখানে result আগেই পুরো R, তাই আসলে আগে থামানো যেত; কিন্তু সাধারণ নিয়মে একটি পূর্ণ “ফাঁকা” pass লাগে।)
- সিদ্ধান্ত। (AD)⁺ = R, তাই AD → R: AD একটি superkey. আবার A⁺ = {A, B, C, E} আর D⁺ = {D} — কোনোটিই একা R নয়। তাই AD থেকে কিছু বাদ দেওয়া যায় না: AD একটি candidate key.
অ্যালগরিদম কেন সঠিক — প্রমাণ (আর Armstrong-এর completeness)
ধরুন অ্যালগরিদম শেষে ফল S. দাবি: A ∈ S ঠিক তখনই, যখন X → A ∈ F⁺.
- S-এর প্রতিটি attribute সত্যিই নির্ধারিত (sound): শুরুতে X → X (reflexivity). ধরুন কোনো মুহূর্তে X → result. একটি FD L → R প্রয়োগ হয় যখন L ⊆ result: decomposition দিয়ে X → L, transitivity দিয়ে X → R, union দিয়ে X → result ∪ R. Induction-এ শেষ পর্যন্ত X → S.
- S-এর বাইরের কিছু নির্ধারিত নয়: দুই সারির একটি instance বানান — t₁-এর সব ঘরে 0; t₂-এর S-এর ঘরে 0, বাকি ঘরে 1. F-এর যেকোনো L → R নিন। L ⊄ S হলে দুটি সারি L-এ মেলে না, FD খাটে। L ⊆ S হলে অ্যালগরিদম থামার সময় R ⊆ S (নইলে আরেকবার বাড়ত), তাই সারি দুটি R-এও মেলে, FD খাটে। অর্থাৎ instance-টি F-এর সব FD মেনে চলে। কিন্তু A ∉ S হলে সারি দুটি X-এ মেলে (X ⊆ S) আর A-তে মেলে না — এই instance-এ X → A ভাঙে। তাই X → A, F থেকে বের হয় না। ∎
প্রথম অংশে শুধু Armstrong-এর নিয়ম ব্যবহার হয়েছে। তাই যে FD সত্যিই বের হয়, তা axiom দিয়েই পাওয়া যায় — এটিই completeness-এর প্রমাণ।
খরচ: ধরুন |R| = n আর F-এ m টি FD. শেষ pass বাদে প্রতিটি pass অন্তত একটি attribute যোগ করে, তাই pass সর্বোচ্চ n − |X| + 1 টি। প্রতি pass-এ m টি FD, প্রতিটির “L ⊆ result?” যাচাই bitset বা boolean array-তে O(n). মোট O(n² · m) — ছোট schema-য় এক নিমেষের কাজ। (আরও দ্রুত, ইনপুটের দৈর্ঘ্যে linear একটি সংস্করণ আছে — “আরও গভীরে” বাক্সে।)
নিজে চালিয়ে দেখুন — closure আর key ক্যালকুলেটর
নিচের ছোট JavaScript প্রোগ্রাম ঠিক উপরের অ্যালগরিদম চালায় আর প্রতিটি যোগ ছাপে। শেষে সব উপসেট ছোট থেকে বড় ক্রমে দেখে সব candidate key বের করে। attrs আর fds বদলে নিজের পরীক্ষার প্রশ্ন মিলিয়ে নিন (প্রতিটি attribute একটি বড় হাতের অক্ষর)।
// FD = "LHS->RHS", each attribute is one capital letter
const attrs = "ABCDEF";
const fds = ["A->B", "A->C", "CD->E", "CD->F", "B->E"];
function closure(x, list, show) {
const res = new Set(x);
let changed = true, pass = 0;
while (changed) {
changed = false; pass++;
for (const fd of list) {
const [lhs, rhs] = fd.split("->");
if ([...lhs].every(a => res.has(a)) && [...rhs].some(a => !res.has(a))) {
for (const a of rhs) res.add(a);
changed = true;
if (show) console.log(` pass ${pass}: ${fd} => {${[...res].sort().join(",")}}`);
}
}
}
return [...res].sort().join("");
}
for (const x of ["AD", "A", "CD"]) console.log(`(${x})+ = {${closure(x, fds, true)}}`);
const subsets = [];
for (let m = 1; m < 1 << attrs.length; m++)
subsets.push([...attrs].filter((_, i) => m >> i & 1).join(""));
subsets.sort((p, q) => p.length - q.length);
const keys = [];
for (const s of subsets)
if (closure(s, fds, false).length === attrs.length && !keys.some(k => [...k].every(a => s.includes(a))))
keys.push(s);
console.log("candidate keys:", keys.join(", "));আউটপুটের প্রথম অংশ ডায়াগ্রামের সাথে হুবহু মেলে: pass 1: A->B => {A,B,D}, তারপর A->C, CD->E, CD->F, আর শেষে (AD)+ = {ABCDEF}. এরপর (A)+ = {ABCE} (A->B, A->C, B->E তিনটি যোগ) আর (CD)+ = {CDEF}. শেষ লাইন candidate keys: AD. Key খোঁজার অংশটি 2⁶ − 1 = ৬৩টি উপসেট আকার অনুযায়ী সাজিয়ে দেখে; কোনো উপসেটের closure পুরো R হলে আর তার ভেতরে আগে পাওয়া কোনো key না থাকলে সেটি নতুন candidate key. এই brute force ছোট schema-য় ঠিক আছে, কিন্তু 2ⁿ বাড়ে — বড় schema-র জন্য পরের অংশের কৌশল লাগবে।
৫ · X⁺ দিয়ে superkey আর candidate key খোঁজা
X⁺ = R হলে X একটি superkey; আর X-এর কোনো প্রকৃত উপসেটের closure R না হলে X একটি candidate key. কিন্তু সব উপসেট পরীক্ষা করা ধীর। পরীক্ষার খাতায় আর বাস্তবে একটি পদ্ধতিগত কৌশল কাজ করে — প্রতিটি attribute কোথায় আসে, তা দেখে শ্রেণিভাগ:
| Attribute কোথায় আসে | Key-তে ভূমিকা | কারণ |
|---|---|---|
| কোনো FD-র ডান দিকে কখনো নয় (শুধু বামে, বা কোথাও নয়) | প্রতিটি key-তে থাকবেই | অন্য কিছু থেকে এটি আসে না, তাই key-কে নিজেই ধরে রাখতে হয় |
| শুধু ডান দিকে (কোনো বামে নয়) | কোনো candidate key-তে থাকবে না | বাকিরা মিলে এটি বের করে ফেলে; রাখলে বাড়তি হয় |
| দুই দিকেই | থাকতেও পারে, না-ও পারে | এগুলো নিয়েই পরীক্ষা করতে হয় |
দ্বিতীয় সারির যুক্তি: ধরুন E শুধু ডান দিকে আসে, আর K একটি superkey যাতে E আছে। K-এর closure হিসাবের সময় E কখনো কোনো FD-র বাম দিকে লাগে না, তাই (K − E)⁺-এও E ছাড়া বাকি সব আসে; তারপর যে FD-র ডান দিকে E, তার বাম দিকও (যাতে E নেই) এসে গেছে, তাই E-ও আসে। অর্থাৎ K − E-ও superkey, K minimal নয়। কৌশলটি তাই: আবশ্যিক অংশ (core) দিয়ে শুরু করুন, তার closure দেখুন, না হলে “দুই দিকের” attribute একটি একটি করে যোগ করুন, ছোট থেকে বড়।
| যা পরীক্ষা করছি | |
| পাওয়া key |
- সমস্যা। পাঁচটি attribute, চারটি FD. সব উপসেট দেখলে ৩১টি; শ্রেণিভাগ করে অনেক কম পরীক্ষায় কাজ সারব।
- শ্রেণিভাগ। B শুধু বামে (AB → C-তে) আসে, কোনো ডান দিকে নেই। E শুধু ডানে (A → E). A, C, D দুই দিকেই।
- Core দিয়ে শুরু। B প্রতিটি key-তে থাকবেই, E কোনোটিতে নয়। B⁺ = {B} — B একা কোনো FD-র বাম দিক পূরণ করে না। তাই B-র সাথে কিছু যোগ লাগবে, আর সেটি আসবে A, C, D থেকে।
- AB. AB → C, তারপর C → D, তারপর A → E: সবই আসে। (AB)⁺ = R. A⁺ = {A, E} বা B⁺ = {B} একা R নয়, তাই AB minimal — candidate key.
- BC. C → D, D → A, A → E — B আর C দিয়েই সব। (BC)⁺ = R, আর B বা C একা যথেষ্ট নয় (C⁺ = {A, C, D, E}, তাতে B নেই)। আরেকটি candidate key.
- BD. D → A দিয়ে A এল, তারপর AB → C, তারপর বাকিগুলো। (BD)⁺ = R — তৃতীয় candidate key.
- উপসংহার। B-র সাথে দুটি করে attribute (ABC, ABD, BCD) বা তিনটি নিলে প্রতিটিতে আগের কোনো key থাকে, তাই minimal নয়। Candidate key ঠিক তিনটি: AB, BC, BD. যে attribute অন্তত একটি candidate key-তে আছে, সে prime: A, B, C, D. E কোনোটিতেই নেই: non-prime.
Prime আর non-prime attribute
শেষ ধাপের দুটি শব্দ পরের পর্বে বারবার লাগবে। Prime attribute হলো এমন attribute, যা অন্তত একটি candidate key-র সদস্য। Non-prime attribute কোনো candidate key-তেই নেই। 2NF আর 3NF-এর সংজ্ঞা (পর্ব ৩৬) ঠিক এই ভাগের ওপর লেখা — যেমন 2NF বলে “কোনো non-prime attribute কোনো candidate key-র অংশের ওপর নির্ভর করবে না”। তাই সব candidate key খুঁজে না পেলে কোনটি prime, বলা যায় না, আর normal form-ও ঠিক করা যায় না। এখানেই একটি সাধারণ ভুল: শুধু একটি key (যেমন primary key) দেখে বাকি সবাইকে non-prime ধরা। উপরের উদাহরণে কেউ শুধু AB দেখলে C আর D-কে non-prime ভাবতেন — অথচ দুটিই prime.
৬ · FD সেটের সমতা আর canonical cover
দুটি FD-সেট F আর G সমতুল্য (equivalent), যদি F⁺ = G⁺ — অর্থাৎ দুটি থেকে ঠিক একই FD-গুলো বের হয়। যাচাই করতে F⁺ লাগে না: G-এর প্রতিটি FD X → Y-এর জন্য F-এর অধীনে X⁺ বের করে দেখুন Y ⊆ X⁺ কি না (তাহলে “F, G-কে cover করে”), আর উল্টো দিকেও একই কাজ। দুই দিকেই মিললে সমতুল্য।
উদাহরণ: F = {A → B, B → C, A → C}, G = {A → B, B → C}. G-এর দুটি FD তো F-এই আছে। F-এর A → C: G-এর অধীনে A⁺ = {A, B, C}, তাতে C আছে। তাই F ≡ G. কিন্তু G′ = {A → B, A → C} নিলে F-এর B → C: G′-এর অধীনে B⁺ = {B} — C নেই। তাই F আর G′ সমতুল্য নয়।
F ≡ G দেখায়, A → C আসলে বাড়তি: বাকি দুটি থেকেই বের হয়। প্রতিটি FD মানে ডেটাবেসকে প্রতিটি INSERT/UPDATE-এ একটি যাচাই করতে হবে, আর normalization অ্যালগরিদমে একটি করে টেবিল আসতে পারে। তাই আমরা চাই সবচেয়ে ছোট সমতুল্য সেট — canonical cover (Elmasri–Navathe-এর বইয়ে “minimal cover”). একটি সেট minimal, যদি:
- প্রতিটি FD-র ডান দিকে একটিই attribute;
- কোনো FD-র বাম দিকে কোনো extraneous (বাড়তি) attribute নেই — অর্থাৎ বাম দিক থেকে একটি attribute ফেললে সেট আর সমতুল্য থাকে না;
- কোনো redundant FD নেই — অর্থাৎ যেকোনো FD বাদ দিলে সেট আর সমতুল্য থাকে না।
দুটি যাচাই কীভাবে করবেন: বাম দিকের বাড়তি attribute: XA → B-তে A বাড়তি, যদি F-এর অধীনে B ∈ X⁺ (A ছাড়াই X দিয়ে B আসে)। Redundant FD: X → B বাদ দিয়ে বাকি সেটের অধীনে X⁺ বের করুন; তাতে B থাকলে FD-টি redundant. Silberschatz-এর বইয়ের canonical cover শেষে একই বাম দিকের FD-গুলো union দিয়ে জুড়ে দেয় (A → B, A → C-কে A → BC); ধারণা একই।
| ধাপ | |
| FD সংখ্যা |
- শুরুর সেট। চারটি FD, কিছু স্পষ্টতই একে অপরের পুনরাবৃত্তি। লক্ষ্য: সবচেয়ে ছোট সমতুল্য সেট।
- ধাপ ১ — ডান দিক ভাঙা। Decomposition নিয়মে A → BC থেকে A → B আর A → C. A → B আগেও ছিল, সেট বলে একবারই রাখি। এখন প্রতিটি FD-র ডান দিকে একটি attribute.
- ধাপ ২ — বাম দিক ছাঁটা। শুধু AB → C-র বাম দিকে একাধিক attribute. A বাড়তি কি না দেখতে বর্তমান F-এর অধীনে B⁺ বের করি: B → C দিয়ে {B, C}. C আছে, তাই A বাড়তি; AB → C হয়ে যায় B → C, যা আগেই আছে। (B বাড়তি কি না, তা আর দেখার দরকার নেই, কারণ FD-টি এখন একক-attribute বাম দিকের।)
- ধাপ ৩ — A → B কি redundant? এটি বাদ দিয়ে বাকি {A → C, B → C}-এর অধীনে A⁺ = {A, C}. B আসে না, তাই A → B অপরিহার্য।
- A → C কি redundant? বাকি {A → B, B → C}-এর অধীনে A⁺ = {A, B, C} — C আসে (transitivity). তাই A → C বাদ।
- B → C কি redundant? বাকি {A → B}-এর অধীনে B⁺ = {B}. C আসে না — রাখতে হবে।
- ফল। Fc = {A → B, B → C}. মূল F-এর চারটি FD-ই এখান থেকে বের হয় (A → BC: A⁺ = {A, B, C}; AB → C: (AB)⁺-এ C আছে)। একই বাম দিকের FD নেই, তাই union-এর কিছু নেই।
ক্রম গুরুত্বপূর্ণ: আগে বাম দিক ছাঁটুন, তারপর redundant FD বাদ দিন। উল্টো ক্রমে করলে ফল minimal না-ও হতে পারে। উদাহরণ F = {B → A, D → A, AB → D}: আগে redundant খুঁজলে কোনোটিই বাদ যায় না। তারপর AB → D-র A বাড়তি (B⁺ = {A, B, D}), তাই সেটি হয় B → D — এখন B → A আবার redundant (B → D → A), কিন্তু সেই পরীক্ষা আগেই শেষ! সঠিক ক্রমে ফল {B → D, D → A}. আরেকটি তথ্য: canonical cover সবসময় একটিই হয় না। যেমন {A → B, B → A, A → C, B → C}-এর দুটি সমান ছোট cover — {A → B, B → A, A → C} আর {A → B, B → A, B → C}. দুটিই সঠিক উত্তর।
৭ · পরীক্ষার ধাঁচের সমস্যা — সম্পূর্ণ সমাধান
বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর লিখিত পরীক্ষায় এই অধ্যায় থেকে সাধারণত চার ধরনের প্রশ্ন আসে: closure বের করা, একটি FD বের হয় কি না যাচাই, সব candidate key, আর canonical cover. প্রতিটির একটি করে সম্পূর্ণ সমাধান:
সমস্যা ১ — সব candidate key
R(A, B, C, D, E), F = {A → BC, CD → E, B → D, E → A}. সব candidate key আর prime attribute বের করুন।
শ্রেণিভাগ: ডান দিকে আসে B, C, E, D, A — সবগুলোই। তাই কোনো আবশ্যিক core নেই; একক attribute দিয়ে শুরু।
একক: A⁺: A → BC দিয়ে {A, B, C}; B → D দিয়ে D; CD → E দিয়ে E — পুরো R. A key. E⁺: E → A, তারপর A-র মতোই সব — E key. B⁺ = {B, D}, C⁺ = {C}, D⁺ = {D} — নয়।
জোড়া (A বা E ছাড়া, কারণ তাদের থাকলে minimal হয় না): BC: B → D, CD → E, E → A — পুরো R, BC key. BD: {B, D}, কিছু চলে না — নয়। CD: CD → E, E → A, A → BC — পুরো R, CD key.
তিনটির সেট: A, E ছাড়া বাকি তিনটি B, C, D মিলে BCD — এতে BC আছে, minimal নয়। উত্তর: candidate key A, E, BC, CD; পাঁচটি attribute-ই prime, non-prime নেই।
সমস্যা ২ — একটি FD বের হয় কি না
একই F-এ (ক) BC → A আর (খ) B → E কি F⁺-এ আছে?
(ক) (BC)⁺ = R (সমস্যা ১), তাতে A আছে — হ্যাঁ. Axiom-এর শিকল: B → D থেকে augment করে BC → CD; CD → E; E → A; transitivity দুবার। (খ) B⁺ = {B, D}, E নেই — না. প্রমাণ হিসেবে দুই সারির instance: দুটি সারি B, D-তে মেলে আর A, C, E-তে আলাদা — এটি F-এর সব FD মানে (দেখে নিন: কোনো FD-র বাম দিক পুরোটা মেলে এমন একমাত্র FD হলো B → D, আর D-তে সারি দুটি মেলে), কিন্তু B → E ভাঙে।
সমস্যা ৩ — দুটি সেট সমতুল্য কি না
F = {A → C, AC → D, E → AD, E → H} আর G = {A → CD, E → AH}. F ≡ G?
G-এর FD, F দিয়ে: F-এ A⁺ = {A, C, D} (A → C, তারপর AC → D) — CD আছে ✓. F-এ E⁺ = {E, A, D, H, C} — AH আছে ✓.
F-এর FD, G দিয়ে: G-এ A⁺ = {A, C, D} — A → C ✓, AC → D ✓ ((AC)⁺ = {A, C, D}). G-এ E⁺ = {E, A, H, C, D} — E → AD ✓, E → H ✓. উত্তর: হ্যাঁ, F ≡ G. লক্ষ করুন, AC → D-র C বাড়তি ছিল (A একাই D দেয়) — G সেটিই সংক্ষেপে লিখেছে।
সমস্যা ৪ — canonical cover, পুরো হিসাবসহ
R(A, B, C, D, E), F = {AB → CD, A → E, E → C}. Canonical cover বের করুন।
ধাপ ১: AB → C, AB → D, A → E, E → C.
ধাপ ২ (বাম দিক): AB → C: F-এ A⁺ = {A, E, C} — C আছে, তাই B বাড়তি; হয় A → C. AB → D: A⁺ = {A, C, E}, D নেই; B⁺ = {B}, D নেই — কোনোটিই বাড়তি নয়, AB → D থাকে। এখন {A → C, AB → D, A → E, E → C}.
ধাপ ৩ (redundant): A → C: বাকি সেটে A⁺ = {A, E, C} — C আসে, বাদ. AB → D: বাকিতে (AB)⁺ = {A, B, E, C}, D নেই — থাকে। A → E: বাকিতে A⁺ = {A}, থাকে। E → C: বাকিতে E⁺ = {E}, থাকে।
উত্তর: Fc = {AB → D, A → E, E → C}. Candidate key-ও এক নজরে: A আর B কোনো ডান দিকে নেই, (AB)⁺ = R — একমাত্র key AB.
৮ · ইতিহাস আর পরের ধাপ — Codd, Armstrong আর multivalued dependency
১৯৭০ সালে relational model প্রস্তাবের পরপরই Edgar F. Codd বুঝলেন, শুধু “টেবিলে রাখো” বললে হবে না — কোন কলাম কোন টেবিলে যাবে, তার নিয়ম লাগবে। ১৯৭১ সালের প্রবন্ধ “Further Normalization of the Data Base Relational Model”-এ (প্রকাশ ১৯৭২) তিনি functional dependency-কে কেন্দ্রে এনে 2NF আর 3NF সংজ্ঞায়িত করেন, আর insert, update ও delete anomaly-র যুক্তি দেন। ১৯৭৪ সালে IFIP Congress-এ William W. Armstrong-এর প্রবন্ধ “Dependency Structures of Data Base Relationships” FD-গুলোর যুক্তিবিদ্যা দাঁড় করায় — যে নিয়মগুলো আজ তাঁর নামে, আর সেগুলো যে sound ও complete, তার প্রমাণ। একই বছর Boyce আর Codd আরও কঠোর BCNF দেন।
এরপর বোঝা গেল FD সব নির্ভরতা ধরতে পারে না। একজন শিক্ষকের একাধিক বিষয় আর একাধিক ফোন নম্বর থাকলে, বিষয় আর ফোন একে অপরের ওপর নির্ভর করে না, অথচ এক টেবিলে রাখলে প্রতিটি জোড়া লিখতে হয়। এই “একটি মানের সাথে একটি সেট বাঁধা” নির্ভরতাকে বলে multivalued dependency (X ↠ Y). ১৯৭৭ সালে Ronald Fagin এটি দিয়ে 4NF সংজ্ঞায়িত করেন (Carlo Zaniolo-ও প্রায় একই সময়ে স্বাধীনভাবে ধারণাটিতে পৌঁছান)। প্রতিটি FD একটি MVD-ও, কিন্তু উল্টোটা নয়। এর বিস্তারিত পর্ব ৩৭-এ।
আরও গভীরে: ১৯৭৯ সালে Catriel Beeri আর Philip Bernstein দেখান, প্রতিটি FD-র জন্য “বাম দিকের কয়টি attribute এখনো বাকি” একটি counter রেখে X⁺ বের করা যায় F-এর মোট দৈর্ঘ্যে linear সময়ে — অংশ ৪-এর O(n²·m) থেকে অনেক দ্রুত। কিন্তু সব candidate key খোঁজা সহজ নয়: key-র সংখ্যাই exponential হতে পারে (n জোড়া Aᵢ ↔ Bᵢ থাকলে 2ⁿ টি key), আর Lucchesi ও Osborn (১৯৭৮) দেখান “একটি attribute prime কি না” প্রশ্নটি NP-complete. তাই বড় schema-য় normal form যাচাইয়ের কিছু প্রশ্ন সত্যিই কঠিন — ছোট পরীক্ষার প্রশ্নে যা টের পাওয়া যায় না।
৯ · AI-এর যুগে functional dependency
AI-কে “এই টেবিলের FD আর key বের করো” বললে সুন্দর গোছানো উত্তর আসে। কিন্তু এই বিষয়ে AI-এর ভুলগুলো খুব নির্দিষ্ট ধরনের, আর হাতে X⁺ বের করলেই ধরা পড়ে:
| AI-এর ভুল | ফল | কীভাবে ধরবেন |
|---|---|---|
| নমুনা ডেটা দেখে FD ঘোষণা (“city → zip খাটছে”) | ভুল key, ভুল ভাঙন | প্রতিটি FD-র পেছনে ব্যবসার নিয়মটি জিজ্ঞেস করুন |
| একটি candidate key পেয়ে থেমে যাওয়া | prime/non-prime ভুল, normal form ভুল | শ্রেণিভাগ কৌশলে বাকি key খুঁজুন |
| closure-এ বাম দিকের একটি attribute থাকলেই FD প্রয়োগ | বেশি বড় X⁺ | প্রতিটি যোগের পাশে কোন FD, লিখিয়ে নিন |
| canonical cover-এ ক্রম উল্টো বা বাম দিক না ছাঁটা | redundant FD রয়ে যায় | শেষ সেটের প্রতিটি FD বাদ দিয়ে X⁺ যাচাই |
| AB → C থেকে A → C “decomposition” বলে বের করা | ভিত্তিহীন FD | decomposition শুধু ডান দিকে খাটে |
| লঙ্ঘন খুঁজতে COUNT(DISTINCT) — NULL বাদ পড়ে | লঙ্ঘন চোখ এড়ায় | SELECT DISTINCT X, Y নিয়ে গুনুন |
AI-কে উত্তর নয়, যাচাইযোগ্য হিসাব চাইতে বলুন — প্রতিটি ধাপ আপনি মিলিয়ে নেবেন:
AI-এর FD-হিসাব হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Functional dependency কী, উদাহরণ দিন। — যেকোনো দুটি সারি X-এ মিললে Y-তে মিলবে; যেমন student_id → student_name.
- Armstrong-এর axiom কয়টি, কী কী? — তিনটি: reflexivity, augmentation, transitivity; এরা sound ও complete. Union, decomposition, pseudo-transitivity এদের থেকে প্রমাণিত।
- Attribute closure দিয়ে কী কী করা যায়? — X → Y যাচাই (Y ⊆ X⁺), superkey/candidate key খোঁজা, দুটি FD-সেটের সমতা আর canonical cover.
- Prime attribute কী? — যে attribute অন্তত একটি candidate key-র সদস্য; 2NF ও 3NF-এর সংজ্ঞায় লাগে।
- ডেটা দেখে কি FD প্রমাণ করা যায়? — না; ডেটা শুধু খণ্ডন করতে পারে (GROUP BY X HAVING COUNT(DISTINCT Y) > 1). FD আসে ব্যবসার নিয়ম থেকে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে নিয়ম-মুখস্থ থেকে ডিজাইনের যুক্তিতে নিয়ে যাবে।
প্র ০১কিছু data profiling টুল পুরোনো টেবিল স্ক্যান করে “সব FD” খুঁজে দেয়। তাহলে ডিজাইনারের কাছে ব্যবসার নিয়ম জিজ্ঞেস করার দরকার কী?
FD আবিষ্কারের অ্যালগরিদম সত্যিই আছে, আর গবেষণার একটি পুরো ধারা এ নিয়ে। ১৯৯০-এর দশকের শেষে প্রকাশিত TANE-এর মতো অ্যালগরিদম একটি টেবিলের সব সারি দেখে বের করে, কোন কোন X → Y এই ডেটায় খাটে। পুরোনো সিস্টেম থেকে ডেটা নতুন সিস্টেমে নেওয়ার সময় এগুলো খুব কাজের: অচেনা ১৫০ কলামের একটি টেবিলে কোথায় লুকোনো নির্ভরতা আছে, তার একটি প্রাথমিক মানচিত্র মেলে।
কিন্তু এই টুলগুলো যা খুঁজে পায়, তা হলো “এই instance-এ খাটে” — সংজ্ঞার দুর্বল অর্ধেক। সমস্যা দুই দিকে। প্রথমত, আকস্মিক FD: ছোট বা একপেশে ডেটায় অনেক কাকতালীয় নির্ভরতা খাটে। এই পর্বের db-uni-তে instructor_id → dept_id (courses-এ) খাটছিল শুধু কারণ এখনো কেউ অন্য বিভাগে পড়াননি। ৮ জন গ্রাহকের টেবিলে name → phone খাটবে, ৮ লাখে খাটবে না। কলাম বেশি হলে এমন কাকতালীয় FD-র সংখ্যা বিস্ফোরকভাবে বাড়ে, আর টুল শত শত “FD” দেয়, যার বেশিরভাগই অর্থহীন।
দ্বিতীয়ত, লুকোনো FD: পুরোনো ডেটা প্রায়ই নোংরা। একজন শিক্ষকের নাম দুটি বানানে লেখা থাকলে instructor_id → instructor_name ডেটায় ভাঙা, তাই টুল এটিকে FD বলবে না — অথচ নিয়মে এটি সত্য, আর এটিই সবচেয়ে জরুরি FD. এজন্য গবেষকেরা “approximate FD” ধারণা আনেন: প্রায় সব সারিতে খাটে, কিছু ব্যতিক্রম। সেই ব্যতিক্রমগুলোই আসলে ডেটা-ত্রুটির তালিকা।
তাই সঠিক কর্মপ্রবাহ হলো: টুল বা GROUP BY কোয়েরি দিয়ে প্রার্থী FD-র তালিকা নিন, তারপর প্রতিটি নিয়ে ব্যবসার মানুষকে প্রশ্ন করুন — “একজন শিক্ষক কি কখনো দুই বিভাগের হতে পারেন?” উত্তর থেকেই FD ঠিক হয়। ডেটা প্রশ্ন তোলে, নিয়ম উত্তর দেয়।
মূল উপলব্ধি: FD হলো ভবিষ্যতের সব বৈধ ডেটা সম্পর্কে একটি প্রতিশ্রুতি। অতীতের ডেটা সেই প্রতিশ্রুতি ভাঙার প্রমাণ দিতে পারে, কিন্তু প্রতিশ্রুতি দিতে পারে না।
প্র ০২Armstrong-এর axiom “sound আর complete” — এর ব্যবহারিক মূল্য কী? শুধু তিনটি নিয়মই কেন যথেষ্ট?
কল্পনা করুন একটি normalization টুল বা optimizer, যাকে প্রতিনিয়ত প্রশ্ন করতে হয়: “এই FD-গুলো জানা থাকলে ওই FD-টি কি সত্য?” Soundness নিশ্চয়তা দেয়, নিয়ম মেনে যা বের হবে তা কখনো মিথ্যা নয় — টুল কখনো ভুল FD ধরে নিয়ে তথ্য-হারানো ভাঙন প্রস্তাব করবে না। Completeness নিশ্চয়তা দেয়, যা সত্যিই সত্য তা নিয়ম দিয়ে বের হবেই — কোনো লুকোনো নির্ভরতা চোখ এড়াবে না, এমন কোনো “চতুর্থ নিয়ম” খুঁজে বেড়াতে হবে না।
দুটি একসাথে মানে যুক্তির জগৎ (নিয়ম দিয়ে প্রমাণযোগ্য) আর অর্থের জগৎ (সব বৈধ instance-এ সত্য) হুবহু এক। গণিতের যুক্তিবিদ্যায় এটি বিরল সৌভাগ্য — অনেক সমৃদ্ধ যুক্তি-ব্যবস্থায় এমন পূর্ণতা পাওয়া যায় না। FD-র ক্ষেত্রে মাত্র তিনটি সরল নিয়মে তা মেলে, Armstrong ১৯৭৪ সালে এটি দেখান।
Completeness-এর প্রমাণের কৌশলটিও শিক্ষণীয়, যা অংশ ৪-এ দেখেছেন: X → A বের না হলে দুই সারির একটি instance বানানো যায়, যা F মানে কিন্তু X → A ভাঙে। এই কৌশলের বড় রূপের নাম “Armstrong relation” — এমন একটি instance, যা একটি FD-সেট ঠিক যা যা বলে (F⁺) তা-ই মানে, তার বেশি একটি FD-ও নয়। ডিজাইনারের কাছে এটি কাজের: Armstrong relation-এর নমুনা ডেটা ব্যবসার মানুষকে দেখিয়ে জিজ্ঞেস করা যায়, “এমন দুটি সারি কি বাস্তবে সম্ভব?” না হলে একটি FD বাদ পড়েছে।
ব্যবহারিক আরেকটি দিক: completeness আছে বলেই X⁺ অ্যালগরিদম যথেষ্ট। আমাদের F⁺-এর 3ⁿ আকারের তালিকা বানাতে হয় না; একটি বহুপদী সময়ের অ্যালগরিদমই যেকোনো FD-প্রশ্নের নিশ্চিত উত্তর দেয়। তবে MVD আর অন্য নির্ভরতা মেশালে পরিস্থিতি বদলায় — FD আর MVD একসাথে নিলে আরও বড় একটি নিয়ম-সেট লাগে (MVD নিজে পর্ব ৩৭-এর বিষয়)।
মূল উপলব্ধি: তিনটি নিয়ম ছোট হলেও পূর্ণ। এজন্যই FD-র সব হিসাব একটি যান্ত্রিক পদ্ধতিতে নামানো যায় — X⁺ — আর পরীক্ষায় তিনটি নিয়ম দিয়ে যেকোনো সত্য FD প্রমাণ করা সম্ভব।
প্র ০৩Canonical cover একটির বেশি হতে পারে। তাহলে normalization-এর ফলও কি ডিজাইনার-ভেদে আলাদা হবে? এটি কি সমস্যা?
হ্যাঁ, হতে পারে, আর এটি নকশা-প্রক্রিয়ার একটি সৎ সত্য। ১৯৭৬ সালে Philip Bernstein “synthesis” পদ্ধতির প্রস্তাব দেন: একটি minimal cover নিন, একই বাম দিকের FD-গুলো জুড়ুন, প্রতিটি দলের জন্য একটি টেবিল বানান। আজকের পাঠ্যবইয়ের 3NF synthesis অ্যালগরিদম এরই উত্তরসূরি। Cover আলাদা হলে টেবিলের সেটও আলাদা হতে পারে।
একটি উদাহরণ: student_id ↔ nid (দুটি পরস্পরকে নির্ধারণ করে), আর student_id → hall. Cover হতে পারে {student_id → nid, nid → student_id, student_id → hall}, অথবা {student_id → nid, nid → student_id, nid → hall}. প্রথমটিতে hall-এর নির্ভরতা student_id দিয়ে লেখা, দ্বিতীয়টিতে nid দিয়ে। দুটিই গাণিতিকভাবে সমান — একই FD⁺ — আর দুটি ফল-ই 3NF, lossless আর dependency-preserving (পর্ব ৩৭-এ এই গুণগুলো)।
তাহলে কোনটি বেছে নেবেন? এখানে তত্ত্ব থামে আর প্রকৌশল শুরু হয়। স্থির, ছোট, সবসময়-জানা key দিয়ে লেখা cover বাছাই করুন — student_id, nid নয়, কারণ NID থাকে না শিশু বা বিদেশি শিক্ষার্থীর, আর গোপনীয়তার ঝুঁকিও আছে (পর্ব ৬)। অর্থাৎ পছন্দটি গণিত দিয়ে নয়, পর্ব ৬-এর primary key-বাছাইয়ের যুক্তি দিয়ে হয়।
আরেকটি সূক্ষ্মতা: minimal cover বের করার সময় কোন FD আগে পরীক্ষা করবেন, তার ওপরও ফল নির্ভর করে। পরীক্ষায় তাই ভিন্ন সঠিক উত্তর গ্রহণযোগ্য হওয়া উচিত, যদি প্রতিটি সমতুল্য ও minimal হয়। নিজের উত্তর যাচাইয়ের উপায় একটিই: দুই দিকের সমতা আর প্রতিটি FD-র অপরিহার্যতা closure দিয়ে দেখানো।
মূল উপলব্ধি: তত্ত্ব নিশ্চয়তা দেয় সব সম্ভাব্য ফল সমান “সঠিক”; কোনটি “ভালো”, তা ঠিক হয় বাস্তবের key, গোপনীয়তা আর কোয়েরির ধরন দেখে।
প্র ০৪SQL-এ PRIMARY KEY, UNIQUE, FOREIGN KEY আছে, কিন্তু “FUNCTIONAL DEPENDENCY” নামের কোনো constraint নেই। একটি non-key FD ডেটাবেসকে দিয়ে কীভাবে রক্ষা করাবেন?
প্রথমে লক্ষ করুন, key constraint নিজেই একটি FD: UNIQUE(K) মানে K → সব কলাম। DBMS এটি একটি unique index দিয়ে সস্তায় রক্ষা করে — প্রতিটি INSERT-এ শুধু index-এ একটি খোঁজ। কিন্তু enrol_wide-এর course_id → course_title-এর মতো FD, যেখানে course_id key নয়, রক্ষা করতে হলে প্রতিটি নতুন সারির জন্য দেখতে হবে একই course_id-এর অন্য সারিতে title আলাদা কি না।
SQL-92 standard-এ এর একটি সাধারণ উপায় ছিল: CREATE ASSERTION, যেখানে যেকোনো শর্ত লেখা যায়, যেমন “এমন কোনো course_id নেই যার দুটি আলাদা title”. কিন্তু PostgreSQL, MySQL, SQL Server বা Oracle — প্রধান কোনো DBMS এটি বাস্তবায়ন করেনি, কারণ প্রতিটি পরিবর্তনে যেকোনো assertion দক্ষভাবে যাচাই করা কঠিন, বিশেষত একসাথে অনেক transaction চলার সময়।
বাস্তব উপায় তাই তিনটি। এক — normalization: টেবিল ভেঙে courses(course_id, title) বানান, তাহলে FD-টি key-তে পরিণত হয়, আর PRIMARY KEY একাই এটি রক্ষা করে। এটিই normalization-এর গভীরতম যুক্তি: প্রতিটি FD-কে key-constraint-এ রূপান্তর, যাতে DBMS বিনা খরচে পাহারা দেয়। দুই — trigger (পর্ব ২৯): INSERT/UPDATE-এর আগে একটি কোয়েরি চালিয়ে সংঘাত পেলে ত্রুটি। কাজ করে, কিন্তু ধীর, আর একসাথে আসা দুটি transaction দুজনেই “সংঘাত নেই” দেখে ফেলতে পারে, যদি isolation যথেষ্ট কঠোর না হয় (পর্ব ৪৭)। তিন — নিয়মিত অডিট: এই পর্বের GROUP BY কোয়েরি রাতে চালিয়ে সংঘাতের রিপোর্ট — ইচ্ছাকৃত denormalized টেবিলে (পর্ব ৩৮) এটিই প্রচলিত।
এখানে একটি সুন্দর সংযোগ আছে পর্ব ৩৭-এর “dependency preservation”-এর সাথে: ভাঙার পর প্রতিটি FD কোনো একটি টেবিলের ভেতরেই যাচাইযোগ্য থাকা চাই, নইলে আবার join বা trigger লাগবে।
মূল উপলব্ধি: DBMS শুধু key-কে সস্তায় রক্ষা করতে পারে। তাই ভালো ডিজাইন প্রতিটি গুরুত্বপূর্ণ FD-কে কোনো টেবিলের key বানিয়ে দেয় — FD বোঝা মানে আসলে ঠিক করা, কোন টেবিলের key কী হবে।
প্র ০৫পরীক্ষার প্রশ্নে সব candidate key কয়েক লাইনে বের হয়ে যায়। বাস্তবের বড় schema-তেও কি এটি সহজ?
না, আর কারণটি গাণিতিক। প্রথমত, candidate key-র সংখ্যাই বিশাল হতে পারে। R(A₁, B₁, …, Aₙ, Bₙ) নিন, যেখানে প্রতিটি i-এর জন্য Aᵢ → Bᵢ আর Bᵢ → Aᵢ (যেমন রোল ↔ রেজিস্ট্রেশন নম্বর, কোড ↔ নাম — এক-এক সম্পর্ক)। প্রতিটি জোড়া থেকে একটি নিলেই key, তাই 2ⁿ টি candidate key. মাত্র ২০ জোড়ায় দশ লাখের বেশি। যে অ্যালগরিদম সব key তালিকা করে, সে exponential সময় নেবেই, কারণ উত্তরটিই এত বড়।
দ্বিতীয়ত, ছোট প্রশ্নও কঠিন। ১৯৭৮ সালে Lucchesi আর Osborn দেখান, “একটি নির্দিষ্ট attribute prime কি না” — অর্থাৎ কোনো candidate key-তে আছে কি না — প্রশ্নটি NP-complete. এর মানে, যদি না P = NP হয়, বড় schema-র জন্য সাধারণ দ্রুত পদ্ধতি নেই। আর 3NF-এর সংজ্ঞা prime attribute-এর ওপর দাঁড়িয়ে, তাই “এই schema কি 3NF?” যাচাইও সাধারণভাবে কঠিন। মজার বিষয়, BCNF যাচাই (প্রতিটি FD-র বাম দিক superkey কি না) শুধু closure দিয়েই হয়, তাই সহজ — যদিও তার নিজস্ব জটিলতা আছে, পর্ব ৩৭-এ দেখবেন।
তাহলে বাস্তবে কী হয়? প্রথমত, বাস্তবের schema সাধারণত ছোট ছোট টেবিলে ভাগ করা, প্রতিটিতে কয়েকটি FD — সেখানে অংশ ৫-এর শ্রেণিভাগ কৌশল দ্রুত কাজ করে। দ্বিতীয়ত, ডিজাইনাররা FD থেকে key খোঁজেন না, উল্টো পথে যান: ER মডেল থেকে entity আর তাদের identifier আগেই জানা (পর্ব ৭–১০), FD-বিশ্লেষণ শুধু যাচাই আর সূক্ষ্ম ভুল ধরতে। তৃতীয়ত, data profiling টুলগুলো heuristic আর সীমিত আকারের key খোঁজে।
পরীক্ষার প্রশ্ন ছোট রাখা হয় ধারণা যাচাইয়ের জন্য; কিন্তু শ্রেণিভাগ কৌশলের মূল্য ঠিক এখানে — এটি exponential খোঁজকে ব্যবহারিক আকারে নামিয়ে আনে, যদিও সবচেয়ে খারাপ ক্ষেত্রে পারে না।
মূল উপলব্ধি: FD-তত্ত্বের কিছু প্রশ্ন সহজ (X⁺, BCNF-যাচাই), কিছু মৌলিকভাবে কঠিন (সব key, prime-যাচাই)। কোনটি কোন দলে, জানা থাকলে বড় সিস্টেমে কোথায় হাতে-কলমে বিচার লাগবে, তা আগেই বোঝা যায়।
অনুশীলন
খাতা-কলম নিন। একটি হাতে closure, একটি কোয়েরি, একটি key খোঁজা, একটি canonical cover.
-
হাতে চালান (trace): R(A, B, C, D, E, G), F = {AB → C, C → A, BC → D, D → EG, BE → C}. প্রথমে (BD)⁺ বের করুন — প্রতিটি pass-এ FD-গুলো এই ক্রমে দেখবেন, আর লিখবেন কোন pass-এ কোন attribute এল। তারপর D⁺. BD কি superkey?
(BD)⁺, pass ১: শুরু {B, D}. AB → C — A নেই, চলে না। C → A — C নেই। BC → D — C নেই। D → EG — চলে: {B, D, E, G}. BE → C — B, E দুটিই আছে: {B, C, D, E, G}.
Pass ২: AB → C — A এখনো নেই। C → A — এবার C আছে: {A, B, C, D, E, G}. বাকিগুলো নতুন কিছু দেয় না। Pass ৩: কিছুই বাড়ে না, থামুন। (BD)⁺ = R — BD superkey.
D⁺ = {D, E, G} (শুধু D → EG). খেয়াল করুন, pass ১-এ C → A চলেনি কারণ তখনো C আসেনি — তালিকার ক্রমে পেছনের FD আগের FD-কে “জাগাতে” পারে, তাই একটি পূর্ণ ফাঁকা pass না হওয়া পর্যন্ত থামা যায় না। B⁺ = {B} আর D⁺-এ B নেই, তাই BD minimal: এটি একটি candidate key.
-
কোয়েরি লিখুন: db-shop-এর products টেবিলে (product_id, name, category_id, price, stock) একজন দাবি করছেন “একই শ্রেণির সব পণ্যের দাম সমান” — অর্থাৎ category_id → price. এই দাবির লঙ্ঘন খোঁজার কোয়েরি লিখুন, যা প্রতিটি লঙ্ঘনকারী শ্রেণির আলাদা দামগুলোও দেখায়। উপরের db-shop এডিটরে চালিয়ে ফল মিলিয়ে নিন।
SELECT category_id, COUNT(DISTINCT price) AS n, GROUP_CONCAT(DISTINCT price) AS prices FROM products GROUP BY category_id HAVING COUNT(DISTINCT price) > 1;ফল চারটি সারি:
1 | 4 | 15000.0,1200.0,,1800.0,3500.0 2 | 2 | 650.0,550.0,3 | 2 | 2200.0,4500.0,4 | 2 | 420.0,260.0. চারটি শ্রেণিই দাবি ভাঙে, তাই category_id → price নিশ্চিতভাবে FD নয়। (দাবিটি অর্থের দিক থেকেও অদ্ভুত — মোবাইল আর হেডফোন একই দামের হবে কেন?) price-এ NULL হতে পারে না (NOT NULL), তাই এখানে COUNT(DISTINCT) নিরাপদ। -
Key খুঁজুন: R(A, B, C, D, E), F = {A → B, BC → E, ED → A}. সব candidate key, prime আর non-prime attribute বের করুন।
শ্রেণিভাগ: ডান দিকে আসে B, E, A. কোনো ডান দিকে নেই C আর D — এরা প্রতিটি key-তে থাকবে। (CD)⁺ = {C, D} — BC → E-র জন্য B লাগে, ED → A-র জন্য E লাগে; কিছুই চলে না।
একটি করে যোগ: ACD: A → B, তারপর BC → E — পুরো R ✓. BCD: BC → E, ED → A — পুরো R ✓. CDE: ED → A, A → B — পুরো R ✓. তিনটিই minimal, কারণ core CD একা যথেষ্ট নয় আর তিনটির প্রতিটিতে CD ছাড়া একটিই attribute.
উত্তর: candidate key ACD, BCD, CDE. পাঁচটি attribute-ই কোনো না কোনো key-তে আছে — সবাই prime, non-prime নেই। (এই উদাহরণটি পর্ব ৩৬-৩৭-এ কাজে লাগবে: এখানে কোনো non-prime নেই বলে relation-টি আপনাআপনি 3NF, যদিও BCNF নয়।)
-
Canonical cover: R(A, B, C, D), F = {A → BC, B → C, AB → D, D → BC}. ধাপে ধাপে canonical cover বের করুন।
ধাপ ১: A → B, A → C, B → C, AB → D, D → B, D → C.
ধাপ ২: AB → D: A⁺ = {A, B, C, D} (A → B, তারপর AB → D) — D আছে, তাই B বাড়তি; হয় A → D. এখন {A → B, A → C, B → C, A → D, D → B, D → C}.
ধাপ ৩: A → B: বাকিতে A⁺ = {A, C, D, B} (A → D → B) — বাদ. A → C: বাকি {B → C, A → D, D → B, D → C}-এ A⁺ = {A, D, B, C} — বাদ. B → C: বাকি {A → D, D → B, D → C}-এ B⁺ = {B} — থাকে। A → D: বাকিতে A⁺ = {A} — থাকে। D → B: বাকি {B → C, A → D, D → C}-এ D⁺ = {D, C} — থাকে। D → C: বাকি {B → C, A → D, D → B}-এ D⁺ = {D, B, C} — বাদ.
উত্তর: Fc = {A → D, D → B, B → C}. যাচাই: A⁺ = {A, B, C, D} তাই A → BC আর AB → D বের হয়; D⁺ = {B, C, D} তাই D → BC বের হয়। (ভিন্ন ক্রমে পরীক্ষা করলেও এখানে একই ফল আসে, কিন্তু সবসময় আসবে এমন নয়।)
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি FD-সেটের canonical cover বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- প্রতিটি FD-র ডান দিক একক attribute-এ ভাঙা
- প্রতিটি FD-র বাম দিকের বাড়তি attribute closure দিয়ে খুঁজে বাদ দেওয়া
- প্রতিটি FD বাদ দিয়ে বাকিদের closure দেখে redundant FD বাদ দেওয়া
- একই বাম দিকের FD-গুলো union দিয়ে একটিতে জোড়া
- মূল সেট আর ফলের সমতা দুই দিকে closure দিয়ে যাচাই
নিচের কোনটি Armstrong-এর তিনটি মূল axiom-এর একটি নয়, বরং তাদের থেকে প্রমাণ করা একটি উপ-নিয়ম?
- Reflexivity
- Augmentation
- Union
- Transitivity
R(A, B, C, D, E)-এ F = {A → B, B → C, CD → E}. (AD)⁺ কত?
- {A, B, C, D}
- {A, B, C, D, E}
- {A, B, D}
- {A, D}
একটি টেবিলে SELECT city FROM t GROUP BY city HAVING COUNT(DISTINCT zip) > 1; চালিয়ে কোনো সারি পাওয়া গেল না, আর zip কলামে কোনো NULL নেই। কোন সিদ্ধান্তটি ঠিক?
- city → zip একটি FD, তাই schema-য় লেখা যায়
- city → zip কখনো FD হতে পারে না
- zip → city একটি FD
- এই ডেটায় city → zip খাটছে, কিন্তু এটি নিয়ম কি না ডেটা থেকে বলা যায় না
R(A, B, C, D)-এ F = {AB → C, C → D, D → A}. R-এর candidate key কয়টি?
- ৩টি — AB, BC আর BD
- ১টি — AB
- ১টি — B
- ২টি — AB আর ABC
নিচের কোন FD-টি trivial?
- AB → C
- AB → A
- A → AB
- A → B
F = {A → B, B → C, A → C}-এর canonical cover কোনটি?
- {A → B, B → C, A → C}
- {A → B, A → C}
- {A → B, B → C}
- {A → C, B → C}
একটি টেবিলে (student_id, course_id, course_title) আছে, key (student_id, course_id), আর নিয়ম course_id → course_title. কোনো কোর্সের একমাত্র শিক্ষার্থীর সারি মুছলে সেই কোর্সের নামও হারিয়ে যায়। এটি কোন ধরনের সমস্যা?
- Insert anomaly
- Update anomaly
- Referential integrity-র লঙ্ঘন
- Delete anomaly
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- এক টেবিলে একাধিক বিষয় রাখলে insert, update আর delete anomaly হয়; মূল কারণ এমন FD, যার বাম দিক key নয়।
- X → Y: দুটি সারি X-এ মিললে Y-তে মিলবে — সব বৈধ instance-এ। ডেটা শুধু খণ্ডন করে:
GROUP BY X HAVING COUNT(DISTINCT Y) > 1(NULL থাকলে DISTINCT-জোড়া গুনুন)। - Armstrong: reflexivity, augmentation, transitivity — sound ও complete; union, decomposition (শুধু ডান দিকে), pseudo-transitivity এদের থেকে আসে।
- X⁺ দিয়ে সব: X → Y ⇔ Y ⊆ X⁺; X⁺ = R হলে superkey. শুধু-বামের attribute প্রতিটি key-তে, শুধু-ডানের কোনোটিতে নয়; key-র সদস্যরা prime.
- Canonical cover: ডান দিক ভাঙা → বাম দিক ছাঁটা → redundant FD বাদ — এই ক্রমে; ফল সবসময় একটিই নয়, কিন্তু প্রতিটি মূল সেটের সমতুল্য।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৬ · Normalization — 1NF, 2NF আর 3NFপরবর্তী পর্বFD দিয়ে টেবিল ভেঙে anomaly দূর করা।
- পর্ব ০৬ · Key আর integrity constraintঝালিয়ে নিনSuperkey আর candidate key-র মূল ধারণা, যা এখানে FD দিয়ে আবার এল।
- ডেটাবেস (DBMS) ও SQL · কোর্স হোম৬৭টি পর্বপুরো কোর্সের রোডম্যাপ — ১০টি মডিউল।
- DSA · অ্যালগরিদমের সঠিকতার প্রমাণগভীরে যানInduction আর loop invariant — closure অ্যালগরিদমের প্রমাণ যে কৌশলে লেখা।
- Python · SQLite ডেটাবেসহাতে-কলমেনিজের ডেটায় FD-লঙ্ঘন খোঁজার কোয়েরি Python থেকে চালান।