Normalization — 1NF, 2NF, 3NF

নরমালাইজেশন — 1NF, 2NF, 3NF

Read: ~35 min Hard 16 practice problems Live SQLite runner

1. Why Normalize? — A Bad Table Tells the Truth

Normalization is the discipline of taking a messy, repetitive table and breaking it into smaller, cleaner tables that store every fact in exactly one place. It is not a religious ritual — it is a response to three concrete problems that bad schemas create: insertion anomalies, update anomalies, and deletion anomalies.

Normalization হলো একটি এলোমেলো, পুনরাবৃত্তিমূলক টেবিলকে ছোট ছোট পরিচ্ছন্ন টেবিলে ভাঙার শৃঙ্খলা — যেখানে প্রতিটি তথ্য ঠিক একবার সংরক্ষিত থাকে। এটি কোনো ধর্মীয় আচার নয়, বরং বাস্তব তিনটি সমস্যার সমাধান: insertion anomaly (নতুন তথ্য যোগ করতে গিয়ে আটকে যাওয়া), update anomaly (একই তথ্য একাধিক জায়গায় বদলাতে হওয়া), এবং deletion anomaly (একটি সারি মুছতে গিয়ে অন্য তথ্যও হারানো)।

In this module we take a real e-commerce table — the kind you might first sketch on a napkin — and walk it through three normal forms until every anomaly is gone.

2. The Broken Table — Daraz-style Orders

Imagine an e-commerce site like Daraz. A junior engineer ships v1 of the schema with a single orders table. Every order line lives in one row. It looks fine until customers actually start placing orders.

ধরা যাক, Daraz-এর মতো একটি e-commerce সাইটে একজন junior engineer প্রথম version-এ একটিমাত্র orders টেবিল বানিয়ে ফেলল — যেখানে প্রতিটি অর্ডার লাইনের সব তথ্য একটি সারিতে থাকে। দেখতে ভালো লাগলেও বাস্তব ব্যবহারে এটি ভেঙে পড়ে।
v1-bad-table.sql
-- The "everything in one place" anti-pattern
CREATE TABLE orders_v1 (
    order_id        INTEGER,
    customer_name   TEXT,
    customer_phone  TEXT,
    customer_city   TEXT,
    products        TEXT,   -- "Phone, Charger, Cover"  ← multiple values!
    product_prices  TEXT,   -- "25000, 500, 300"
    category        TEXT,   -- depends on product, not order
    category_manager TEXT   -- depends on category, not order
);

INSERT INTO orders_v1 VALUES
 (1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka',
  'Phone, Charger', '25000, 500', 'Electronics', 'Mr. Karim'),
 (2, 'Sadia Akter', '018xxxxxxxx', 'Chittagong',
  'Book',            '450',         'Books',        'Mr. Rahim');

SELECT * FROM orders_v1;
Three problems in this single table
AnomalyConcrete failureবাংলায়
InsertionYou cannot add a new product (e.g., "Headphone") until at least one customer orders it.কেউ অর্ডার না দেওয়া পর্যন্ত নতুন পণ্য যোগ করা যাচ্ছে না।
UpdateIf "Mr. Karim" gets replaced as Electronics manager, you must update every Electronics row.একটি ম্যানেজার বদলালে শত শত সারি update করতে হবে।
DeletionDeleting Sadia's order also deletes the only record that Books exists with manager Rahim.একটি অর্ডার মুছলে Books category-র তথ্যও হারিয়ে যাচ্ছে।

3. First Normal Form (1NF) — Atomicity

Rule: Every cell must contain a single, atomic value. No lists, no comma-separated strings, no JSON arrays squeezed into a TEXT column. Every row must be uniquely identifiable, and the order of rows and columns must not carry meaning.

