2013-08-29 32 views
8

Trước tiên, tôi biết rằng câu hỏi này đã được đăng thường là Equals(=) vs. LIKE. Ở đây, tôi truy vấn về loại dữ liệu ngày trên cơ sở dữ liệu ORACLE, tôi thấy những điều sau đây, khi tôi viết chọn Statment theo cách này:Bằng (=) so với LIKE cho kiểu dữ liệu ngày

SELECT ACCOUNT.ACCOUNT_ID, ACCOUNT.LAST_TRANSACTION_DATE 
FROM ACCOUNT 
WHERE ACCOUNT.LAST_TRANSACTION_DATE LIKE '30-JUL-07'; 

tôi nhận được tất cả các hàng tôi đang tìm kiếm. nhưng khi tôi sử dụng ký hiệu bằng = thay thế:

SELECT ACCOUNT.ACCOUNT_ID, ACCOUNT.LAST_TRANSACTION_DATE 
FROM ACCOUNT 
WHERE ACCOUNT.LAST_TRANSACTION_DATE = '30-JUL-07'; 

Tôi không nhận được gì mặc dù không có gì khác biệt ngoại trừ dấu bằng. Tôi có thể tìm thấy bất kỳ lời giải thích nào cho việc này không?

+1

Tôi đoán cột đầu tiên chỉ khớp với '30-Jul-07 00: 00: 00' trong khi cột thứ hai khớp với' 30-Jul-07 **: **: ** '? –

Trả lời

14

Giả sử LAST_TRANSACTION_DATE là một DATE cột (hoặc TIMESTAMP) sau đó cả hai phiên bản là thực hành rất xấu.

Trong cả hai trường hợp, cột DATE sẽ được chuyển hoàn toàn thành ký tự theo chữ dựa trên cài đặt NLS hiện tại.Điều đó có nghĩa với các khách hàng khác nhau, bạn sẽ nhận được kết quả khác nhau.

Khi sử dụng ngày literals luôn sử dụng to_date() với (!) Một mặt nạ dạng hoặc sử dụng một ngày ANSI chữ. Bằng cách đó bạn so sánh ngày tháng với ngày không phải là chuỗi với chuỗi. Vì vậy, cho sự so sánh tương đương bạn nên sử dụng:

LAST_TRANSACTION_DATE = to_date('30-JUL-07', 'dd-mon-yy') 

Lưu ý rằng việc sử dụng 'MON' vẫn có thể dẫn đến sai sót với các thiết lập khác nhau NLS ('DEC' vs 'DEZ' hoặc 'MAR' vs 'MRZ'). Đó là ít hơn nhiều lỗi dễ sử dụng số tháng (và bốn năm chữ số):

LAST_TRANSACTION_DATE = to_date('30-07-2007', 'dd-mm-yyyy') 

hoặc sử dụng một ngày ANSI đen

LAST_TRANSACTION_DATE = DATE '2007-07-30' 

Bây giờ lý do tại sao các truy vấn trên là rất có khả năng trở lại không có gì là rằng trong các cột Oracle DATE cũng bao gồm thời gian. Các chữ ngày tháng ẩn chứa thời gian 00:00. Nếu thời gian trong bảng khác (ví dụ: 19:54) thì tất nhiên các ngày không bằng nhau.

Để workaround vấn đề này, bạn có các tùy chọn khác nhau:

  1. sử dụng trunc() trên cột bảng để "bình thường hóa" thời gian để 00:00 trunc(LAST_TRANSACTION_DATE) = DATE '2007-07-30 này tuy nhiên sẽ ngăn chặn việc sử dụng một chỉ số xác định trên LAST_TRANSACTION_DATE
  2. sử dụng between
    LAST_TRANSACTION_DATE between to_date('2007-07-30 00:00:00', 'yyyy-mm-dd hh24:mi:ss') and to_date('2007-07-30 23:59:59', 'yyyy-mm-dd hh24:mi:ss')

Vấn đề hiệu suất của giải pháp đầu tiên có thể được giải quyết bằng cách tạo chỉ mục trên trunc(LAST_TRANSACTION_DATE) có thể được sử dụng bởi biểu thức đó. Nhưng khái niệm LAST_TRANSACTION_DATE = '30-JUL-07' ngăn chặn một cách sử dụng chỉ số cũng vì nội nó được xử lý như to_char(LAST_TRANSACTION_DATE) = '30-JUL-07'

