Primary key: Doc ID o Codice?

Nella progettazione della struttura di un DB, una delle scelte da effettuare è quella della definizione della PK.

DOC ID
Volendo trattare i dati attraverso Classi DO, InDe mette a disposizione diversi servizi, come:

  • Identificazione documenti
  • Allegati
  • Sincronizzazione
  • ecc…

Alcuni di questi richiedono espressamente che la PK sia definita come DocID (Identificazione documentale), in altri non è strettamente necessario ma la scelta del DocID facilita grandemente l’utilizzo del servizio (Sincronizzazione).

CODICE
D’altra parte, in molti ambiti, l’operatore è abituato a trattare gli elementi che manipola tramite Codice (codice articolo, codice cliente…).
Pensiamo all’imputazione rapida di un DDT, in cui l’operatore smaliziato conosce la maggior parte dei codici più utilizzati ed è abituato a riferirsi ad essi. Eliminare il codice e costringerlo a imputare i dati tramite una ricerca per descrizione diventa pressoché inaccettabile.

Quale scelta effettuare?
Nel nostro ultimo progetto abbiamo scelto una soluzione mista:

  • Tabelle come Iva, Province, Operatori: Codice
  • Tabelle come Clienti, Articoli: DocID + Codice

L’idea è riuscire ad avere il meglio delle due soluzioni.
Nella soluzione Codice è stata valutata una bassa probabilità di modifica del codice e un basso vantaggio nell’utilizzo del DocID.
Nella soluzione DocID + Codice abbiamo la che la PK è formata dal solo DocID, avendo poi un indice univoco per Codice. In questo caso, la valutazione è stata l’opposto della precedente: una più alta probabilità di modifica del codice e dell’utilizzo dei servizi documentali.

Così facendo, l’operatore ha la possibilità di lavorare riferendosi sempre tramite Codice, mentre il DocID mi permette di alleggerire il lavoro negli ambiti in cui è richiesto.
Non contenti, in alcune tabelle (per fortuna poche), abbiamo sia il DocID che il Codice, per facilitare le query effettuate al di fuori dell’applicazione.

Conclusioni
Finora questa soluzione ha portato ad accontentare un po’ tutti e non ha creato grossi problemi.
Eppure non mi convince del tutto :expressionless:.

Voi cosa ne pensate?
Quali soluzioni adottate?

5 Mi Piace

Grazie @r.bianco concordo con la tua esperienza e da formatore ho visto questo utilizzo delle PK diverse volte durante le sessioni di formazione con i nuovi utenti di Instant Developer Foundation.

1 Mi Piace

Personalmente, ormai affido tutte le chiavi primarie al DocID (UUID in IDC) e indicizzo in maniera opportuna i campi tipo Codice, quando ce ne sono, relegandone l’uso alla sola interfaccia per semplificare la ricerca agli utenti. Il vantaggio di adottare una sola metodologia senza dovermi ricordare dove per la PK ho usato un campo piuttosto che un altro per me vince sugli altri, tenuto anche conto che IDC è in grado di compilare automaticamente le query corrette pure nel caso di documenti collegati tra loro. Tirate le somme, come ho detto per me questo è il sistema più pratico in ogni scenario.

3 Mi Piace

Ciao. Noi usiamo docId per tutto, anche se un esperienze precedenti (prima di adottare foundation) usavamo solo PK intere contatori (anche se gestite a mano e non con identity).

Usando docId si delega in qualche modo a foundation il problema delle PK, di certo quando si scrive una query a mano scrivere “where ID= ''f4_:kò?()_çòasjiwjì?” è spiacevole, però a livello di PK, per integrità referenziale un docId è ottimo e poi anche per la sincronizzazione è ineccepibile.

1 Mi Piace

Non usate mai codici per identificare un elemento? Il classico codice articolo per esempio. Come vi approcciate a questo?

Sì, usiamo codici ma non li mettiamo nella PK, ne validiamo l’unicità nell’OnValidate. Non c’è un conrollo a livello DB ma solo applicativo, quindi bug a parte per l’utente è una sensazione simile ad avere la PK codice. Ciao.

1 Mi Piace

