2009-12-04 32 views
33

Tôi có một quy trình được lưu trữ với một số tham số. Tôi muốn viết truy vấn của tôi để nó tham gia với một số bảng nhưng chỉ khi một tham số cụ thể có một giá trị. Lấy ví dụ sau: Tôi có một bảng Person. Ngoài ra còn có một bảng địa chỉ chứa các địa chỉ con người và một bảng nhóm chứa các nhóm người. Cả hai đều là một trong nhiều mối quan hệ với bảng Person. Thủ tục được lưu trữ của tôi có tham số @AddressID và tham số @GroupID.T-SQL - Cách viết tham gia có điều kiện

Truy vấn luôn trả về các trường từ bảng Person. Nếu không có tham số nào có giá trị thì truy vấn sẽ trả về tất cả các bản ghi từ bảng Person. Nếu tham số @AddressID được cung cấp thì nó sẽ chỉ trả về các bản ghi có bản ghi phù hợp trong bảng Địa chỉ và bỏ qua bảng Nhóm. Nếu tham số @GroupID được cung cấp thì nó sẽ chỉ trả về các bản ghi có bản ghi khớp trong bảng Nhóm và bỏ qua bảng Địa chỉ. Nếu cả hai tham số được cung cấp thì nó sẽ chỉ hiển thị các bản ghi có bản ghi khớp trong cả hai bảng. Có lý?

Có cách nào đơn giản để thực hiện việc này mà tôi bị thiếu không?

Cảm ơn, Corey

+0

Cám ơn hỏi. Không ai khác có. Điều này khá hữu ích khi sử dụng LEFT JOIN chẳng hạn. – Jorge

Trả lời

34

Nếu tôi hiểu đúng nó có vẻ như điều kiện tham gia của bạn sẽ là tương đương với
ON ((@AddressID IS NOT NULL) AND (alias.column = @AddressID)) và tương tự cho nhóm tham gia.

Tôi sử dụng tham gia có điều kiện này vào các thời điểm.

+0

Hoàn hảo! Làm việc như người ở. Cám ơn rất nhiều. –

+0

đẹp! ......... – fearofawhackplanet

+0

Đôi khi nó có thể rất đơn giản! Cảm ơn * facepalm * – Shion

7

Có, nó rất đơn giản. Do tham gia trái vào địa chỉ và nhóm. Sau đó, trong mệnh đề where ...

(@group_id is null or g.group_id = @group_id) 
and (@address_id is null or a.address_id = @address_id) 
+1

Điều này sẽ cung cấp câu trả lời đúng, nhưng hiệu suất sẽ chậm với các điều kiện OR trong bạn tham gia. –

2

Bạn sẽ có thể mở rộng về vấn đề này ...

DECLARE @SQL varchar(max) 

    SET @SQL = 'SELECT * FROM PERSON P' 

    IF NULLIF(@ADDRESSID,"") IS NULL SET @SQL = @SQL + " INNER JOIN ADDRESSES A ON P.AddressID = A.AddressID" 

    EXEC sp_executesql @SQL, N'@ADDRESSID int', @ADDRESSID 
+0

@Paul - Đừng làm điều này – JonH

+0

Tại sao không? SQL động là tuyệt vời! –

+7

Không, đó là con đẻ của chính con quỷ hầu hết thời gian và chỉ nên được sử dụng trong trường hợp không có bất kỳ sự thay thế thực sự nào. Chỉ cần cố gắng lưu một dòng SQL không được tính. Không chỉ vậy, nhưng SQL năng động của bạn sẽ tạo ra một kế hoạch truy vấn ít hiệu quả, phức tạp rất nhiều bất kỳ nâng cấp hoặc kiểm toán, và quan trọng nhất lá bạn dễ bị tấn công SQL injection. Chủ yếu là vì lười. – MartW

0

gì Quntin đăng là đẹp tuy nhiên có một số vấn đề hiệu suất với nó. Tin hay không gì là nhanh hơn là để kiểm tra từng thông số và viết SQL Tham dựa trên trường hợp

Ngoài ra:

IF @AddressParameter IS NOT NULL 
BEGIN 
SELECT blah1, blah2 FROM OneTable INNER JOIN AddressTable WHERE .... 
-more code 
END 
ELSE... 
BEGIN 
END 
... 

Một điều bạn có thể làm là thực hiện tham gia và trong truy vấn bộ lọc (mệnh đề where) bạn có thể làm:

Hiệu suất ở đây cũng bóng râm.

+0

Điều đó sẽ không thực sự hiệu quả trong tình huống này. Tôi có khoảng 9 tham số có thể hoặc không được rỗng. Các kết hợp có thể có của tất cả các thông số khác nhau sẽ giống như 9 giai thừa hoặc một số lượng lớn. –

+0

Tôi không chắc tôi hiểu ý của bạn là Corey. Tất cả bạn sẽ làm là xử lý trong WHERE cho n Số tham số. Ngoài ra, WHERE ((Địa chỉ = @Address HOẶC @Address IS NULL) VÀ (Group = @Group HOẶC @Group IS NULL) AND (...)) – JonH

+0

Một phép nối đơn giản sẽ loại trừ các bản ghi. Nó cần phải là một tham gia trái. – RayLoveless

22

Cách đơn giản là giải pháp không thực sự tốt. Tệ như âm thanh, giải pháp tốt nhất là có IF rõ ràng trong mã và các truy vấn riêng biệt:

