'Id' ที่มีรูปแบบ: YYYYNNNNNN ที่มีส่วน NNNNNN เริ่มต้นใหม่ทุกปี


11

ฉันมีข้อกำหนดทางธุรกิจที่แต่ละระเบียนในตารางใบแจ้งหนี้มีรหัสซึ่งดูเหมือน YYYYNNNNNNN

ส่วน NNNNNN จำเป็นต้องเริ่มต้นใหม่ทุกต้นปี ดังนั้นแถวแรกที่ป้อนในปี 2559 จะมีลักษณะเป็น 2016000001 และแถวที่สองเช่น 2016000002 ฯลฯ ให้บอกว่าระเบียนสุดท้ายของปี 2559 คือ 2016123456 แถวถัดไป (ของปี 2017) ควรมีลักษณะเหมือน 2017000001

ฉันไม่ต้องการรหัสนี้เป็นคีย์หลักและฉันเก็บวันที่สร้างเช่นกัน แนวคิดคือ 'รหัสแสดง' นี้ไม่ซ้ำกัน (ดังนั้นฉันจึงสามารถค้นหาได้) และกลุ่มคนสามารถปี

ไม่น่าเป็นไปได้ที่จะมีการลบบันทึกใด ๆ อย่างไรก็ตามฉันจะโน้มน้าวให้รหัสป้องกันสิ่งเช่นนั้น

มีวิธีใดบ้างที่ฉันสามารถสร้าง id นี้โดยไม่ต้องค้นหา max id ในปีนี้ทุกครั้งที่มีการแทรกแถวใหม่

ไอเดีย:

  • A CreateNewInvoiceSPซึ่งได้รับความMAXคุ้มค่าสำหรับปีนั้น (yucky)
  • คุณลักษณะบางอย่างในตัวที่มีมนต์ขลังสำหรับการทำสิ่งนี้ (ฉันฝันได้ถูกต้อง)
  • ความสามารถในการระบุ UDF หรือบางอย่างในIDENTITYหรือDEFAULTการประกาศ (??)
  • มุมมองที่ใช้PARTITION OVER + ROW()(ถูกลบจะมีปัญหา)
  • ทริกเกอร์เปิดINSERT(ยังคงต้องเรียกใช้MAXแบบสอบถาม :()
  • งานพื้นหลังประจำปีอัปเดตตารางด้วย MAX สำหรับแต่ละปีซึ่งฉันใส่เข้าไปแล้ว ... อะไรซักอย่าง!

ทั้งหมดนี้ค่อนข้างไม่เหมาะนัก ยินดีต้อนรับความคิดหรือรูปแบบใด ๆ !


คุณมีคำตอบที่ดี แต่ถ้าคุณมีปี, id เป็น PK ดังนั้นเลือก max ได้อย่างรวดเร็ว
paparazzo

การใช้คิวรี่ max id แบบเลือกเป็นวิธีปฏิบัติทั่วไป ใช้มัน
UğurGümüşhan

คำตอบ:


17

มี 2 ​​องค์ประกอบในฟิลด์ของคุณ

  • ปี
  • หมายเลขที่เพิ่มขึ้นอัตโนมัติ

พวกเขาไม่จำเป็นต้องจัดเก็บเป็นหนึ่งฟิลด์

ตัวอย่าง:

  • คอลัมน์ปีซึ่งมีค่าเริ่มต้นเป็น YEAR(GETDATE())
  • คอลัมน์ตัวเลขตามลำดับ

จากนั้นสร้างคอลัมน์ที่คำนวณพร้อมกัน (พร้อมการจัดรูปแบบที่เหมาะสม) สามารถรีเซ็ตลำดับได้เมื่อเปลี่ยนปี

โค้ดตัวอย่างในSQLfiddle : * (SQLfiddle ไม่ทำงานเสมอไป)

-- 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
บางทีมันอาจจะสะอาดกว่าที่จะมีหนึ่งชุดต่อปี วิธีนี้ไม่จำเป็นต้องเรียกใช้ DDL เนื่องจากเป็นส่วนหนึ่งของการดำเนินงานปกติ
usr

@gbn ดังนั้นฉันจะต้องมีงานพื้นหลังเพื่อเริ่มต้นใหม่SEQUENCE ทุกต้นปีหรือไม่
DarcyThomas

@usr เศร้าคุณไม่สามารถใช้NEXT VALUE FORในCASEคำสั่ง (ฉันพยายาม)
DarcyThomas

8

คุณได้พิจารณาสร้างฟิลด์ข้อมูลประจำตัวด้วย seed = 2016000000 หรือไม่

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

เมล็ดนี้ควรถูกสร้างขึ้นใหม่อัตโนมัติในแต่ละปีตัวอย่างเช่นในตอนกลางคืนของปี 2017/1/1 คุณต้องกำหนดเวลา

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

แต่ฉันเห็นปัญหากับการออกแบบแล้วตัวอย่างเช่นถ้าคุณมีล้านระเบียน


2
ปัญหาอื่นคือถ้าระเบียนไม่ปรากฏตามลำดับเวลา ตัวตนอาจไม่ใช่วิธีที่จะไปหากเป็นกรณีนี้
Daniel Hutmacher

@LiyaTansky ในกรณีของฉันฉันได้รับการบอกว่าควรจะเป็น 50k ระเบียนต่อปี แต่ฉันเข้าใจในความหมายของคุณว่ามันเปราะบางด้วยแถว
1kk

1

สิ่งที่ฉันทำในสถานการณ์นี้คือการคูณปีด้วย 10 ^ 6 และเพิ่มค่าลำดับลงไป นี้มีประโยชน์จากการไม่ต้องมีการคำนวณข้อมูลด้วย (เล็ก) PRIMARY KEYค่าใช้จ่ายอย่างต่อเนื่องและข้อมูลที่สามารถนำมาใช้เป็น

มี gotchas ที่เป็นไปได้สองแบบ:

  • ตรวจสอบให้แน่ใจว่าตัวคูณของคุณมีขนาดใหญ่พอที่จะไม่หมดและ

  • คุณไม่รับประกันลำดับโดยไม่มีช่องว่างเนื่องจากการแคชของลำดับ

ฉันไม่ใช่ผู้เชี่ยวชาญใน SQL Server แต่คุณสามารถตั้งค่าเหตุการณ์ให้เรียกที่ 201x 00:00:00 เพื่อรีเซ็ตลำดับของคุณเป็นศูนย์ นั่นคือสิ่งที่ฉันทำกับ Firebird (หรือว่าเป็น Interbase?)


1

แก้ไข: วิธีนี้ไม่ทำงานภายใต้การโหลด

ฉันไม่ใช่แฟนของทริกเกอร์ แต่นี่ดูเหมือนจะดีที่สุดที่ฉันจะออกกำลังกายได้

ข้อดี:

  • ไม่มีงานพื้นหลัง
  • สามารถทำการสืบค้นอย่างรวดเร็วบน DisplayId
  • ทริกเกอร์ไม่จำเป็นต้องสแกนหาชิ้นส่วน NNNNNN ก่อนหน้า
  • จะเริ่มต้นส่วน NNNNN ใหม่ทุกปี
  • จะทำงานหากมีมากกว่า 100,000 แถวต่อปี
  • ไม่ต้องการการอัปเดตสคีมา (เช่นการรีเซ็ตตามลำดับ) เพื่อให้สามารถใช้งานได้ในอนาคต

แก้ไข:ข้อเสีย:

  • จะล้มเหลวภายใต้ภาระ (กลับไปที่กระดานวาดภาพ)

(ให้เครดิต @gbn เพราะฉันได้แรงบันดาลใจจากคำตอบของพวกเขา) (ฟีดใด ๆ & ชี้ให้เห็นถึงข้อผิดพลาดที่ชัดเจนยินดีต้อนรับ :)

เพิ่มCOLUMNเอสและเอINDEX

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)')