1NF-এর নিয়ম: প্রতিটি cell-এ একটিমাত্র atomic মান থাকবে। কমা দিয়ে আলাদা করা list, JSON array কিংবা একই কলামে একাধিক মান — কোনোটিই allowed নয়। প্রতিটি সারি unique হতে হবে এবং সারি বা কলামের ক্রম-এর মাধ্যমে কোনো অর্থ প্রকাশ পাবে না।
Before — multi-valued order_id=1, products="Phone, Charger" prices="25000, 500" After — atomic rows order_id=1, product="Phone", price=25000 order_id=1, product="Charger", price=500 order_id=2, product="Book", price=450 Composite key: (order_id, product) প্রতিটি সারি unique হয় (order_id + product)-এর সমন্বয়ে। Figure 26.1 — 1NF আনতে হলে multi-valued কলামকে আলাদা সারিতে ভাঙতে হবে।
v2-1nf.sql
-- Step 1: split multi-valued columns into separate rows.
CREATE TABLE orders_1nf (
    order_id        INTEGER,
    customer_name   TEXT,
    customer_phone  TEXT,
    customer_city   TEXT,
    product         TEXT,
    price           INTEGER,
    category        TEXT,
    category_manager TEXT,
    PRIMARY KEY (order_id, product)
);

INSERT INTO orders_1nf VALUES
 (1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka',      'Phone',   25000, 'Electronics', 'Mr. Karim'),
 (1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka',      'Charger', 500,   'Electronics', 'Mr. Karim'),
 (2, 'Sadia Akter',  '018xxxxxxxx', 'Chittagong', 'Book',    450,   'Books',       'Mr. Rahim');

SELECT * FROM orders_1nf;
Heuristic. If you ever need to write split(), LIKE '%,%' or JSON_EXTRACT just to query a column, the table is not in 1NF.

যদি একটি কলামের ভেতর থেকে split করে মান বের করতে হয়, ধরে নিন টেবিলটি 1NF-এ নেই।

4. Second Normal Form (2NF) — No Partial Dependencies

We have a composite primary key (order_id, product). Partial dependency means: some non-key column depends on only part of that key, not the whole thing. In orders_1nf, customer_name, customer_phone and customer_city depend only on order_id — not on the product. Similarly, category depends only on product.

যখন কোনো টেবিলের primary key composite (একাধিক কলাম মিলে), তখন partial dependency বলতে বোঝায় — কোনো non-key কলাম সেই key-এর শুধু একটি অংশের ওপর নির্ভর করছে, পুরো key-এর ওপর নয়। এখানে customer-এর তথ্য শুধু order_id-র ওপর, আর category শুধু product-এর ওপর নির্ভর করছে — পুরো key (order_id, product) লাগছে না।

Rule: A table is in 2NF if it is in 1NF and every non-key column depends on the entire primary key. To fix it, we split the table.

v3-2nf.sql
-- Step 2: split into customers, products, order_items.
CREATE TABLE customers (
    order_id       INTEGER PRIMARY KEY,
    customer_name  TEXT,
    customer_phone TEXT,
    customer_city  TEXT
);

CREATE TABLE products (
    product          TEXT PRIMARY KEY,
    price            INTEGER,
    category         TEXT,
    category_manager TEXT
);

CREATE TABLE order_items (
    order_id  INTEGER,
    product   TEXT,
    PRIMARY KEY (order_id, product),
    FOREIGN KEY (order_id) REFERENCES customers(order_id),
    FOREIGN KEY (product)  REFERENCES products(product)
);

INSERT INTO customers VALUES
 (1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka'),
 (2, 'Sadia Akter',  '018xxxxxxxx', 'Chittagong');

INSERT INTO products VALUES
 ('Phone',   25000, 'Electronics', 'Mr. Karim'),
 ('Charger', 500,   'Electronics', 'Mr. Karim'),
 ('Book',    450,   'Books',       'Mr. Rahim');

INSERT INTO order_items VALUES
 (1, 'Phone'), (1, 'Charger'), (2, 'Book');

SELECT c.customer_name, p.product, p.price
FROM order_items oi
JOIN customers c ON c.order_id = oi.order_id
JOIN products  p ON p.product  = oi.product;

Now adding a new product no longer requires an order, and updating a customer's phone number happens in exactly one place. But one anomaly remains — the manager.

5. Third Normal Form (3NF) — No Transitive Dependencies

In products, the primary key is product. We have: product → category, and then category → category_manager. The manager depends on the key only via the category. This indirect chain is a transitive dependency and it must go.

product থেকে category বের হচ্ছে, আবার category থেকে category_manager বের হচ্ছে। অর্থাৎ manager-টি product-এর ওপর সরাসরি নির্ভর করছে না, বরং category-র মাধ্যমে indirect-ভাবে নির্ভর করছে — এটিই transitive dependency। 3NF-এ এই indirect chain ভাঙতে হয়।

Rule: A table is in 3NF if it is in 2NF and no non-key column transitively depends on the primary key. Codd's clean phrasing: "every non-key attribute depends on the key, the whole key, and nothing but the key — so help me Codd."

v4-3nf.sql
-- Step 3: extract the category → manager chain into its own table.
CREATE TABLE categories (
    category         TEXT PRIMARY KEY,
    category_manager TEXT
);

CREATE TABLE products (
    product  TEXT PRIMARY KEY,
    price    INTEGER,
    category TEXT,
    FOREIGN KEY (category) REFERENCES categories(category)
);

INSERT INTO categories VALUES
 ('Electronics', 'Mr. Karim'),
 ('Books',       'Mr. Rahim');

INSERT INTO products VALUES
 ('Phone',   25000, 'Electronics'),
 ('Charger', 500,   'Electronics'),
 ('Book',    450,   'Books');

-- Now changing a manager touches exactly one row:
UPDATE categories SET category_manager = 'Ms. Tahmina' WHERE category = 'Electronics';

SELECT p.product, p.category, c.category_manager
FROM products p
JOIN categories c ON c.category = p.category;
categories category (PK) category_manager products product (PK) price category (FK) customers · order_items order_id (PK) customer_name customer_phone FK Each fact lives in exactly one place — clean 3NF. Figure 26.2 — 3NF-এ পৌঁছানোর পর প্রতিটি তথ্য ঠিক একটি টেবিলেই থাকে।

6. When to Stop — 3NF Is Usually Enough

Higher normal forms (BCNF, 4NF, 5NF) exist and we cover them in the next module. But for almost every OLTP application — Daraz, Pathao, Foodpanda, a university registration system — 3NF is the practical sweet spot. It eliminates the three anomalies, keeps joins manageable, and does not over-fragment the schema.

উচ্চতর normal form (BCNF, 4NF, 5NF) আছে এবং পরবর্তী module-এ সেগুলো শিখব। কিন্তু বাস্তবে অধিকাংশ OLTP system-এর জন্য — Daraz, Pathao, Foodpanda কিংবা university registration — 3NF-ই যথেষ্ট। এটি তিনটি anomaly দূর করে, join-ও খুব বেশি বাড়ে না, আর schema অতিরিক্ত খণ্ডিত হয় না।

✅ Stop normalizing when (কখন থামবেন)

  • You are in 3NF and no anomalies remain.
  • Further splits would force a 4-way join for every query.
  • Read performance is a written, measured requirement.
  • The team can reason about every join in their head.

⚠️ Keep going when (আরও কাটতে হবে)

  • You see real update anomalies on a non-trivial column.
  • An attribute belongs to multiple independent multi-valued facts.
  • You are designing a financial or audit-critical system.
  • Tests reveal hidden functional dependencies you missed.
The mantra. "The key, the whole key, and nothing but the key." — every non-key attribute should depend on the primary key, the entire primary key, and only the primary key.

"Key, পুরো key, এবং একমাত্র key" — প্রতিটি non-key attribute শুধু primary key-এর ওপর, পুরো primary key-এর ওপর, এবং কেবলমাত্র primary key-এর ওপর নির্ভর করবে।

7. Glossary (শব্দকোষ)

TermMeaningবাংলায়
Functional dependencyIf you know X, you know Y. Written X → Y.X জানলে Y নিশ্চিতভাবে জানা যায়।
Atomic valueA single, indivisible value in a cell.একটি cell-এ একটিমাত্র অবিভাজ্য মান।
Composite keyA primary key made of two or more columns.একাধিক কলাম মিলে গঠিত primary key।
Partial dependencyA non-key column depending on only part of a composite key.composite key-এর শুধু একটি অংশের ওপর নির্ভরশীলতা।
Transitive dependencyA → B and B → C, so A → C indirectly.মাধ্যম ব্যবহার করে indirect নির্ভরশীলতা।
AnomalyAn update, insert or delete that produces inconsistent data.data inconsistent করে ফেলা insert / update / delete।

8. Practice Problems

Sixteen problems below. Most have a runnable SQLite answer — try them yourself first, then check.

নিচে ১৬টি প্রশ্ন আছে। বেশিরভাগ প্রশ্নের উত্তরে চালু-করার-যোগ্য SQLite কোড দেওয়া আছে। নিজে চেষ্টা করুন, তারপর মিলিয়ে নিন।
  1. Identify the 1NF violation in a column called tags that stores 'sale,featured,new'.
    যদি একটি কলামে 'sale,featured,new'-এর মতো মান থাকে, সেটি 1NF লঙ্ঘন কেন?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: The cell holds multiple values. To filter by tag you must use LIKE or split — proof of non-atomicity. Fix by creating a product_tags(product_id, tag) child table.

    একই cell-এ একাধিক মান আছে। filter করতে হলে split বা LIKE লাগে — অর্থাৎ atomic নয়। সমাধান: একটি child table product_tags(product_id, tag)।

  2. Convert the multi-valued tags field into a 1NF table and run a query that finds all products tagged "sale".
    multi-valued tags-কে 1NF টেবিলে রূপান্তর করুন এবং "sale" tag-যুক্ত পণ্য বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans2.sql
    CREATE TABLE product_tags (
        product_id INTEGER,
        tag        TEXT,
        PRIMARY KEY (product_id, tag)
    );
    INSERT INTO product_tags VALUES
     (1, 'sale'), (1, 'featured'),
     (2, 'new'),
     (3, 'sale');
    
    SELECT p.name
    FROM products p
    JOIN product_tags t ON t.product_id = p.product_id
    WHERE t.tag = 'sale';
  3. Given enrolment(student_id, course_id, student_name, course_title) with PK (student_id, course_id), list every partial dependency.
    উপরের টেবিলে কোন কোন partial dependency আছে?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: student_id → student_name (depends on only part of the key) and course_id → course_title. Both are partial dependencies — the table is not in 2NF.

    student_id → student_name এবং course_id → course_title — দুটিই partial dependency, তাই টেবিলটি 2NF-এ নেই।

  4. Decompose the enrolment table above into 2NF and verify with a SQL join.
    উপরের টেবিলকে 2NF-এ ভাঙুন এবং join দিয়ে যাচাই করুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans4.sql
    CREATE TABLE students  (student_id INTEGER PRIMARY KEY, student_name TEXT);
    CREATE TABLE courses   (course_id  INTEGER PRIMARY KEY, course_title TEXT);
    CREATE TABLE enrolment (
        student_id INTEGER,
        course_id  INTEGER,
        PRIMARY KEY (student_id, course_id)
    );
    INSERT INTO students VALUES (1, 'Arif'), (2, 'Sadia');
    INSERT INTO courses  VALUES (101, 'DBMS'), (102, 'OS');
    INSERT INTO enrolment VALUES (1, 101), (1, 102), (2, 101);
    
    SELECT s.student_name, c.course_title
    FROM enrolment e
    JOIN students s ON s.student_id = e.student_id
    JOIN courses  c ON c.course_id  = e.course_id;
  5. Find the transitive dependency in employees(emp_id, dept_id, dept_name, dept_location) with PK emp_id.
    উপরের টেবিলে transitive dependency কোনটি?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: emp_id → dept_id → dept_name and emp_id → dept_id → dept_location. Both dept_name and dept_location depend on the key transitively through dept_id. Move them to a departments table.

    dept_name এবং dept_location — দুটিই dept_id-র মাধ্যমে indirect-ভাবে নির্ভরশীল। এদের একটি আলাদা departments টেবিলে সরাতে হবে।

  6. Convert the employees table to 3NF and write a query that lists every employee with their department location.
    employees টেবিলকে 3NF-এ আনুন এবং প্রতিটি কর্মীর department location দেখান।
    ✨ Show Answer (উত্তর দেখুন)
    ans6.sql
    CREATE TABLE departments (
        dept_id       INTEGER PRIMARY KEY,
        dept_name     TEXT,
        dept_location TEXT
    );
    CREATE TABLE employees (
        emp_id  INTEGER PRIMARY KEY,
        name    TEXT,
        dept_id INTEGER REFERENCES departments(dept_id)
    );
    INSERT INTO departments VALUES (10, 'Engineering', 'Dhaka'), (20, 'Sales', 'Sylhet');
    INSERT INTO employees   VALUES (1, 'Arif', 10), (2, 'Sadia', 20);
    
    SELECT e.name, d.dept_name, d.dept_location
    FROM employees e
    JOIN departments d ON d.dept_id = e.dept_id;
  7. A library has book(isbn, title, author_name, author_country) with PK isbn. Is it in 3NF? Justify.
    উপরের library টেবিল কি 3NF-এ আছে? ব্যাখ্যা করুন।
    ✨ Show Answer (উত্তর দেখুন)

    Answer: No. author_country depends on author_name, not on isbn. Transitive dependency. Split into authors(author_id, name, country) and reference it from book.

    না। author_country নির্ভর করে author_name-এর ওপর, isbn-এর ওপর নয়। তাই এটি 3NF-এ নেই — author-কে আলাদা টেবিলে আনতে হবে।

  8. Show an insertion anomaly that exists in 1NF but disappears in 2NF (use the enrolment example).
    উপরের enrolment উদাহরণে এমন একটি insertion anomaly দেখান যা 1NF-এ আছে কিন্তু 2NF-এ নেই।
    ✨ Show Answer (উত্তর দেখুন)

    Answer: In 1NF you cannot insert a new course (e.g. "Networks") until at least one student enrols, because (student_id, course_id) is the primary key — a row needs both. In 2NF you simply INSERT INTO courses with no enrolments, no anomaly.

    1NF-এ কোনো ছাত্র enrol না করা পর্যন্ত নতুন course যোগ করা যায় না, কারণ key-এ student_id লাগবেই। 2NF-এ courses টেবিলে সরাসরি যোগ করা যায়।

  9. Write SQL that demonstrates an update anomaly on the un-normalized orders_v1 table: change the manager and observe the duplication.
    un-normalized টেবিলে manager update করার সময় anomaly প্রদর্শন করুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans9.sql
    CREATE TABLE orders_v1 (
        order_id INTEGER, product TEXT,
        category TEXT, category_manager TEXT
    );
    INSERT INTO orders_v1 VALUES
     (1, 'Phone',   'Electronics', 'Mr. Karim'),
     (2, 'Charger', 'Electronics', 'Mr. Karim'),
     (3, 'TV',      'Electronics', 'Mr. Karim');
    
    -- Forget even one row and the data is now inconsistent:
    UPDATE orders_v1 SET category_manager = 'Ms. Tahmina'
    WHERE order_id < 3;
    
    SELECT * FROM orders_v1;

    Notice the contradiction: order 1 and 2 say "Ms. Tahmina", order 3 still says "Mr. Karim".

  10. Given book_loan(loan_id, member_id, member_name, isbn, title, due_date) with PK loan_id, list every functional dependency.
    উপরের লাইব্রেরি টেবিলে কোন কোন functional dependency আছে?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: loan_id → member_id, isbn, due_date; member_id → member_name; isbn → title. The last two are transitive — table is in 2NF but not 3NF.

  11. Decompose the book_loan table into 3NF.
    উপরের book_loan-কে 3NF-এ আনুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans11.sql
    CREATE TABLE members (member_id INTEGER PRIMARY KEY, member_name TEXT);
    CREATE TABLE books   (isbn      TEXT    PRIMARY KEY, title       TEXT);
    CREATE TABLE loans (
        loan_id   INTEGER PRIMARY KEY,
        member_id INTEGER REFERENCES members(member_id),
        isbn      TEXT    REFERENCES books(isbn),
        due_date  TEXT
    );
    INSERT INTO members VALUES (1, 'Arif');
    INSERT INTO books   VALUES ('978-1', 'C Programming');
    INSERT INTO loans   VALUES (100, 1, '978-1', '2026-06-01');
    SELECT * FROM loans;
  12. Why is "phone number" sometimes a 1NF violation even though it is a single string?
    phone number কখন 1NF লঙ্ঘন হতে পারে যদিও সেটি একটি single string?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: If a customer can have multiple phone numbers and you store them as "017xxx,018xxx", that single string is multi-valued — a 1NF violation. Either store one phone per customer column (limited) or, better, create a customer_phones(customer_id, phone) child table.

  13. Is invoice(invoice_id, total_amount, line_count) in 3NF if line_count is computed from invoice lines?
    উপরের invoice টেবিলে line_count অন্য টেবিল থেকে গণনা করা — এটি কি 3NF-এ আছে?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: Strictly speaking, yes — there is no transitive dependency among the three columns. But it stores derivable data, which is a different code-smell (denormalization for performance). Pure normalization theory has no quarrel here; in module 28 we discuss when you might keep such a column on purpose.

  14. Insert two rows that violate 3NF on the products table from §4 and explain.
    §4-এর products টেবিলে এমন দুটি সারি দিন যা 3NF লঙ্ঘন করবে।
    ✨ Show Answer (উত্তর দেখুন)

    Answer: Insert ('Laptop', 60000, 'Electronics', 'Mr. Karim') and ('Tablet', 18000, 'Electronics', 'Ms. Different'). Now two products in the same Electronics category disagree on the manager — a direct contradiction caused by the transitive dependency category → category_manager not being isolated.

  15. Take the bKash transaction sheet tx(tx_id, sender_phone, sender_name, receiver_phone, receiver_name, amount) and bring it to 3NF.
    উপরের bKash সারণিকে 3NF-এ আনুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans15.sql
    CREATE TABLE users (
        phone TEXT PRIMARY KEY,
        name  TEXT
    );
    CREATE TABLE tx (
        tx_id           INTEGER PRIMARY KEY,
        sender_phone    TEXT REFERENCES users(phone),
        receiver_phone  TEXT REFERENCES users(phone),
        amount          INTEGER
    );
    INSERT INTO users VALUES ('017', 'Arif'), ('018', 'Sadia');
    INSERT INTO tx    VALUES (1, '017', '018', 1500);
    
    SELECT t.tx_id, s.name AS sender, r.name AS receiver, t.amount
    FROM tx t
    JOIN users s ON s.phone = t.sender_phone
    JOIN users r ON r.phone = t.receiver_phone;
  16. When is keeping a "computed" or "duplicated" column actually correct? Give one good and one bad reason.
    কখন duplicate column রাখা ঠিক হতে পারে — একটি ভালো এবং একটি খারাপ কারণ দিন।
    ✨ Show Answer (উত্তর দেখুন)

    Good reason: a frozen historical value — e.g. order_lines.unit_price_at_purchase. The product price will change tomorrow but the invoice must keep the price at the moment of sale. Storing it is correct, not denormalization.

    Bad reason: "It is faster to read." Without measuring, this is premature optimization that introduces every anomaly we just spent an hour eliminating.

Summary — Module 26

We took a single bloated orders table through three normal forms. 1NF made every cell atomic. 2NF removed partial dependencies on a composite key. 3NF killed transitive dependencies. The result: every fact lives in exactly one place, and the three classic anomalies — insertion, update, deletion — disappear. For most production OLTP systems, 3NF is exactly where you should stop.

একটি বিশৃঙ্খল orders টেবিলকে আমরা তিনটি normal form-এর মধ্য দিয়ে নিয়ে গেছি। 1NF প্রতিটি cell-কে atomic করেছে, 2NF partial dependency দূর করেছে, এবং 3NF transitive dependency বাদ দিয়েছে। ফলাফল — প্রতিটি তথ্য ঠিক একটি জায়গায় থাকে এবং insert/update/delete-এর তিনটি anomaly আর থাকে না। অধিকাংশ production OLTP system-এর জন্য 3NF-ই সঠিক বিন্দু।

Next Module → BCNF, 4NF, 5NF — যেখানে 3NF যথেষ্ট নয়।