IF (condition) 
    SELECT ... FROM Person WHERE ... 
ELSE IF (otherCondition) 
    SELECT ... FROM Person JOIN ... ON ... WHERE ... 
ELSE IF (moreCondition) 
    SELECT ... FROM Persons JOIN ... JOIN ... WHERE ... 

Lý do là nếu bạn đang cố gắng xây dựng một truy vấn duy nhất phù hợp với cả ba (hoặc nhiều hơn) điều kiện sau đó động cơ phải sản xuất một duy nhất gói truy vấn hoạt động ở tất cả các điều kiện. Trong một câu lệnh T-SQL bằng một kế hoạch. Hãy nhớ rằng các kế hoạch được tạo cho trường hợp chung, cho bất kỳ giá trị biến số nào, do đó kết quả luôn là một kế hoạch rất, rất xấu.

Trong khi là phản trực giác và có vẻ như một giải pháp khủng khiếp đối với bất kỳ lập trình viên nào, đây là cách cơ sở dữ liệu hoạt động. Lý do tại sao đây không phải là vấn đề 99,99% thời gian là sau khi thử những gì bạn hỏi và xem những gì nó phải được thực hiện, các nhà phát triển nhanh chóng đến với giác quan của họ và sửa lại yêu cầu của họ để họ không bao giờ phải chạy các truy vấn dựa trên các giá trị biến runtime;)

+2

LINQ2SQL là, có lẽ đáng ngạc nhiên, một fitr tốt hơn cho một cái gì đó như thế này, vì bản chất của các biểu thức truy vấn là các đối tượng đầy đủ có thể được thao tác và tinh chế, xem http://stackoverflow.com/questions/1849005/converting-conditionally- built-sql-where-clause-into-linq/1849041 # 1849041 –

+1

Tốt nếu bạn có các tiêu chí loại trừ lẫn nhau, hầu hết các màn hình tìm kiếm đều không có. Và nó làm tăng chi phí bảo trì - nếu thay đổi JOIN, bạn phải thực hiện các thay đổi cho tất cả các bản sao. Dynamic SQL là giải pháp thực tế hơn trong các tình huống như vậy. –

+0

@Pony: Vâng, SQL động là tốt hơn cho các điều kiện phức tạp. –

0

Tham gia vào ba bảng với nhau và sử dụng một cái gì đó như thế này trong mệnh đề WHERE của bạn:

WHERE Addresses.ID = COALESCE(@AddressID, Addresses.ID) 
AND Groups.ID = COALESCE(@GroupID, Groups.ID) 
+0

Một phép nối đơn giản sẽ loại trừ các bản ghi. Nó cần phải là một gia nhập trái tôi tin. – RayLoveless

0

Uh, có lẽ tất cả các bạn đã giải quyết này cho đến nay.

Khi tôi hiểu Bạn, bạn muốn có truy vấn 'động', để tham gia bảng nếu tham số tồn tại hoặc để bỏ qua tham số nếu tham số là null. Bí mật đang sử dụng kết nối bên ngoài bên trái. Giống như:

SELECT p.* 
FROM Parent AS p 
LEFT OUTER JOIN Child AS c ON p.Id = c.ParentId 
WHERE 
     (@ConditionId IS NULL OR c.ConditionId = @ConditionId) 

Cách thức hoạt động?

  • Nếu tham số lọc @ConditionId là rỗng, thì không có con để tham gia bên ngoài và kết quả sẽ có tất cả của cha mẹ.
  • Nếu tham số lọc @ConditionId không phải là rỗng, thì tham gia bên ngoài sẽ tham gia Child với cha mẹ này, và điều kiện (@ConditionId IS NULL OR c.ConditionId = @ConditionId) sẽ ném ra của cha mẹ mà đã không tham gia của trẻ em với điều kiện c.ConditionId = @ConditionId.

LEFT OUTER THAM GIA chắc chắn có vấn đề về hiệu suất, nhưng càng nhanh càng tốt, tôi không muốn ghép nối truy vấn của chuỗi.

3

Đây là cách tôi đã làm cho trường hợp của tôi.


DECLARE 
    @ColorParam varchar(500) 

SET 
    @ColorParam = 'red, green, blue' 

declare @Colors table 
(
    Color NVARCHAR(50) PRIMARY KEY 
) 

-- populate @Colors table by parsing the input param, 
-- table can be empty if there is nothing to parse, i.e.: no condition 
INSERT @Colors SELECT Value FROM dbo.Splitter(@ColorParam, ',') 

SELECT 
    m.Col1, 
    c.Color 
FROM 
    MainTable AS m 
FULL JOIN -- instead of using CROSS JOIN which won't work if @Colors is empty 
    @Colors AS c 
ON 
    1 = 1 -- the trick 
WHERE 
    (@ColorParam IS NULL OR c.Color = m.Color) 
+0

Tham gia đầy đủ sẽ trả lại một loạt các giá trị rỗng trong các cột trong bảng chính nơi id không khớp. U cần tham gia trái để thay thế. – RayLoveless

0

Left tham gia và mệnh đề where nên làm các trick:

SELECT Customers.CustomerName, Customers.Country, Orders.OrderID 
    FROM Customers 
    LEFT JOIN Orders 
    ON Customers.CustomerID=Orders.CustomerID 
    WHERE Country= @MyOptionalCountryArg or @MyOptionalCountryArg is null; 
Các vấn đề liên quan