Câu lệnh SELECT INTO

Mục đích và cách dùng

Giả sử cần lưu giữ một số dòng của bảng A trước khi thực hiện các hành động update hoặc delete thì theo các bạn lưu ở đâu là tiện nhất? Chúng ta sẽ nghĩ ngay đến việc chứa chúng trong một bảng với cấu trúc y chang như vậy và đặt tên là A_bk hoặc A_yyyymmdd. Có hai cách để thực hiện việc này, hoặc ta tạo mới bảng A_yyyymmdd rồi copy những dòng sắp update/delete qua bảng mới, hoặc chúng ta có thể vừa tạo ra bảng mới vừa đổ data vào chỉ với câu lệnh SELECT INTO

Cú pháp trông thế này:

SELECT cột_1, cột_2, cột_3, cột_4
INTO A_20220401 ON [filegroup_1]
FROM A
WHERE x=123

Ở mệnh đề SELECT chúng ta chỉ định những cột cần mang vào bảng A_20220401. Nếu không muốn liệt kê dạng đầy đủ các bạn có thể dùng SELECT * để lấy tất cả các cột. Ngoài ra, bạn còn có thể thêm cột bất kì vào danh sách này như ví dụ ở phía dưới

Tiếp đến là mệnh đề INTO, chỉ định tên bảng mới chứa data trả về từ câu lệnh SELECT, và từ SQL Server 2016 SP2 trở đi, bạn còn có thể chỉ định filegroup cho bảng này ở mệnh đề ON. Nếu không được chỉ định bảng sẽ được tạo trên filegroup mặc định. Và sau đó là các mệnh đề FROM JOIN WHERE GROUP BY như một câu truy vấn thông thường

SELECT *
INTO objects_20220401_01
FROM sys.objects 

SELECT GETDATE() AS LogDate, *
INTO objects_20220401_02 ON [DATA]
FROM sys.objects 
WHERE name NOT LIKE 'sys%'

Câu lệnh SELECT INTO đầu tiên chỉ đơn giản tạo bảng object_20220401_01 và lấy tất cả các dòng của bảng sys.objects insert vào. Câu lệnh thứ 2 phức tạp hơn một chút với việc tạo thêm cột LogDate ghi lại ngày giờ select. Bảng objects_20220401_02 được tạo trên filegroup [DATA], điều này giúp bạn linh hoạt hơn trong việc chọn lưu giữ bảng này trên ổ đĩa phù hợp.

Bạn cũng có thể sử dụng câu lệnh SELECT INTO với bảng tạm, cả cục bộ (#tênbảng) hoặc toàn cục (##tênbảng) đều được. Tuy nhiên bạn không thể áp dụng cho biến bản (@tênbảng)

SELECT * 
INTO #objects_tmp
FROM sys.objects 

SELECT * 
INTO ##objects_tmp
FROM sys.objects 

Một vài lưu ý khi sử dụng SELECT INTO

SELECT INTO không thể insert data vào bảng đã có sẵn nên câu lệnh sẽ báo lỗi nếu bảng đã tồn tại. Bảng mới sẽ có tên các cột và kiểu dữ liệu giống với bảng gốc, tuy nhiên với các cột dạng computed chỉ được copy giá trị tại thời điểm select vào cột tương ứng trong bảng đích chứ không copy được thuộc tính này.

Bên cạnh computed column, các đối tượng như các ràng buộc, khóa chính, indexes, triggers cũng sẽ không được copy theo. Tuy nhiên, thuộc tính identity lại có thể copy được. Nếu bảng gốc có cột identity thì thuộc tính này mặc định sẽ được mang qua bảng đích, nhưng phải không vi phạm một trong các điều sau:

  • Câu lệnh SELECT có phép JOIN hoặc UNION, hoăc có mệnh đề GROUP BY hoặc các hàm tổng hợp (aggregate function)
  • Cột identity được liệt kê hơn một lần trên mệnh đề SELECT
  • Cột identity tham gia vào các biểu thức trên mệnh đề SELECT
  • Cột identity từ bảng ở remoted server (data source)

Hãy cùng kiểm tra điều này thông qua demo sau đây.


USE tempdb 
GO

CREATE TABLE T1(id INT IDENTITY, LogDate DATETIME)
GO

INSERT INTO T1(LogDate)
SELECT GETDATE()
GO 100

GO
SELECT * 
INTO T1_20220401_01
FROM T1 

GO
SELECT  id, LogDate, id AS id2
INTO T1_20220401_02
FROM T1 


so sánh hai kết quả câu lệnh select into
So sánh cột identity giữa hai bảng T1_20220401_01 và T1_20220401_02.

Rõ ràng bảng T1_20220401_02 không có thuộc tính identity trên cột id giống như bảng T1_20220401_01. Bởi vì câu lệnh SELECT INTO của bảng T1_20220401_02 đã vi phạm việc liệt kê cột id hai lần trên mệnh đề SELECT.

Vì SELECT INTO có tạo bảng nên nó cũng được xem là một câu lệnh DDL. Việc tạo bảng này cần lock một số đối tượng trong bảng hệ thống và sẽ giữ lock đến khi kết thúc câu lệnh. Nếu câu lệnh SELECT INTO chạy càng lâu thì mức độ ảnh hưởng sẽ càng lớn nên các bạn cần chú ý vấn đề này. Nếu được hãy hạn chế sử dụng SELECT INTO trên production và thay bằng việc tạo bảng trước rồi hãy insert.

Leave a Reply