Utilizzando un codice univoco che non sia la PK della tabella di solito io metto nel database un indice unique per quel codice così anche inserimenti fatti direttamente dal manager de database non mi creano duplicati.

4 Mi Piace

Buona considerazione, in effetti pensare che la validazione DO sia a prova di bomba è una leggerezza: l’indice dul DB ci tutela da tutte le operazioni “a picconate”. Ciao!

1 Mi Piace

Esatto. Inoltre l’indice velocizza le qry basate su codice.

Ciao
come regola generale nella progettazione di un database tendo sempre a definire una PK tecnica (docid o sequence) che non ha legami con l’informazione contenuta nella tabella per i seguenti motivi:

  • Responsabilità: la PK mi serve per definire vincoli relazionali tra le tabelle, se devo identificare univocamente un record in base ai suoi dati informativi definisco una o più “chiavi logiche” (indice unique). Nel tempo la chiave logica potrebbe cambiare (contenuto o campi coinvolti) ed è buona cosa a livello di manutenzione non aver propagato tale codice nelle tabelle collegate.

  • Performance: quando viene usata la PK per l’accesso a delle righe sia direttamente (filtro nella where specificando il valore) sia indirettamente (tramite clausola di join) alcuni dbms sono più veloci con certi tipi di dato piuttosto che con altri. Sapere di poter modificare il tipo di chiave in funzione della situazione mi dà più flessibilità rispetto alla scelta di un codice il cui tipo non è modificabile.

All’inizio avevo un po’ di remore ad utilizzare il docid di INDE ma adesso è la mia scelta di default e rispetto ai sequence ci sta aiutando a gestire situazioni particolari ad esempio portare dati da produzione a test senza problemi di gestione di chiavi diverse o duplicate (non mi dilungo, se qualcuno è interessato posso spiegare meglio cosa intendo).

3 Mi Piace

Grazie mille, e benvenuto.
Quindi, ad esempio, nelle righe dell’ordine non metterai il codice articolo ma il suo docid. Corretto?
C’è qualche caso in cui, oltre al docid metteresti anche il codice?

1 Mi Piace

Grazie del benvenuto!
Si esatto nell’ordine metterei solo il docid e non il codice.
Non metto mai il codice nella tabella mentre nella classe documentale quasi sempre, ed è una funzionalità di inde che apprezzo molto, modificando la master query e mettendo in join le tabelle da cui voglio estrarre informazioni “derivate” mappandole su campi creati ad hoc.

3 Mi Piace

Ciao a tutti.

Volevo riprendere l’argomento in quanto anche noi all’inizio abbiamo avuto un po’ di perplessità su come approcciare il problema.
Premetto che abbiamo il 99% delle soluzioni senza device mobile (per ora) per cui il problema della sincronizzazione non è molto sentito.
Inizialmente e per il progetto SMART (il nostro nuovo ERP) abbiamo adottato l’utilizzo del ID di SQL progressivo contatore, ma nei nuovi progetti adotteremo il DocID di INDE.
Questa doppia scelta è stata dettata dalle necessità.
La gestione di SMART (il nostro ERP in Saas) nasce da un progetto inizialmente sviluppato con CodeOnTime, dove le relazioni sono per ID (contatore SQL), per cui abbiamo ereditato lo schema del DB completo con tutte le relazioni sull’ID, e cambiare lo schema, stored procedure e funzioni SQL era un lavoraccio, nonché il fatto che alcune parti del SW “vecchio” dovevano convivere su alcuni clienti.