เพิ่มใหม่ 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

ฉันขอแนะนำไม่ให้ทำ เป็นไปได้ที่จะหยุดชะงักและทำให้เกิดความล้มเหลวในการแทรกเมื่อคุณได้รับภาระน้อย คุณได้ใส่สำเนาลงในฐานข้อมูลจำลองและใช้ค้อนกับเธรดไม่กี่เธรดพร้อมกันในการแทรก (และอาจเลือก / อัพเดต / ลบเช่นกัน) เพื่อดูว่าเกิดอะไรขึ้น?
โคดี Konior

@CodyKonior มีข้อบกพร่องพื้นฐานหรือสามารถกู้คืนได้ด้วยการล็อคอย่างรอบคอบ? ถ้าไม่ใช่คุณจะแก้ไขปัญหาอย่างไร
DarcyThomas

อืมม วิ่งด้วย 10 เธรด ไม่แน่ใจว่ามันเป็นกุญแจล็อคหรือไม่ แต่ฉันจะได้รับเงื่อนไขการแข่งขันบางอย่าง ที่หนึ่งทริกเกอร์เสร็จสมบูรณ์ก่อนที่ทริกเกอร์แถวก่อนหน้านี้จะเสร็จสิ้น สิ่งนี้นำไปสู่NULLการป้อนค่าจำนวนมาก กลับไปกระดานวาดภาพ ...
DarcyThomas

ภัยพิบัติกลับมาแล้ว :-) ความลับของฉันคือฉันจำรูปแบบของสิ่งที่ฉันทำเมื่อห้าปีก่อน ฉันรู้เพียงว่าวิธีที่คุณสแกนตารางภายในทริกเกอร์เพื่อค้นหาลำดับถัดไปทำให้สิ่งต่าง ๆ เกิดขึ้นภายใต้การโหลด ฉันจำไม่ได้ว่าฉันแก้ปัญหาได้อย่างไร แต่ฉันสามารถตรวจสอบได้ในภายหลัง
โคดี Konior

@CodyKonior ฉันไม่คิดว่าจะทำการสแกน ( ON Previous.Id = (I.Id -1) ควรหา) แต่ใช่ยังไม่ทำงาน หากฉันสามารถล็อคตาราง (?) ระหว่างการแทรกและทริกเกอร์ได้ฉันคิดว่ามันจะใช้งานได้ แต่นั่นฟังดูเหมือนรหัสกลิ่นด้วย
DarcyThomas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.