'Id' với định dạng: YYYYNNNNNN với phần NNNNNN khởi động lại mỗi năm


11

Tôi có một yêu cầu kinh doanh rằng mỗi bản ghi trong bảng Hóa đơn có một id trông giống như YYYYNNNNNN.

Phần NNNNNN cần khởi động lại vào đầu mỗi năm. Vì vậy, hàng đầu tiên được nhập vào năm 2016 sẽ trông giống như 2016000001 và hàng thứ hai như 2016000002, v.v. Hãy nói rằng bản ghi cuối cùng của năm 2016 là 2016123456, Hàng tiếp theo (của năm 2017) sẽ giống như 2017000001

Tôi không cần id này làm khóa chính và tôi cũng lưu trữ ngày tạo. Ý tưởng là 'id hiển thị' này là duy nhất (vì vậy tôi có thể truy vấn theo nó) và nhóm người có thể theo năm.

Không chắc là bất kỳ hồ sơ sẽ bị xóa; tuy nhiên, tôi sẽ có xu hướng mã hóa phòng thủ chống lại một cái gì đó như thế.

Có cách nào để tôi có thể tạo id này mà không phải truy vấn id tối đa trong năm nay mỗi khi chèn một hàng mới không?

Ý tưởng:

  • A CreateNewInvoiceSP, nhận được MAXgiá trị cho năm đó (yucky)
  • Một số tính năng được xây dựng trong tính năng để thực hiện chính xác điều này (tôi có thể mơ đúng)
  • Có thể chỉ định một số UDF hoặc một cái gì đó trong IDENTITYhoặc DEFAULTkhai báo (??)
  • Một khung nhìn sử dụng PARTITION OVER + ROW()(bị xóa sẽ có vấn đề)
  • Một kích hoạt trên INSERT(vẫn sẽ cần chạy một số MAXtruy vấn :()
  • Một công việc nền hàng năm, đã cập nhật một bảng với MAX cho mỗi năm được chèn vào mà sau đó tôi ... Cái gì đó?!

Tất cả đều là một chút không lý tưởng. Bất kỳ ý tưởng hoặc biến thể chào đón mặc dù!


Bạn có một số câu trả lời hay nhưng nếu bạn có năm, id là PK thì chọn max là khá nhanh.
paparazzo

sử dụng truy vấn id tối đa được chọn là một cách phổ biến. sử dụng nó.
Uur Gümüşhan

Câu trả lời:


17

Có 2 yếu tố cho lĩnh vực của bạn

  • Năm
  • Số tăng tự động

Chúng không cần được lưu trữ dưới dạng một trường

Thí dụ:

  • Một cột năm có mặc định là YEAR(GETDATE())
  • Một cột số dựa trên một chuỗi.

Sau đó tạo một cột được tính toán nối chúng (với định dạng phù hợp). Trình tự có thể được thiết lập lại khi thay đổi năm.

Mã mẫu trong SQLfiddle : * (SQLfiddle không luôn hoạt động)

-- Create a sequence
CREATE SEQUENCE CountBy1
    START WITH 1
    INCREMENT BY 1 ;

-- Create a table
CREATE TABLE Orders
    (Yearly int NOT NULL DEFAULT (YEAR(GETDATE())),
    OrderID int NOT NULL DEFAULT (NEXT VALUE FOR CountBy1),
    Name varchar(20) NOT NULL,
    Qty int NOT NULL,
    -- computed column
    BusinessOrderID AS RIGHT('000' + CAST(Yearly AS VARCHAR(4)), 4)
                     + RIGHT('00000' + CAST(OrderID AS VARCHAR(6)), 6),
    PRIMARY KEY (Yearly, OrderID)
    ) ;


-- Insert two records for 2015
INSERT INTO Orders (Yearly, Name, Qty)
    VALUES
     (2015, 'Tire', 7),
     (2015, 'Seat', 8) ;


-- Restart the sequence (Add this also to an annual recurring 'Server Agent' Job)
ALTER SEQUENCE CountBy1
    RESTART WITH 1 ;

-- Insert three records, this year.
INSERT INTO Orders (Name, Qty)
    VALUES
     ('Tire', 2),
     ('Seat', 1),
     ('Brake', 1) ;

1
Có lẽ nó sạch hơn để có một chuỗi mỗi năm. Theo cách đó, không cần thực thi DDL như một phần của hoạt động thông thường.
usr

@gbn Vậy tôi có cần một công việc nền để khởi động lại SEQUENCE vào đầu mỗi năm không?
DarcyThomas

@usr Đáng buồn thay, bạn không thể sử dụng NEXT VALUE FORtrong một CASEtuyên bố (Tôi đã thử)
DarcyThomas

8

Bạn đã xem xét để tạo một trường nhận dạng với seed = 2016000000 chưa?

 create table Table1 (
   id bigint identity(2016000000,1),
   field1 varchar(20)...
)

Hạt giống này nên được tự động hóa mỗi năm, ví dụ vào đêm 2017/1/1 bạn cần lên lịch

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

Nhưng tôi đã thấy vấn đề với thiết kế, ví dụ: nếu bạn có hàng triệu bản ghi thì sao?


2
Một vấn đề khác là nếu các hồ sơ không xuất hiện theo trình tự thời gian. Danh tính có lẽ không phải là con đường để đi nếu đây là trường hợp.
Daniel Hutmacher

@LiyaTansky Trong trường hợp của tôi, tôi đã nói rằng chỉ nên là 50 nghìn hồ sơ mỗi năm. Nhưng tôi hiểu ý của bạn về việc nó dễ vỡ với 1kk hàng
DarcyThomas

1

Những gì tôi đã làm trong kịch bản này là nhân năm đó với 10 ^ 6 và thêm giá trị chuỗi vào đó. Điều này có lợi thế là không yêu cầu trường được tính toán với chi phí liên tục (nhỏ) và trường có thể được sử dụng làm trườngPRIMARY KEY .

Có hai vấn đề có thể xảy ra:

  • đảm bảo rằng hệ số nhân của bạn đủ lớn để không bao giờ cạn kiệt, và

  • bạn không được đảm bảo một chuỗi không có khoảng trống do bộ nhớ đệm của chuỗi.

Tôi không phải là chuyên gia về SQL Server, nhưng có lẽ bạn có thể đặt sự kiện để kích hoạt vào 201x 00:00:00 để đặt lại chuỗi của bạn về không. Đó cũng là những gì tôi đã làm trên Firebird (hoặc đó là Interbase?).


1

Chỉnh sửa: Giải pháp này không hoạt động dưới tải

Tôi không phải là người hâm mộ của các yếu tố kích hoạt, nhưng điều này có vẻ tốt nhất tôi có thể giải quyết.

Ưu điểm:

  • Không có công việc nền
  • Có thể thực hiện các truy vấn nhanh trên DisplayId
  • Kích hoạt không cần quét phần NNNNNN trước đó
  • Sẽ khởi động lại phần NNNNN hàng năm
  • Sẽ hoạt động nếu có hơn 100000 hàng mỗi năm
  • Không yêu cầu cập nhật lược đồ (ví dụ: đặt lại chuỗi) để tiếp tục hoạt động trong tương lai

Chỉnh sửa: Nhược điểm:

  • Sẽ không tải được (quay lại bảng vẽ)

(Tín dụng cho @gbn khi tôi lấy một số cảm hứng từ câu trả lời của họ) (Bất kỳ phản hồi nào & chỉ ra những sai lầm rõ ràng được chào đón :)

Thêm một số COLUMNs mới và mộtINDEX

ALTER TABLE dbo.Invoices
ADD     [NNNNNNId]      INT  NULL 

ALTER TABLE dbo.Invoices
ADD [Year]              int NOT NULL DEFAULT (YEAR(GETDATE()))

ALTER TABLE dbo.Invoices
ADD [DisplayId]     AS  'INV' +
                        CAST([Year] AS VARCHAR(4))+
                        RIGHT('00000' + CAST([NNNNNNId] AS VARCHAR(4)),  IIF (5  >= LEN([NNNNNNId]), 5, LEN([NNNNNNId])) )                  

EXEC('CREATE NONCLUSTERED INDEX IX_Invoices_DisplayId
ON dbo.Invoices (DisplayId)')

Thêm cái mới TRIGGER

CREATE TRIGGER Invoices_DisplayId
ON dbo.Invoices
  AFTER  INSERT
AS 
BEGIN

SET NOCOUNT ON;    

UPDATE dbo.Invoices
SET NNNNNNId = CalcDisplayId
FROM (SELECT I.ID, IIF (Previous.Year = I.Year , (ISNULL(Previous.NNNNNNId,0) + 1), 1) AS CalcDisplayId  FROM
        (SELECT 
            ID  
           ,NNNNNNId 
           ,[year]
        FROM  dbo.Invoices
        ) AS Previous
    JOIN inserted AS I 
    ON Previous.Id = (I.Id -1) 
    ) X
WHERE 
   X.Id = dbo.Invoices.ID       
END
GO

Tôi rất khuyên bạn không nên làm điều này. Nó có khả năng bế tắc và gây ra lỗi chèn khi bạn chịu tải nhẹ. Bạn đã đặt một bản sao vào cơ sở dữ liệu giả và đập nó với vài chục luồng cùng một lúc khi thực hiện chèn (và có thể chọn / cập nhật / xóa) để xem điều gì xảy ra?
Cody Konior

@CodyKonior về cơ bản là thiếu sót hoặc nó có thể được phục hồi với một chút khóa hợp lý? Nếu không làm thế nào bạn sẽ tiếp cận vấn đề?
DarcyThomas

Hừm. Ran với 10 chủ đề. Không chắc chắn nếu đó là ổ khóa chết, nhưng tôi có một số điều kiện cuộc đua. Khi một kích hoạt hoàn thành, trước khi kích hoạt hàng trước kết thúc. Điều này dẫn đến một loạt các NULLgiá trị được nhập vào. Quay lại bảng vẽ ...
DarcyThomas

Thảm họa đã biến mất sau đó :-) Bí mật của tôi là tôi đã nhận ra mô hình cho một cái gì đó tôi đã làm khoảng năm năm trước. Tôi chỉ biết rằng cách bạn quét bảng bên trong trình kích hoạt tìm kiếm chuỗi tiếp theo sẽ khiến mọi thứ trở nên quá tải. Tôi không nhớ tôi đã giải quyết nó như thế nào nhưng tôi có thể kiểm tra sau.
Cody Konior

@CodyKonior Tôi không nghĩ rằng nó đang thực hiện quét ( ON Previous.Id = (I.Id -1) chỉ nên tìm kiếm), nhưng vâng vẫn không hoạt động. Nếu tôi có thể khóa bảng (?) Trong quá trình chèn kích hoạt thì tôi nghĩ nó sẽ hoạt động. Nhưng âm thanh đó cũng giống như mùi mã.
DarcyThomas
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.