2011-08-31 19 views
9

Tôi đang gặp sự cố với một số truy vấn máy chủ SQL. Hóa ra rằng tôi có một bảng với các trường "Attibute_Name" và "Attibute_Value", có thể thuộc bất kỳ loại nào, được lưu trữ trong varchar. (Vâng ... Tôi biết.)Chuyển đổi thành datetime không thành công chỉ trên mệnh đề WHERE?

Tất cả các ngày cho một thuộc tính cụ thể dường như được lưu trữ định dạng "YYYY-MM-DD hh: mm: ss" (không chắc chắn 100% về điều đó, có hàng triệu các hồ sơ ở đây), vì vậy tôi có thể thực thi mã này mà không có vấn đề:

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value) 
from 
    ProductAttributes pa 
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID 
where 
    a.Attribute_Name = 'SomeDate' 

Tuy nhiên, nếu tôi thực hiện đoạn mã sau:

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value) 
from 
    ProductAttributes pa 
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID 
where 
    a.Attribute_Name = 'SomeDate' 
    and CONVERT(DATETIME, pa.Attribute_Value) < GETDATE() 

tôi sẽ nhận được lỗi sau: chuyển đổi thất bại khi chuyển đổi ngày và/hoặc thời gian từ chuỗi ký tự.

Làm thế nào nó không thành công trên mệnh đề where chứ không phải trên mệnh đề được chọn?

Một đầu mối:

Nếu thay vì lọc bởi ATTRIBUTE_NAME tôi sử dụng Attribute_ID thực tế lưu trữ trong cơ sở dữ liệu (PK) nó sẽ làm việc mà không có vấn đề.

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value) 
from 
    ProductAttributes pa 
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID 
where 
    a.Attribute_ID = 15 
    and CONVERT(DATETIME, pa.Attribute_Value) < GETDATE() 

Cập nhật Cảm ơn tất cả mọi người cho câu trả lời. Tôi thấy thật khó để chọn một câu trả lời đúng vì mọi người đã chỉ ra điều gì đó hữu ích để hiểu vấn đề. Nó chắc chắn phải làm với thứ tự thực hiện. Hóa ra truy vấn đầu tiên của tôi hoạt động chính xác vì mệnh đề WHERE được thực hiện trước, sau đó là SELECT. Truy vấn thứ hai của tôi không thành công vì lý do tương tự (vì Thuộc tính không được lọc, chuyển đổi không thành công khi thực thi cùng mệnh đề WHERE). Truy vấn thứ ba của tôi đã hoạt động vì ID là một phần của chỉ mục (PK), vì vậy nó được ưu tiên và nó đã khoan xuống kết quả về điều kiện đó trước tiên.

Cảm ơn!

+0

là có một thuộc tính được gọi là 'sOmeDaTe' - trong trường hợp bạn đang sử dụng một trường hợp đối chiếu nhạy cảm? Có lẽ nó rối tung lên tham gia của bạn. – YetAnotherUser

+1

Bạn dường như giả định một số loại đánh giá ngắn mạch hoặc đảm bảo thứ tự của các vị từ trong mệnh đề 'WHERE'. Điều này không được bảo đảm. Khi bạn có các kiểu dữ liệu hỗn hợp trong một cột như vậy, cách duy nhất để xử lý chúng là biểu thức 'CASE'. –

+0

PHA là gì? Nếu PHA là một bảng khác với PA thì có vẻ như dữ liệu của PHA có một số hồ sơ không thể chuyển đổi, trong đó PA không có. – N0Alias

Trả lời

2

Nếu chuyển đổi nằm trong mệnh đề WHERE, nó có thể được đánh giá cho nhiều bản ghi (giá trị) hơn nếu nó xuất hiện trong bản chiếu danh sách. Tôi đã nói về điều này trước đây trong bối cảnh khác nhau, xem T-SQL functions do no imply a certain order of executionOn SQL Server boolean operator short-circuit. Trường hợp của bạn thậm chí còn đơn giản hơn, nhưng cũng tương tự, và cuối cùng nguyên nhân gốc là giống nhau: không giả sử một lệnh thực thi bắt buộc khi giao dịch với một ngôn ngữ khai báo như SQL.

Giải pháp tốt nhất của bạn, bằng lề xa và lớn, là vệ sinh dữ liệu và thay đổi loại cột thành loại DATETIME hoặc DATETIME2. Tất cả cách giải quyết khác sẽ có một thiếu sót hoặc cách khác, vì vậy bạn có thể tốt hơn để làm điều đúng.

