SQL Server: làm thế nào để giới hạn một bảng chứa một hàng?


81

Tôi muốn lưu trữ một hàng trong bảng cấu hình cho ứng dụng của mình. Tôi muốn thực thi rằng bảng này chỉ có thể chứa một hàng.

Cách đơn giản nhất để thực thi ràng buộc hàng đơn là gì?


Tại sao không sử dụng bảng với các cột (Name, Value)có khóa chính trên Tên. Sau đó, bạn có thể select Value from Table where Name = ?chắc chắn rằng không có hàng hoặc một hàng sẽ được trả lại.
a'r

2
Tôi không chắc sql là giải pháp tốt nhất ở đây. Có thể một tệp xml đơn giản thích hợp hơn cho cấu hình. Tôi thường nghĩ rằng cấu hình! = Data và sql được tạo cho dữ liệu.
Rem bourgarel

2
@ar - Tôi đã thấy điều đó rất sai khi bạn đang mong đợi đọc, chẳng hạn như một số nguyên và bạn nhận được một số giá trị có định dạng sai trong cột giá trị.
Damien_The_Un Believer 19/10/10

@Damien_The_Un Believer Tại sao điều đó lại xảy ra? Bởi vì bạn đã chỉ định một giá trị không tồn tại cho Name?
Noumenon

1
@Noumenon - lưu ý rằng nhận xét của tôi là phản hồi cho arnhận xét của s. Vấn đề là, nếu bạn chỉ lưu trữ các cặp tên / giá trị, thì giá trị khá tốt phải là chuỗi và bạn không có cách nào để thực thi xác thực trong cơ sở dữ liệu. Khi bạn sử dụng bảng một hàng với các cột riêng biệt cho từng cài đặt (như OP muốn) thì bạn có thể dễ dàng thực thi xác thực cho từng cài đặt cấu hình thông qua các ràng buộc kiểm tra.
Damien_The_Un Believer

Câu trả lời:


97

Bạn đảm bảo rằng một trong các cột chỉ có thể chứa một giá trị, sau đó đặt đó làm khóa chính (hoặc áp dụng ràng buộc duy nhất).

CREATE TABLE T1(
    Lock char(1) not null,
    /* Other columns */,
    constraint PK_T1 PRIMARY KEY (Lock),
    constraint CK_T1_Locked CHECK (Lock='X')
)

Tôi có một số bảng này trong các cơ sở dữ liệu khác nhau, chủ yếu để lưu trữ cấu hình. Tốt hơn rất nhiều khi biết rằng, nếu mục cấu hình phải là một int, bạn sẽ chỉ đọc một int từ DB.


2
+1. Đây là phương pháp Celko sử dụng cho một bảng hằng số phụ trong "SQL cho Smarties"
Martin Smith

Điều này là đơn giản như nó được. Cảm ơn.
Martin

+1: Giải pháp thú vị. Tôi đã không nhìn thấy điều này trước đây vì vậy cảm ơn vì đã chia sẻ. Luôn cách, dễ dàng khi bạn biết làm thế nào ....
John Sansom

Đây là một câu hỏi tiếp theo để suy nghĩ lại. Khóa chính của bảng này là gì? :)
nvogel

2
@BZ - cdt là viết tắt của comp.databases.theory, một nhóm usenet (hiển thị thông qua các nhóm Google) mà tôi thừa nhận rằng tôi đã không đọc nhiều gần đây. Nó thiên về lý thuyết quan hệ hơn là SQL - nhưng tôi tình cờ biết rằng dportas / sqlvogel cũng thường xuyên lui tới cùng một nhóm. TTM là một tham chiếu đến Tuyên ngôn thứ ba , đây là một cuốn sách hay nói (một lần nữa) về lý thuyết quan hệ hơn là SQL.
Damien_The_Un Believer

53

Tôi thường sử dụng cách tiếp cận của Damien, cách này luôn mang lại hiệu quả tốt cho tôi, nhưng tôi cũng nói thêm một điều:

CREATE TABLE T1(
    Lock char(1) not null DEFAULT 'X',
    /* Other columns */,
    constraint PK_T1 PRIMARY KEY (Lock),
    constraint CK_T1_Locked CHECK (Lock='X')
)

Thêm "DEFAULT 'X'", bạn sẽ không bao giờ phải đối phó với cột Khóa và sẽ không phải nhớ đâu là giá trị khóa khi tải bảng lần đầu tiên.


4
Ràng buộc mặc định cũng nên được đặt tên, nếu không nó sẽ nhận được một tên khó hiểu được tạo tự động. Lock char(1) not null CONSTRAINT DF_T1_Lock DEFAULT 'X'
David S.

1
Hơn nữa, bạn nên đặt giá trị mặc định của mình khác với 'X'. Tôi đã tạo cho mình một chuỗi dài hơn và đây là thông báo lỗi của tôi bây giờ nếu tôi cố gắng chèn một hàng thứ hai: Vi phạm ràng buộc PRIMARY KEY 'PK_RestrictToOneRow'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.1ROWTABLE'. Giá trị khóa trùng lặp là (Bảng này được khóa thành một hàng).
Geoff Griswald

14

Bạn có thể muốn suy nghĩ lại về chiến lược này. Trong các tình huống tương tự, tôi thường thấy việc để các hàng cấu hình cũ nằm xung quanh để tìm thông tin lịch sử là vô giá.