Những điều quan trọng cần nhớ:

  1. Không bao giờ, bao giờ dựa vào chuyển đổi kiểu dữ liệu ngầm. Nó sẽ cung cấp cho bạn các vấn đề tại một số điểm. Luôn so sánh các loại dữ liệu chính xác
  2. Oracle DATE các cột luôn chứa thời gian là một phần của quy tắc so sánh.
+0

có thêm một 'y' vào LAST_TRANSACTION_DATE = to_date ('30 -07-2007 ',' dd-mm-yyyyy '), vui lòng chỉnh sửa. Tôi không thể tự mình chỉnh sửa vì tôi cần chỉnh sửa ít nhất 6 ký tự :) nhờ –

+1

@QHero: cảm ơn, đã chỉnh sửa –

1

Trường ngày không phải là một chuỗi. Nội bộ một chuyển đổi tiềm ẩn được thực hiện cho một chuỗi khi bạn sử dụng =, mà không phù hợp với bất cứ điều gì bởi vì chuỗi của bạn không có số lượng chính xác cần thiết.

Tôi có thể đoán rằng câu lệnh LIKE hoạt động hơi khác với trường ngày, gây ra các ký tự đại diện ngầm được sử dụng trong so sánh giúp loại bỏ yêu cầu về độ chính xác bất kỳ. Về cơ bản, số LIKE của bạn hoạt động như sau:

SELECT ACCOUNT.ACCOUNT_ID, ACCOUNT.LAST_TRANSACTION_DATE 
FROM ACCOUNT 
WHERE ACCOUNT.LAST_TRANSACTION_DATE BETWEEN DATE('30-JUL-07 00:00:00.00000+00:00') AND DATE('30-JUL-07 23:59:59.99999+00:00'); 
6

Bạn không nên so sánh ngày với chuỗi trực tiếp. Bạn dựa vào implicit conversions, các quy tắc khó nhớ.

Hơn nữa, lựa chọn định dạng ngày của bạn không tối ưu: năm có bốn chữ số (lỗi Y2K?) Và không phải tất cả các ngôn ngữ đều có tháng thứ bảy của năm có tên là JUL. Bạn nên sử dụng một cái gì đó như YYYY/MM/DD.

Cuối cùng, ngày tháng trong Oracle là thời điểm chính xác đến lần thứ hai. Tất cả các ngày đều có thành phần thời gian, ngay cả khi là 00:00:00. Khi bạn sử dụng toán tử =, Oracle sẽ so sánh ngày và giờ cho ngày tháng.

Dưới đây là một trường hợp thử nghiệm tái tạo hành vi mà bạn mô tả:

SQL> create table test_date (d date); 

Table created 

SQL> alter session set nls_date_format = 'DD-MON-RR'; 

Session altered 

SQL> insert into test_date values 
    2  (to_date ('2007/07/30 11:50:00', 'yyyy/mm/dd hh24:mi:ss')); 

1 row inserted 

SQL> select * from test_date where d = '30-JUL-07'; 

D 
----------- 

SQL> select * from test_date where d like '30-JUL-07'; 

D 
----------- 
30/07/2007 

Khi bạn sử dụng các nhà điều hành =, Oracle sẽ chuyển đổi các chuỗi liên tục 30-JUL-07 đến một ngày và so sánh giá trị với cột, như thế này:

SQL> select * from test_date where d = to_date('30-JUL-07', 'DD-MON-RR'); 

D 
----------- 

Khi bạn sử dụng các nhà điều hành LIKE, Oracle sẽ chuyển đổi các cột vào một chuỗi và so sánh nó với phía bên tay phải, đó là tương đương với:

01.
SQL> select * from test_date where to_char(d, 'DD-MON-RR') like '30-JUL-07'; 

D 
----------- 
30/07/2007 

Luôn so sánh ngày với ngày và chuỗi với chuỗi. câu hỏi liên quan:

+0

Giả sử các định dạng dữ liệu này không nằm dưới sự kiểm soát của tôi và tôi cần phải so sánh với ngày 30 tháng 7-07, tôi đoán tôi phải sử dụng chuyển đổi rõ ràng như thế này to_date ('30 -JUL-07 '). đúng? – Hawk

+4

Bạn phải sử dụng 'to_date' với hai đối số (hằng số ** và ** định dạng :). Ngoài ra 'LIKE' là một nhà điều hành chuỗi và như vậy sẽ không hoạt động tốt với ngày tháng. –

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