Cập nhật

Sau khi xem xét kỹ hơn (xin lỗi, tôi @VLDB và chỉ nhìn trộm SO giữa buổi) Tôi nhận ra rằng mình có một cửa hàng EAV với loại miễn ngữ nghĩa vốn có (các attribute_value thể BEA chuỗi, một ngày, một int vv). Ý kiến ​​của tôi là đặt cược tốt nhất của bạn là sử dụng sql_variant trong lưu trữ và tất cả các cách lên đến khách hàng (ví dụ: dự án sql_variant). Bạn có thể kết hợp kiểu trong máy khách, tất cả các API của khách hàng đều có các phương thức để trích xuất kiểu bên trong từ một sql_variant, xem Using sql_variant Data (tốt, hầu như tất cả các API ứng dụng khách ... Using the sql_variant datatype in CLR). Với sql_variant, bạn có thể lưu trữ nhiều loại khác nhau, bạn có thể sử dụng SQL_VARIANT_PROPERTY để kiểm tra những thứ như BaseType trong các giá trị được lưu trữ và thậm chí bạn có thể nghĩ như kiểm tra ràng buộc để thực thi đúng loại dữ liệu.

+0

Tôi sẽ rất do dự khi sử dụng 'SQL_VARIANT' * trừ khi * bạn đang thực hiện tất cả các bản trình bày, lọc và so sánh tại máy khách. Trong hệ thống EAV của chúng tôi, chúng tôi đã nhanh chóng chuyển từ 'SQL_VARIANT' sang các cột dành riêng cho từng loại. Ok, vì vậy bạn có hai NULL trong mỗi hàng, nhưng bạn không phải đối phó với tất cả những thứ khó chịu khác đi kèm với nó. Chỉ cần để cho một lắc công bằng ở cả hai bên, tôi viết blog một chút về những hạn chế ở đây: http://sqlblog.com/blogs/aaron_bertrand/archive/2009/10/12/bad-habits-to-kick-using-the- wrong-data-type.aspx ... bạn có thể hiển thị truy vấn của bạn nếu cột là 'SQL_VARIANT'? –

+0

Tôi thấy quan điểm của bạn. Làm và tổng hợp cấu trúc EAV sql_variant sẽ trở thành nạn nhân của các vấn đề cast, trong khi một cột/kiểu chuyên dụng có thể dễ dàng tổng hợp các giá trị bởi vì nó biết tất cả chúng đều ở trong trường cho kiểu đó, và kiểu không yêu cầu CAST. Phản đối hợp lệ. –

0

Có vẻ như vấn đề về dữ liệu với tôi. Hãy xem dữ liệu khi bạn chọn nó bằng cách sử dụng hai phương pháp khác nhau, hãy thử tìm độ dài khác nhau và sau đó chọn các mục trong các bộ khác nhau và nhãn cầu chúng. Ngoài ra kiểm tra nulls? (Tôi không chắc điều gì sẽ xảy ra nếu bạn thử chuyển đổi null thành ngày giờ)

+0

chuyển đổi null thành một kết quả datetime trong một null. –

1

Điều này phải làm theo thứ tự truy vấn SELECT được xử lý. Mệnh đề WHERE được xử lý lâu trước SELECT. Nó phải xác định những hàng để bao gồm/loại trừ. Mệnh đề sử dụng tên phải sử dụng quét để điều tra tất cả các hàng, một số không chứa dữ liệu ngày/giờ hợp lệ, trong khi khóa có thể dẫn đến tìm kiếm và không có hàng không hợp lệ nào được bao gồm tại điểm. Chuyển đổi trong danh sách SELECT được thực hiện cuối cùng, và rõ ràng bởi thời gian này nó sẽ không cố gắng chuyển đổi các hàng không hợp lệ. Vì bạn đang trộn dữ liệu ngày/giờ với dữ liệu khác, bạn có thể xem xét lưu trữ dữ liệu ngày hoặc số trong các cột chuyên dụng với các kiểu dữ liệu chính xác. Trong khi đó, bạn có thể trì hoãn việc kiểm tra theo cách sau:

SELECT /* ... */ 
FROM 
(
    SELECT /* ... */ 
    FROM ProductAttributes AS pa 
    INNER JOIN dbo.Attributes AS a 
    ON a.Attribute_ID = pa.Attribute_ID 
    WHERE a.Attribute_Name = 'SomeDate' 
    AND ISDATE (pa.Attribute_Value) = 1 
) AS z 
WHERE CONVERT(CHAR(8), AttributeValue, 112) < CONVERT(CHAR(8), GETDATE(), 112); 

