Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Làm thế nào để chuyển đổi varchar thành date chỉ khi nó chứa một ngày hợp lệ?

Câu trả lời điển hình là thêm mệnh đề WHERE:

WHERE ISDATE(a.valor) = 1

Tuy nhiên, điều này có vấn đề trong tình huống của bạn vì một số lý do:

  1. ISDATE() sẽ không nhất thiết phải khớp theo cách bạn muốn tùy thuộc vào cài đặt khu vực của máy chủ, ngôn ngữ của người dùng hoặc tùy chọn định dạng ngày, v.v. Ví dụ:

    SET DATEFORMAT dmy;
    SELECT ISDATE('13/01/2012'); -- 1
    
    SET DATEFORMAT mdy;
    SELECT ISDATE('13/01/2012'); -- 0
    
  2. Bạn thực sự không thể kiểm soát rằng SQL Server sẽ thử và thực hiện CONVERT sau bộ lọc.

Bạn thậm chí không thể sử dụng truy vấn con hoặc CTE để thử và tách bộ lọc khỏi CHUYỂN ĐỔI vì SQL Server có thể tối ưu hóa các hoạt động trong truy vấn theo bất kỳ thứ tự nào mà nó cho là hiệu quả hơn.

Ví dụ:với một mẫu giới hạn, bạn có thể sẽ thấy rằng điều này hoạt động ổn:

SET DATEFORMAT dmy;

SELECT valor, valor_date FROM (
  SELECT valor, valor_date = CONVERT(DATE, 
    CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
  FROM dbo.mytable
  WHERE ISDATE(valor) = 1
) AS sub WHERE valor_date BETWEEN '01/01/2012' AND '01/03/2012';

Nhưng tôi đã gặp các trường hợp với ngay cả cấu trúc này trong đó SQL Server đã cố gắng đánh giá bộ lọc trước, dẫn đến lỗi tương tự mà bạn hiện đang gặp phải.

Một số cách giải quyết an toàn hơn:

Thêm một cột được tính toán, ví dụ:

ALTER TABLE dbo.mytable ADD valor_date
  AS CONVERT(DATE, CASE WHEN ISDATE(valor) = 1 THEN valor 
    ELSE NULL END, 103);

Để bảo vệ bản thân khỏi các hiểu sai có thể xảy ra trong thời gian chạy, bạn nên chỉ định định dạng ngày trước khi đưa ra truy vấn tham chiếu đến cột được tính toán, ví dụ:

SET DATEFORMAT dmy;
SELECT valor, valor_date FROM dbo.mytable WHERE ...;

Tạo chế độ xem:

CREATE VIEW dbo.myview
AS
  SELECT valor, valor_date = CONVERT(DATE, 
    CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
  FROM dbo.mytable
  WHERE ISDATE(valor) = 1;

Một lần nữa, bạn sẽ muốn phát hành một SET DATEFORMAT khi truy vấn chế độ xem.

Sử dụng bảng tạm thời:

SELECT <cols>
INTO #foo
FROM dbo.mytable
WHERE ISDATE(valor) = 1;

SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;

Bạn vẫn có thể muốn sử dụng DATEFORMAT để bảo vệ bạn khỏi xung đột giữa ISDATE và cài đặt người dùng.

Và không, bạn không nên không cố gắng xác thực các chuỗi của bạn dưới dạng ngày tháng bằng cách sử dụng đối sánh mẫu chuỗi như đã được đề xuất trong một câu trả lời khác (hiện đã bị xóa):

like '%__/%' or like '%/%'

Bạn sẽ phải có một số xác thực khá phức tạp và nặng tay ở đó để xử lý tất cả các ngày hợp lệ bao gồm cả năm nhuận.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sử dụng CPU của cơ sở dữ liệu?

  2. Làm cách nào để tôi có thể đặt ID_insert trên tên bảng được truyền dưới dạng một biến

  3. Thoát tham số lệnh được chuyển tới xp_cmdshell đến dtexec

  4. Lỗi kết nối RODBC odbcDriverConnect ()

  5. Tìm kiếm văn bản trong quy trình được lưu trữ trong SQL Server