Şu biçimdeki 'Id': NYNNNN parçası her yıl yeniden başlatılan YYYYNNNNNN


11

Fatura tablosundaki her kaydın YYYYNNNNNN'ye benzeyen bir kimliği olması gereken bir iş gereksinimim var.

NNNNNN parçasının her yılın başında yeniden başlatılması gerekir. Yani 2016'da girilen ilk satır 2016000001 ve ikincisi 2016000002 vb.Gibi görünecektir.

Birincil anahtar olmak için bu kimliğe ihtiyacım yok ve oluşturma tarihini de saklıyorum. Fikir, bu 'display id' benzersizdir (bu yüzden sorgulayabilirim) ve insan grubu yapabiliyor.

Herhangi bir kaydın silinmesi olası değildir; Ancak, böyle bir şeye karşı defansif kodlama eğilimi olacaktır.

Yeni bir satır eklemek her zaman bu yıl max id sorgulamak zorunda kalmadan bu kimliği oluşturmak için herhangi bir yolu var mı?

fikirler:

  • A CreateNewInvoiceSP, MAXo yılın değerini alır (yucky)
  • Tam olarak bunu yapmak için bazı büyülü yerleşik özellik (doğru hayal edebilirim)
  • IDENTITYVeya DEFAULTbeyanında bazı UDF veya bir şey belirtebilme (??)
  • Kullanan bir görünüm PARTITION OVER + ROW()(silinen sorunlu olur)
  • Bir tetikleyici INSERT(yine de bazı MAXsorgu çalıştırmanız gerekir :()
  • Yıllık bir arka plan işi, eklenen her yıl için MAX ile bir tablo güncelledi hangi sonra ... Bir şey ?!

Hepsi biraz ideal değil. Herhangi bir fikir veya varyasyon olsa hoş geldiniz!


Bazı iyi cevaplarınız var, ancak yılınız varsa, bir PK olarak id, sonra max'ı seçmek oldukça hızlıdır.
paparazzo

select max id sorgusu kullanmak yaygın bir uygulamadır. bunu kullan.
Uğur Gümüşhan

Yanıtlar:


17

Alanınızda 2 öğe var

  • Yıl
  • Otomatik artan bir sayı

Bir alan olarak depolanmaları gerekmez

Misal:

  • Varsayılan değeri olan bir yıl sütunu YEAR(GETDATE())
  • Bir diziye dayalı bir sayı sütunu.

Ardından bunları birleştirerek hesaplanmış bir sütun oluşturun (uygun biçimlendirmeyle). Sıra, yıl değiştiğinde sıfırlanabilir.

SQLfiddle'daki örnek kod : * (SQLfiddle her zaman çalışmaz)

-- 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
Belki yılda bir sıraya sahip olmak daha temizdir. Bu şekilde DDL'nin normal işlemlerin bir parçası olarak yürütülmesine gerek kalmaz.
usr

@gbn Yani SEQUENCE her yılın başında yeniden başlatmak için bir arka plan işine ihtiyacım olur mu?
DarcyThomas

@usr Ne yazık ki NEXT VALUE FORbir CASEifadede kullanamazsınız (denedim)
DarcyThomas

8

Seed = 2016000000 ile bir kimlik alanı oluşturmayı düşündünüz mü?

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

Bu tohum her yıl otomatik olarak büyütülmelidir, örneğin 2017/1/1 gecesi planlamanız gerekir

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

Ama zaten tasarımla ilgili problemler görüyorum, örneğin: Ya milyon kaydınız varsa?


2
Başka bir sorun, kayıtların kronolojik olarak görünmemesidir. Durum böyleyse kimlik muhtemelen bir yol değildir.
Daniel Hutmacher

@LiyaTansky Benim durumumda yılda sadece 50 bin kayıt olması gerektiği söylendi. Ama 1kk satırları ile kırılgan olmanın ne demek istediğini anlıyorum
DarcyThomas

1

Bu senaryoda yaptığım, yılı 10 ^ 6 ile çarpmak ve sıra değerini buna eklemekti. Bunun avantajı, (küçük) devam eden ek yükü ile hesaplanan bir alan gerektirmemesidir ve alan a olarak kullanılabilir PRIMARY KEY.

İki olası gotcha vardır:

  • çarpanınızın asla tükenmeyecek kadar büyük olduğundan emin olun ve

  • dizinin önbelleğe alınması nedeniyle boşluklar olmadan bir dizi garanti edilmez.

SQL Server'da uzman değilim, ancak dizinizi sıfıra sıfırlamak için 201x 00:00:00 saatinde tetiklemek için bir olay ayarlayabilirsiniz. Aynı zamanda Firebird'de de yaptım (ya da Interbase miydi?).


1

Düzenleme: Bu çözüm yük altında çalışmıyor

Tetikleyicilerin hayranı değilim, ama bu en iyi şekilde çalışabilirim.

Artıları:

  • Arka plan işi yok
  • DisplayId'de hızlı sorgular yapabilir
  • Tetikleyicinin önceki NNNNNN parçasını taramasına gerek yoktur
  • NNNNN bölümünü her yıl yeniden başlatacak
  • Yılda 100.000'den fazla satır varsa çalışacaktır
  • Gelecekte çalışmaya devam etmek için şema güncellemeleri (ör. Dizi sıfırlamaları) gerektirmez

Düzenleme: Eksileri:

  • Yük altında başarısız olur (çizim tahtasına geri dön)

(Cevabından biraz ilham aldıkça @ gbn'ye kredi verin)

Birkaç yeni COLUMNs ve birINDEX

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

Yeni ekle 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

Bunu yapmamanızı tavsiye ederim. Hafif yük altında kaldıktan sonra tıkanma ve ekleme hatalarına neden olması muhtemeldir. Kukla bir veritabanına bir kopya koydunuz ve ne olduğunu görmek için bir kerede birkaç düzine iplikle (ve belki de seçer / güncellemeler / siler) çekiçlediniz mi?
Cody Konior

@CodyKonior temelde kusurlu mu yoksa biraz mantıklı kilitleme ile diriltilebilir mi? Değilse, soruna nasıl yaklaşırsınız?
DarcyThomas

Hmmm. 10 iplik ile koştu. Ölü kilit olup olmadığından emin değilim, ama bazı yarış koşulları elde ediyorum. Bir tetikleyicinin tamamlandığı yerde, önceki satırlar tetikleyici bitmeden. Bu, bir grup NULLdeğerin girilmesine yol açar . Çizim tahtasına geri dön ...
DarcyThomas

Felaket o zaman önlendi :-) Sırrım, yaklaşık beş yıl önce yaptığım bir şey için kalıbı tanımıştım. Sadece bir sonraki diziyi aramak için tetiğin içindeki tabloyu tarama şeklinizin yük altında bir şeyleri açtığını biliyorum. Nasıl çözdüğümü hatırlamıyorum ama daha sonra kontrol edebilirim.
Cody Konior

@CodyKonior Ben bir tarama yaptığını sanmıyorum ( ON Previous.Id = (I.Id -1) sadece aramak gerekir), ama evet hala çalışmıyor. Ekleme ve tetikleme sırasında tabloyu (?) Kilitleyebilseydim işe yarayacağını düşünüyorum. Ama bu bir kod kokusu gibi geliyor.
DarcyThomas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.