NNNNNN 부분이 매년 다시 시작되는 YYYYNNNNNN 형식의 'Id'


11

인보이스 테이블의 각 레코드에 YYYYNNNNNN과 같은 ID가 있어야한다는 비즈니스 요구 사항이 있습니다.

NNNNNN 부품은 매년 초에 다시 시작해야합니다. 2016 년에 입력 한 첫 번째 행은 2016000001과 같고 두 번째 행은 2016000002와 같습니다. 2016 년의 마지막 레코드는 2016123456이고 다음 행 (2017 년)은 2017000001과 같아

이 키가 기본 키일 필요는 없으며 생성 날짜도 저장합니다. 아이디어는이 '표시 ID'가 독특하고 (따라서 쿼리 할 수 ​​있음) 연도별로 그룹화 할 수 있다는 것입니다.

모든 레코드가 삭제되지는 않습니다. 그러나 나는 그런 것에 대해 방어 적으로 코드를 작성하는 경향이 있습니다.

올해 새로운 행을 삽입 할 때마다 최대 ID를 쿼리하지 않고이 ID를 만들 수있는 방법이 있습니까?

아이디어 :

  • 그 해 CreateNewInvoiceSPMAX가치를 얻는 A (yucky)
  • 정확하게이 작업을 수행 할 수있는 몇 가지 마법 기능이 내장되어 있습니다.
  • IDENTITY또는 DEFAULT선언 에서 UDF 또는 무언가를 지정할 수 있음 (??)
  • 사용하는보기 PARTITION OVER + ROW()(삭제 된 문제)
  • 에 트리거 INSERT(여전히 MAX쿼리 를 실행해야 합니다 :()
  • 매년 백그라운드 작업으로 매년 삽입 된 MAX로 테이블을 업데이트했습니다.

모두 이상적이지 않습니다. 아이디어 나 변형은 환영합니다!


좋은 답변이 있지만 연도가 있다면 PK로 id를 선택하면 max가 매우 빠릅니다.
paparazzo

select max id 쿼리를 사용하는 것이 일반적입니다. 사용하십시오.
Uğur Gü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 FORA의 CASE문 (I 시도)
DarcyThomas

8

seed = 2016000000으로 ID 필드를 작성하려고 했습니까?

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

이 씨앗은 매년 자동 증분해야합니다 (예 : 2017/1/1 밤)

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

그러나 나는 이미 디자인에 문제가 있다고 생각합니다.


2
다른 문제는 기록이 시간순으로 나타나지 않는 경우입니다. 이것이 사실이라면 정체성은 갈 길이 아닐 것입니다.
Daniel Hutmacher

@LiyaTansky 제 경우에는 연간 50 만 개의 레코드 만 있어야한다는 말을 들었습니다. 그러나 나는 1kk의 행으로 부서지기 쉽다는 것에 대해 당신이 의미하는 바를 얻습니다
DarcyThomas

1

이 시나리오에서 내가 한 일은 연도에 10 ^ 6을 곱하고 시퀀스 값을 추가하는 것이 었습니다. 이것은 (작은) 지속적인 오버 헤드로 계산 된 필드를 필요로하지 않는 이점이 있으며 필드는로 사용될 수 있습니다 PRIMARY KEY.

가능한 두 가지 문제점이 있습니다.

  • 곱셈기가 소진되지 않도록 충분히 커야합니다.

  • 시퀀스 캐싱으로 인해 간격이없는 시퀀스를 보장 할 수 없습니다.

저는 SQL Server 전문가는 아니지만 201x 00:00:00에서 트리거하도록 이벤트를 설정하여 시퀀스를 0으로 재설정 할 수 있습니다. 그것은 또한 Firebird에서 수행 한 작업입니다 (또는 Interbase입니까?).


1

편집 :이 솔루션은로드에서 작동하지 않습니다

나는 방아쇠 팬이 아니지만, 내가 해결할 수있는 것이 가장 좋습니다.

장점 :

  • 백그라운드 작업이 없습니다.
  • DisplayId에서 빠른 쿼리를 할 수 있습니다
  • 트리거는 이전 NNNNNN 부품 을 스캔 할 필요가 없습니다.
  • 매년 NNNNN 부분을 다시 시작합니다
  • 연간 100000 개가 넘는 행이있는 경우 작동합니다
  • 나중에 계속 작동하기 위해 스키마 업데이트 (예 : 시퀀스 재설정)가 필요하지 않습니다.

편집 : 단점 :

  • 로드시 실패 (도면으로 돌아 가기)

(나는 그들의 대답에서 영감을 얻은 @gbn에게 감사합니다) (모든 피드백 및 명백한 실수를 지적 :)

새로운 COLUMNs 및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

나는 이것을하지 않는 것이 좋습니다. 가벼운 하중을 받으면 교착 상태가 발생하여 인서트 고장이 발생할 수 있습니다. 사본을 더미 데이터베이스에 넣고 한 번에 수십 개의 스레드로 복사하여 삽입 (및 선택 / 업데이트 / 삭제)을 수행하여 어떤 일이 발생하는지 확인 했습니까?
Cody Konior

@CodyKonior 근본적으로 결함이 있거나 약간의 신중한 잠금으로 부활 할 수 있습니까? 그렇지 않다면 어떻게 문제에 접근 하시겠습니까?
DarcyThomas

흠. 스레드 10 개를 실행했습니다. 그것이 데드락인지 확실하지 않지만, 경쟁 조건이 있습니다. 이전 행 트리거가 완료되기 전에 하나의 트리거가 완료되는 위치 이로 인해 많은 NULL값이 입력됩니다. 드로잉 보드로 돌아 가기 ...
DarcyThomas

재난을 피했다 :-) 나의 비밀은 약 5 년 전에 내가 한 일에 대한 패턴을 인식했다는 것이다. 다음 시퀀스를 찾기 위해 트리거 내부의 테이블을 스캔하는 방식이로드 상태에서 물건을 트립한다는 것을 알고 있습니다. 어떻게 해결했는지 기억이 나지 않지만 나중에 확인할 수 있습니다.
Cody Konior

@CodyKonior 나는 그것이 스캔을하고 있다고 생각하지는 않지만 ( ON Previous.Id = (I.Id -1) 탐색해야하지만) 그래도 여전히 작동하지 않습니다. 삽입 트리거 중에 테이블 (?)을 잠글 수 있다면 작동한다고 생각합니다. 그러나 그것은 코드 냄새처럼 들립니다.
DarcyThomas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.