C’è però un’altra “bega” che ci assilla e provo a spiegare meglio:
INDE ha un problema noto e riconosciuto, che se sulle tabelle ci sono definiti dei trigger SQL (e noi ne usiamo parecchi), e se si estendono le entità di base con delle proprie classi derivate da ID_DOCUMENT, nella propagazione delle proprietà si perde il valore dell’ultimo ID inserito in tabella, per cui si fa un macello quando si inseriscono i dati. Questo succede perché INDE per recuperare l’ultimo ID inserito usa lo variabile globale SQL @@IDENTITY anziché la funzione SCOPE_IDENTITY(). La prima viene valorizzata con l’ultimo ID inserito nella sessione, mentre la seconda ritorna il valore dell’ultimo ID della tabella. Per ovviare al problema (vedi anche nel forum) si vanno a scrivere due righe di codice nell’evento after save della classe così da valorizzare l’ID correttamente, e se il trigger è su una sola tabella problemi non ce ne sono, ma iniziano quando le tabelle dell’entità sono più di una (4 o 5 come nel nostro caso…) perché l’evento after save viene notificato per tutte le classi dell’entità nello stesso momento, quindi l’ID che si recupera non è detto che sia quello della classe padre.
Per farla breve il tutto si risolve utilizzando il DocID di INDE dove viene generato correttamente e propagato seguendo le relazioni delle PK.
Resta il problema che non si può fare manutenzione “extra INDE” ad esempio da SQL manager o da un’altra applicazione scritta con un linguaggio diverso INDE.
Per questo ci siamo scritti tre funzioni SQL per la generazione del DocID e la trasformazione del DocID in GUID e viceversa, così da poter fare in SQL quello che si fa in INDE…
Di seguito lo script di generazione di una vista che serve all’interno delle funzioni, dal momento che in una funzione SQL non si può richiamare la funzione di sistema NEWID()

      CREATE view [dbo].[vw_NewGuid]
      as
      SELECT NEWID() AS NewGuid         
      GO

– =============================================
– Author: MADA Service - Tamara Cocchi
– Create date: 2022-08-11
– Description: Funzione che converte un Doc ID di INDE in GUID
– =============================================
CREATE function [dbo].[DecodeDocID]
(
@DocId varchar(20)
)
returns varchar(36) as
BEGIN

declare @guid varchar(36) = ''

declare @restoPrecedente as bigint = 0
	
DECLARE @Counter INT ,
		@giro int = 1,
		@moltiplicatore INT = 0,
		@guidTemp varchar(36)
		
SET @Counter=1
WHILE ( @Counter <= LEN(@DocId))
BEGIN
	set @restoPrecedente = 0
	
	declare @subGiro int = 1
	WHILE @subGiro <= 5
		BEGIN

			SELECT @moltiplicatore = CASE @subGiro
				WHEN 1
					THEN 1
				WHEN 2
					THEN 85
				WHEN 3
					THEN 7225
				WHEN 4
					THEN 614125
				ELSE
					52200625
			END

			declare @valore bigint 
			SET @valore = (CONVERT(varbinary,SUBSTRING(@docID,@counter,1))-40)*CONVERT(BIGINT,@moltiplicatore)+@restoPrecedente	
			set @restoPrecedente = @valore
			SET @Counter  = @Counter  + 1

			SET @subGiro = @subGiro + 1
		END
	
	SET @guidTemp =  CONVERT(VARCHAR(1000), CONVERT(VARBINARY(4),CAST(@restoPrecedente AS BIGINT)),2)

	SET @guid = @guid + CASE @giro
							WHEN 1
								THEN @guidTemp + '-'
							WHEN 2
								THEN SUBSTRING(@guidTemp,5,4)+'-'+SUBSTRING(@guidTemp,1,4)+'-'
							WHEN 3
								THEN SUBSTRING(@guidTemp,7,2)+SUBSTRING(@guidTemp,5,2)+'-'+SUBSTRING(@guidTemp,3,2)+SUBSTRING(@guidTemp,1,2)
							ELSE
								SUBSTRING(@guidTemp,7,2)+SUBSTRING(@guidTemp,5,2)+SUBSTRING(@guidTemp,3,2)+SUBSTRING(@guidTemp,1,2)
						END
						
	SET @giro = @giro + 1
END


return  @guid	

END

– =============================================
– Author: MADA Service - Tamara Cocchi
– Create date: 2022-08-11
– Description: Funzione che converte un GUID in un Doc ID come INDE comanda
– =============================================
CREATE function [dbo].[EncodeDocID]
(
@guid varchar(36)
)
returns varchar(20) as
BEGIN

declare @DocId varchar(20) = ''

declare @numero32bit as bigint
	