Để làm điều đó, bạn thực sự có thêm một cột creation_date_time(ngày / giờ chèn hoặc cập nhật) và một trình kích hoạt chèn hoặc chèn / cập nhật sẽ điền chính xác vào ngày / giờ hiện tại.

Sau đó, để có được cấu hình hiện tại của bạn, bạn sử dụng một số thứ như:

select * from config_table order by creation_date_time desc fetch first row only

(tùy thuộc vào hương vị DBMS của bạn).

Bằng cách đó, bạn vẫn có thể duy trì lịch sử cho các mục đích khôi phục (bạn có thể bắt đầu quy trình dọn dẹp nếu bảng quá lớn nhưng điều này khó xảy ra) và bạn vẫn có thể làm việc với cấu hình mới nhất.


+1: Tôi nghe bạn đang nói gì, nhưng tôi thích ghi lại lịch sử thay đổi trong các bảng kiểm toán riêng biệt.
Martin

+1: Để chia sẻ ý tưởng thú vị về việc triển khai một lộ trình kiểm tra.
John Sansom,

Đẹp. JustSELECT TOP 1 ... ORDER BY creation_date_time DESC
Baodad

5

Bạn có thể triển khai một kích hoạt INSTEAD OF để thực thi loại logic nghiệp vụ này trong cơ sở dữ liệu.

Trình kích hoạt có thể chứa logic để kiểm tra xem bản ghi đã tồn tại trong bảng chưa và nếu có, hãy QUAY LẠI Chèn.

Bây giờ, quay lại một bước để nhìn vào bức tranh lớn hơn, tôi tự hỏi liệu có cách nào thay thế và phù hợp hơn để bạn lưu trữ thông tin này, có lẽ trong một tệp cấu hình hoặc biến môi trường chẳng hạn?


+1 - Tôi có thể thấy trình kích hoạt sẽ hoạt động như thế nào và có thể tôi sẽ quay lại với cách tiếp cận đó, nhưng tôi thích ràng buộc đơn giản của Damien. Có một cuộc thảo luận thú vị về loại dữ liệu cấu hình thuộc tệp cấu hình và dữ liệu thuộc về DB. Trong trường hợp này, tôi nghĩ DB là nơi chính xác. Không nghi ngờ gì nữa, tôi sẽ sống để hối tiếc về điều này ... :-)
Martin

2

Tôi sử dụng một trường bit cho khóa chính với tên IsActive. Vì vậy, có thể có tối đa 2 hàng và và sql để lấy hàng hợp lệ là: chọn * từ Cài đặt trong đó IsActive = 1 nếu bảng có tên Cài đặt.


2

Đây là một giải pháp mà tôi đã đưa ra cho một bảng kiểu khóa chỉ có thể chứa một hàng, giữ Y hoặc N (ví dụ: trạng thái khóa ứng dụng).

Tạo bảng với một cột. Tôi đặt một ràng buộc kiểm tra trên một cột để chỉ có thể đặt một Y hoặc N vào đó. (Hoặc 1 hoặc 0, hoặc bất cứ điều gì)

Chèn một hàng trong bảng, với trạng thái "bình thường" (ví dụ: N nghĩa là không bị khóa)

Sau đó, tạo một kích hoạt INSERT trên bảng chỉ có SIGNAL (DB2) hoặc RAISERROR (SQL Server) hoặc RAISE_APPLICATION_ERROR (Oracle). Điều này làm cho mã ứng dụng có thể cập nhật bảng, nhưng bất kỳ INSERT nào không thành công.

Ví dụ về DB2:

create table PRICE_LIST_LOCK
(
    LOCKED_YN       char(1)   not null  
        constraint PRICE_LIST_LOCK_YN_CK  check (LOCKED_YN in ('Y', 'N') )
);
--- do this insert when creating the table
insert into PRICE_LIST_LOCK
values ('N');

--- once there is one row in the table, create this trigger
CREATE TRIGGER ONLY_ONE_ROW_IN_PRICE_LIST_LOCK
   NO CASCADE 
   BEFORE INSERT ON PRICE_LIST_LOCK
   FOR EACH ROW
   SIGNAL SQLSTATE '81000'  -- arbitrary user-defined value
     SET MESSAGE_TEXT='Only one row is allowed in this table';

Làm việc cho tôi.


2

Câu hỏi cũ nhưng làm thế nào về việc sử dụng IDENTITY (MAX, 1) của một loại cột nhỏ?

CREATE TABLE [dbo].[Config](
[ID] [tinyint] IDENTITY(255,1) NOT NULL,
[Config1] [nvarchar](max) NOT NULL,
[Config2] [nvarchar](max) NOT NULL

Bạn có thể thêm một hàng khác bằng cách sử dụng SET IDENTITY_INSERT.
Razvan Socol

1

Bạn có thể viết trình kích hoạt trên hành động chèn trên bảng. Bất cứ khi nào ai đó cố gắng chèn một hàng mới trong bảng, hãy loại bỏ logic loại bỏ hàng mới nhất trong mã kích hoạt chèn.



-1

Ở đây chúng ta cũng có thể tạo một giá trị ẩn sẽ giống nhau sau lần nhập đầu tiên trong cơ sở dữ liệu. Ví dụ: Bảng Sinh viên: Id: int firstname: char Ở đây trong hộp nhập, chúng ta phải chỉ định cùng một giá trị cho cột id sẽ hạn chế như sau lần nhập đầu tiên khác với việc viết khóa bla bla do hạn chế về khóa chính nên mãi mãi chỉ có một hàng. Hi vọng điêu nay co ich!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.