Self join chỉ là một phép join có alias
Self join không có gì đặc biệt. Đó là một JOIN bình thường mà hai vế tình cờ là cùng một bảng. Mấu chốt là SQLite cần cách phân biệt hai bản sao đó, nên bạn đặt cho mỗi bản sao một alias.
Bạn sẽ cần đến self join mỗi khi một hàng trong bảng tham chiếu đến một hàng khác trong cùng bảng. Trường hợp kinh điển: bảng employees, trong đó mỗi hàng có cột manager_id trỏ đến một nhân viên khác:
Ada không có quản lý. Boris và Cleo báo cáo cho Ada. Diego và Esme báo cáo cho Boris. Toàn bộ quan hệ nằm gọn trong một bảng, và đó chính là chỗ self join phát huy tác dụng.
Dạng cơ bản
Để ghép mỗi nhân viên với tên quản lý của họ, hãy join employees với chính nó. Một bản sao đóng vai "nhân viên", bản còn lại đóng vai "quản lý":
Hãy đọc truy vấn này như hai bảng tình cờ dùng chung nơi lưu trữ. e là hàng nhân viên; m là hàng quản lý. Điều kiện join e.manager_id = m.id xếp chúng thẳng hàng: với mỗi nhân viên, tìm hàng trong m có id trùng với manager_id của nhân viên đó.
Để ý rằng Ada không xuất hiện. manager_id của Ada là NULL, và INNER JOIN bỏ đi các hàng không khớp.
Giữ lại các hàng không khớp: LEFT JOIN
Nếu bạn muốn mọi người đều có trong kết quả, kể cả những người không có quản lý, hãy đổi sang LEFT JOIN:
Giờ Ada xuất hiện với NULL ở cột quản lý. Cơ chế self join vẫn vậy, chỉ là kiểu join làm đúng việc LEFT JOIN luôn làm: giữ mọi hàng ở vế trái, để trống ở chỗ không có hàng khớp.
Đây là dạng bạn thường cần khi hiển thị danh sách người. "Không có quản lý" cũng là thông tin; bỏ mất hàng đó thì không.
Alias là bắt buộc
Thử join mà không có alias, SQLite sẽ không hiểu bạn muốn gì:
SELECT name, manager_id FROM employees JOIN employees ON manager_id = id;
-- Error: ambiguous column name: name
Mỗi cột xuất hiện hai lần, mỗi lần từ một bản sao của bảng, và SQLite không thể chọn. Alias giải quyết chuyện này bằng cách đặt cho mỗi bản sao một tên riêng. Hãy chọn alias mô tả vai trò của hàng, không phải tên bảng:
evàmcho nhân viên/quản lý.parentvàchildcho cấu trúc phân cấp.avàbkhi bạn so sánh các cặp bất kỳ.
Alias chính là lý do khiến một self join dễ đọc.
Tìm các cặp trong cùng một bảng
Self join không chỉ dành cho cấu trúc phân cấp. Bất cứ khi nào bạn muốn so sánh các hàng trong cùng một bảng, mẫu này đều dùng được. Dưới đây là danh sách sản phẩm, và ta muốn tìm mọi cặp có cùng giá:
Có hai điều cần chú ý. Thứ nhất, a.price = b.price là điều kiện ghép thật sự. Thứ hai, a.id < b.id ngăn truy vấn trả về mỗi cặp hai lần (một lần là (Mug, Notebook), lần nữa là (Notebook, Mug)) và ngăn ghép mỗi hàng với chính nó. Mẹo dùng < này rất đáng nhớ: nó xuất hiện bất cứ khi nào bạn liệt kê các cặp.
Đi lên hai cấp
Một self join xử lý được một bước trong cấu trúc phân cấp. Muốn biết quản lý của quản lý mỗi nhân viên? Hãy join ba lần:
Mỗi alias mới đại diện cho một cấp cao hơn trên cây. Cách này ổn với hai hoặc ba bước, nhưng nhanh chóng bế tắc: bạn phải biết độ sâu của cấu trúc phân cấp ngay khi viết truy vấn, và thêm một phép join cho mỗi cấp. Đó chính là bức tường mà CTE đệ quy được tạo ra để phá vỡ.
Khi nào không nên dùng self join
Self join là công cụ đúng khi bạn cần cột từ cả hai phía của quan hệ trong kết quả. Nếu bạn chỉ cần lọc, chẳng hạn tìm mọi nhân viên có quản lý là Ada, thì subquery thường dễ đọc hơn:
Không cần xoay xở với alias, và ý định rõ ràng không thể nhầm. Quy tắc ngón tay cái: bạn muốn dữ liệu từ cả hai hàng trong đầu ra? Dùng self join. Bạn chỉ cần một giá trị để so sánh? Dùng subquery.
Với cấu trúc phân cấp có độ sâu bất kỳ (sơ đồ tổ chức, cây thư mục, bình luận theo luồng), cả hai mẫu đều không mở rộng được. Đó là lãnh địa của CTE đệ quy.
Tiếp theo: subquery
Self join và subquery giải quyết những bài toán chồng lấn nhau, và biết cái nào phù hợp sẽ giúp bạn đỡ phải nheo mắt nhìn SQL về sau. Trang tiếp theo đi sâu vào subquery: các dạng vô hướng, tương quan và IN, cùng chỗ mạnh của từng dạng.
Câu hỏi thường gặp
Self join trong SQLite là gì?
Self join là một JOIN bình thường, trong đó một bảng được join với chính nó. Bạn đặt cho cùng một bảng hai alias khác nhau để SQLite coi chúng là hai nguồn hàng riêng biệt, rồi ghép các hàng theo một cột liên kết hàng này với hàng khác, thường gặp nhất là quan hệ cha/con như nhân viên/quản lý.
Vì sao self join cần alias?
Không có alias, SQLite không biết bạn đang nói đến bản sao nào của bảng khi bạn viết tên cột. Đặt cho mỗi bản sao một alias riêng (ví dụ e cho nhân viên và m cho quản lý) giúp bạn viết e.manager_id = m.id mà không bị mơ hồ. Alias ở đây là bắt buộc: thiếu chúng, truy vấn sẽ không được phân tích cú pháp.
Khi nào nên dùng self join, khi nào dùng subquery?
Dùng self join khi bạn muốn có cột từ cả hai hàng trong kết quả, ví dụ tên nhân viên và tên quản lý trên cùng một dòng. Dùng subquery khi bạn chỉ cần lọc hoặc tra một giá trị duy nhất. Với các cấu trúc phân cấp lồng sâu, cả hai đều không phù hợp; CTE đệ quy mới là công cụ đúng.