DECLARE @Counter INT ,
		@giro int = 1,
		@tentativi INT = 0,
		@divisore INT = 0,
		@docIDTemp varchar(20) = ''

SET	@guid = REPLACE(@guid,'-','') 

SET @Counter=1
WHILE ( @Counter <= LEN(@guid))
BEGIN
	declare @stringa varchar (50) = ''
	SELECT @stringa = CASE @giro
		WHEN 1
			THEN SUBSTRING(@guid,@counter,8)
		WHEN 2
			THEN SUBSTRING(@guid,@counter+4,4)+SUBSTRING(@guid,@counter,4)
		ELSE
			SUBSTRING(@guid,@counter+6,2)+SUBSTRING(@guid,@counter+4,2)+SUBSTRING(@guid,@counter+2,2)+SUBSTRING(@guid,@counter,2)
	END
	
	SELECT @numero32bit = CONVERT(bigint, convert(varbinary, '0x' + @stringa, 1))

	set @docIDTemp = ''
	declare @subGiro int = 1
	WHILE @subGiro <= 5
		BEGIN

			SELECT @divisore = CASE @subGiro
				WHEN 1
					THEN 52200625
				WHEN 2
					THEN 614125
				WHEN 3
					THEN 7225
				WHEN 4
					THEN 85
				ELSE
					1
			END

			declare @valore char 
			SET @valore = CHAR(@numero32bit / @divisore + 40)		
			select @docIDTemp = @valore + @docIDTemp
			set @numero32bit = @numero32bit % @divisore	

			SET @subGiro = @subGiro + 1
		END

	SET @docID = @docID + @docIDTemp

	SET @giro = @giro + 1
	SET @Counter  = @Counter  + 8
END


return  @docID	

END

– =============================================
– Author: MADA Service - Tamara Cocchi
– Create date: 2022-08-11
– Description: Funzione che crea un nuovo un Doc ID come INDE comanda
– =============================================
CREATE function [dbo].[GetNewDocID]
()
returns varchar(20) as
BEGIN

declare @guid as varchar(36) = '',
		@DocId varchar(20) = ''

SELECT	@guid = NewGuid
FROM	[vw_NewGuid]

declare @numero32bit as bigint
	
DECLARE @Counter INT ,
		@giro int = 1,
		@trovato BIT = 0,
		@tentativi INT = 0,
		@divisore INT = 0,
		@docIDTemp varchar(20) = ''

WHILE @docID = '' and @tentativi < 100
	BEGIN

		SELECT @DocId = [dbo].[EncodeDocID](@guid)

		if CHARINDEX('\',@docID) > 0
			BEGIN
				SELECT	@guid = NewGuid
				FROM	[vw_NewGuid]
				set @docID = ''
			END

		set @tentativi = @tentativi + 1
	END

return  @docID	

END

3 Mi Piace

Ciao Giancarlo,
a memoria, non siamo mai incappati in questa casistica (fortunatamente!).

Oltre questo, anche noi abbiamo usato le store procedure che traducono GIUD in DocID, per mantenere allineati il DB del gestionale fatto con InDe (A) e il DB del gestionale fatto con un altro sistema di sviluppo (B). In pratica, durante l’inserimento di un record da parte di B, chiamiamo una stored function che genera un nuovo GIUD e lo traduce in DocID, per poi popolare un campo apposito.
Il record viene poi trasferito tramite WebAPI al gestionale A, e viene mantenuto allineato tramite questo campo.
Con il senno di poi, però, non penso sia una buona idea perché abbiamo limitato B nel dialogo con altri applicativi, essendo il DocID proprio di InDe. Avrebbe avuto più senso mantenere nel DB di B un GIUD, per poi tradurlo in fase di ricezione/trasmissione via WebAPI.

Ciao, r.bianco
SI certo è giusta l’osservazione. In quest’ottica B potrebbe avere un GUID e non un DocID.
Nel nostro caso però è il nostro DB che deve far coesistere anche Applicazioni non INDE, per cui è corretto (a mio avviso) generare ID come vuole INDE anche fuori da INDE.
Non so se mi sono spiegato…
Ciao…

1 Mi Piace