Nhưng câu trả lời tốt hơn có lẽ là sử dụng Attribute_ID chìa khóa thay vì tên nếu có thể.

+1

Điều này không đảm bảo hoạt động. Vô hướng tính toán trong danh sách 'SELECT' có thể được đánh giá trước bộ lọc' WHERE'. Xem ví dụ [câu trả lời này] (http://stackoverflow.com/questions/5191701/tsql-divide-by-zero-encountered-despite-no-columns-containing-0/5203211#5203211) hoặc [mục kết nối này] (http://connect.microsoft.com/SQLServer/feedback/details/537419/sql-server-nên-không-tăng-phi logic-lỗi) –

+0

Không, điều này sẽ không hoạt động. Bạn đang giả định rằng thứ tự khai báo (subquery) ngụ ý thứ tự đánh giá như trong http://rusanu.com/2011/08/10/t-sql-functions-do-no-imply-a-certain- order-of-execution/QO có thể chọn một kế hoạch đánh giá CONVERT * trước * so sánh attribute_name và kích hoạt lỗi chuyển đổi. –

+0

Không, tôi nên nói, "bạn có thể thử trì hoãn" ... câu trả lời tốt hơn là lưu trữ dữ liệu trong các cột chuyên dụng của kiểu dữ liệu bên phải, thay vì nhồi tất cả mọi thứ vào một cột VARCHAR. –

7

Bạn dường như đang giả định một số loại đánh giá mạch ngắn hoặc thứ tự đảm bảo của các biến vị ngữ trong mệnh đề WHERE. Điều này không được bảo đảm. Khi bạn có các kiểu dữ liệu hỗn hợp trong một cột như vậy, cách an toàn duy nhất để xử lý chúng là biểu thức CASE.

sử dụng (ví dụ)

CONVERT(DATETIME, 
     CASE WHEN ISDATE(pa.Attribute_Value) = 1 THEN pa.Attribute_Value END) 

Không

CONVERT(DATETIME, pa.Attribute_Value) 
0

Tôi nghĩ vấn đề là bạn có một ngày xấu trong cơ sở dữ liệu của bạn (rõ ràng).

Trong ví dụ đầu tiên của bạn, nơi bạn không kiểm tra ngày trong mệnh đề WHERE, tất cả các ngày trong đó a.attribute.Name = 'SomeDate' hợp lệ, vì vậy nó không bao giờ cố gắng chuyển đổi ngày không hợp lệ.

Trong ví dụ thứ hai của bạn, việc thêm vào mệnh đề WHERE đang làm cho kế hoạch truy vấn thực sự chuyển đổi tất cả những ngày đó và tìm ngày xấu và sau đó xem tên thuộc tính.

Trong ví dụ thứ ba, thay đổi để sử dụng Attribute_Id có thể thay đổi kế hoạch truy vấn để nó chỉ tìm kiếm những vị trí id = 15 Đầu tiên, sau đó kiểm tra xem những bản ghi đó có ngày hợp lệ hay không. (Có lẽ Attribute_Id được lập chỉ mục và Attribute_name không phải là)

Vì vậy, bạn có một ngày tồi tệ nơi nào đó, nhưng nó không phải trên bất kỳ hồ sơ với Arttribute_id = 15.

0

Bạn có thể kiểm tra kế hoạch thực hiện.Có thể là với truy vấn đầu tiên, tiêu chí thứ hai (CONVERT(DATETIME, pa.Attribute_Value) < GETDATE()) được đánh giá đầu tiên trên tất cả các hàng bao gồm dữ liệu không hợp lệ (không phải ngày), trong trường hợp thứ hai - a.Attribute_ID = 15 được đánh giá trước. Do đó, loại trừ các hàng có giá trị không phải ngày.

btw, thứ hai cũng có thể nhanh hơn và nếu bạn không có bất kỳ thứ gì từ Attributes trong danh sách chọn, bạn có thể loại bỏ inner join Attributes a on a.Attribute_ID = pa.Attribute_ID.

Ngày lưu ý rằng, nó sẽ được khuyến khích để thoát khỏi EAV trong khi nó không có gì quá muộn :)

+0

bạn có thể thử tính toán lại số liệu thống kê bảng. Nếu 'ProductAttributes' chứa hàng triệu hàng, việc đánh giá' CONVERT (DATETIME, pa.Attribute_Value) nad2000

+0

'ANALYZE TABLE' không đúng với SQL Server. –

Các vấn đề liên quan