I sinonimi in SQL Server Di Gianluca Negrelli L'identificazione di un oggetto in SQL Server necessita sempre di un riferimento alla gerarchia che lo contiene. Al vertice della gerarchia si posiziona il server mentre in coda troviamo l'oggetto desiderato. In termini formali la sintassi da utilizzare è la seguente server.database.schema_name.object_name Nella maggior parte dei casi l'individuazione di un oggetto necessita del solo nome (object_name) in quanto SQL Server propone come risolutore di default del percorso, il server corrente, il database corrente e lo schema corrente. Ecco perché una semplice selezione sulla tabella Anagrafica viene risolta sia con la sintassi completa che con quella compressa -- Sintassi completa SELECT * FROM Server1.Ordini.dbo.Anagrafica -- Sintassi compressa SELECT * FROM Anagrafica I sinonimi, introdotti per la prima volta in SQL Server 2005, rappresentano dei costrutti il cui unico scopo è quello di accedere ad oggetti esistenti attraverso alias. In altre parole se dal Server2 è necessario selezionare i record della tabella Anagrafica residente sul Server1 o si sta utilizzando il percorso gerarchico completo visto poc'anzi, è possibile accorciare il testo necessario mappando attraverso un sinonimo la tabella. Il risultato sarà quello di poter accedere alla tabella Anagrafica con una sintassi più compatta. Un po' di pratica Per capire quali siano le caratteristiche operative dei sinonimi dobbiamo immaginare un ambiente su cui poter effettuare i nostri test. Prevediamo quindi una struttura che si componga di due database: nel primo saranno contenuti i dati consolidati mentre nell'altro quelli in lavorazione. La scelta di usare due database separati per le diverse tipologie di dati piuttosto che tabelle distinte residenti in uno stesso database, non è infrequente e rappresenta un buon sistema per mantenere divise anche le logiche di lavoro dei due ambienti. Il nostro primo database conterrà i dati del gestionale aziendale, completamente gestito attraverso un'applicazione di back office, mentre il secondo conterrà gli ordini dei nostri clienti che si trovino in una fase di lavorazione non definitiva. Una volta che un ordine viene consolidato, una procedura di importazione lo migra al database principale e lo elimina dal database di lavoro. Chiamiamo i nostri database rispettivamente Gestionale e Ordini. Il database Gestionale contiene, tra le altre cose, l'anagrafica dei nostri clienti che ritroviamo nell'omonima tabella. La tabella Anagrafica viene giornalmente integrata e modificata dall'ufficio amministrazione e ogni singolo cambiamento deve essere subito reso disponibile all'applicazione che si occupa della gestione del database Ordini. A questo punto è necessario decidere se creare una tabella Anagrafica speculare nel database Ordini, con i conseguenti problemi di sincronizzazione, o se riferirsi direttamente alla tabella Anagrafica originale. Naturalmente, ai fini della nostra trattazione, la scelta ricadrà sul puntamento diretto. Eseguiamo una query di selezione per vedere come l'accesso da un database agli oggetti di un altro database sia cosa semplice. SELECT * FROM Gestionale.dbo.Anagrafica Gestionale è naturalmente il nome del nostro database, dbo è lo schema con cui accediamo all'oggetto e Anagrafica è la tabella. In questo caso, risiedendo i due database sullo stesso server, non c’è bisogno di specificare anche il vertice della gerarchia di accesso, che è l'identificatore del server (server.database.schema_name.object_name), perché SQL Server si occupa autonomamente del completamento utilizzando il valore corrente (Server1). Creazione di un sinonimo La creazione di un sinonimo che risieda nel database Ordini e che punti alla tabella Anagrafica del database Gestionale permette di accedere alla tabella remota con una sintassi più concisa e chiara. CREATE SYNONYM dbo.Anagrafica FOR Gestionale.dbo.Anagrafica GO SELECT * FROM Anagrafica Modifica dei sinonimi Un sinonimo non può essere modificato attraverso il costrutto ALTER ma può essere eliminato attraverso l'istruzione DROP. Il sinonimo definito precedentemente può essere quindi modificato solo attraverso la sua cancellazione e ricreazione. DROP SYNONYM Anagrafica Vincoli I sinonimi possono essere usati per mappare i seguenti oggetti. Tabelle Viste Stored procedure (sia CLR che SQL) Funzioni scalari, tabellari e aggregate (sia CLR che SQL) Procedure di filtro per repliche (Replication filter procedures) Extended Stored procedure Inoltre i sinonimi possono essere utilizzati nelle seguenti istruzioni TSQL: SELECT sub-Select UPDATE INSERT DELETE EXECUTE Un altro vincolo alla creazione dei sinonimi riguarda la nomenclatura utilizzata. Non può essere creato un sinonimo con un nome di oggetto già esistente nel database in quanto esso vive all'interno di uno schema e come gli altri oggetti appartenenti ad uno schema, deve poter essere individuato univocamente. La natura dei sinonimi fa sì che essi vengano risolti a run-time a differenza di ogni altro oggetto esistente in SQL Server. E' possibile infatti creare un sinonimo per un oggetto non (ancora) esistente nel database. Prendiamo la seguente istruzione: CREATE SYNONYM dbo.Anagrafica2 FOR Gestionale.dbo.Anagrafica2 La creazione del sinonimo andrà a buon fine malgrado l'oggetto Gestionale.dbo.Anagrafica2 non esista. Naturalmente l'istruzione di selezione su Anagrafica2 ritornerà un errore. SELECT * FROM Anagrafica2 Msg 208, Level 16, State 1, Line 1 Invalid object name 'Anagrafica2'. La ragione di questo "strano comportamento" di SQL Server va cercata nel modo in cui i sinonimi funzionano. Essenzialmente si tratta di costrutti che attivano il collegamento all'oggetto puntato solo in late binding quindi al momento della chiamata e non al momento della creazione. Ritengo che la scelta nasca dal fatto che il sinonimo è tipicamente un legame tra database diversi e che non si vogliano creare correlazioni troppo strette tra entità che sono per loro natura separate. Inoltre l’assenza di legami dei sinonimi verso gli oggetti puntati permette l’utilizzo degli stessi in task di refactoring della struttura dati altrimenti impossibili. Utilizzi pratici dei sinonimi Scorciatoia Il primo e più immediato uso che i sinonimi ispirano è senz'altro quello della scorciatoia. Se nel nostro codice SQL dobbiamo spesso riferirci ad un oggetto residente su un diverso DB come ad esempio l'anagrafica del database Ordini, la creazione di un sinonimo ci risparmia dall'imputazione continua dell'intero percorso dell'oggetto e la nostra query di selezione si riduce alla già vista: SELECT * FROM Anagrafica Ma proviamo ad approfondire la questione per capire se esistono altri vantaggi meno evidenti nell'utilizzo dei sinonimi. Stabilizzatore I sinonimi possono essere utilizzati come "stabilizzatori" di aree del database che siano in una fase ancora instabile e non definitiva. La caratteristica dei sinonimi di essere attivati solo in late binding permette di creare una struttura di riferimenti stabile, garantita dai sinonimi stessi, cui appoggiarsi per la consultazione dei dati, mentre la struttura di riferimento può variare anche di molto senza per questo dover modificare altre aree del database. Siamo cioè di fronte ad uno strumento che ci permette di costruire un layer di astrazione che separa le logiche di interrogazione da quelle di costruzione del database. Pensiamo ad esempio ad un sinonimo che punti una tabella di un DB residente su un server differente. Uno spostamento del DB verso un'altra macchina non obbliga alla riscrittura di tutto il codice di interrogazione ma alla distruzione e ricreazione del solo sinonimo. Questa caratteristica dei sinonimi permette altresì di procedere ad operazioni di refactoring della struttura del DB senza la necessità di alterare la logica di consultazione. Se ad esempio la nostra tabella Anagrafica soffrisse di problemi di scarsa normalizzazione, potrebbe essere sostituita da una serie di nuove tabelle. Il sinonimo Anagrafica del database Ordini andrebbe a questo punto ricreato per puntare ad una vista che rimetta assieme le tabelle normalizzate. I chiamanti, puntando al sinonimo Anagrafica, non si accorgerebbero della sostituzione della sorgente dei dati né della modifica della struttura del database, né del fatto di puntare ad una vista piuttosto che alla tabella originale. Security Attraverso l'uso di un sinonimo, l'oggetto puntato non è completamente disponibile. Infatti sono precluse tutte le operazioni di modifica e/o cancellazione dell'oggetto. L'aggiunta di una colonna alla tabella Anagrafica funziona solo se è la tabella originale ad essere puntata e non il sinonimo. L'istruzione: USE Ordini ALTER TABLE Anagrafica ADD Test varchar(1) ha come conseguenza il lancio dell'eccezione: "Cannot alter 'Anagrafica' because it is not a table." mentre l'istruzione: USE Ordini ALTER TABLE Gestionale.dbo.Anagrafica ADD Test varchar(1) va naturalmente a buon fine. In altre parole l'utente che utilizza il sinonimo si ritrova implicitamente autorizzato all'accesso alla tabella Anagrafica in selezione, inserimento e cancellazione dei record ma non in modifica della struttura della tabella. Va sottolineato comunque che l'utilizzo dei sinonimi come gestori di autorizzazioni sugli oggetti non è un approccio condivisibile né auspicabile. Prestazioni Dal punto di vista delle prestazioni non ci sono particolari controindicazioni nell'uso dei sinonimi. I piani di esecuzione di SQL Server, nel caso di uso di un sinonimo, non aggiungono nessun livello di elaborazione rispetto all'uso diretto dell'oggetto puntato. Un esempio d'uso Come abbiamo visto i sinonimi sono costrutti comodi ma apparentemente mai necessari. Vediamo un contesto invece in cui diventano insostituibili. Spesso a fronte di un ambiente effettivo, le nostre applicazioni devono essere preventivamente rilasciate anche in un ambiente di test. I database seguono di norma la suddivisione degli ambienti applicativi per cui l'applicazione di test riferirà al database di test mentre l'applicazione effettiva all'effettivo. Nel caso più semplice le applicazioni, che conoscono il loro ambito di esecuzione, si collegano ai database attraverso stringhe di connessione differenti, coordinando di fatto l’attribuzione del database di test all’ambiente di test e viceversa. Ipotizziamo però un caso particolare. La nostra applicazione è costituita di due database: Gestionale e Ordini. Il layer dati è costituito da Stored procedure che sono presenti sia in uno che nell'altro database. La SP che si occupa della migrazione e consolidamento degli ordini in compilazione verso il database Gestionale, risiede nel DB Ordini e contiene alcune istruzioni come queste: ... INSERT INTO Gestionale.dbo.DocumentoOrdini(Numero, Data) SELECT '123_2010', GETDATE() ... Già a questo livello potremmo apprezzare l'utilità di un sinonimo per accorciare il riferimento alla tabella DocumentoOrdini residente sull’altro database, ma procediamo con ordine. Siccome la nostra applicazione necessita di un completo ambiente di test i database coinvolti saranno quattro: Gestionale e GestionaleTest, Ordini e OrdiniTest A questo punto nasce un problema. C'è l'esigenza di mantenere la nostra SP di migrazione perfettamente allineata nei due ambienti (e naturalmente anche in sviluppo) per facilitare il processo di deploy dall'ambiente di testing a quello effettivo, ma, abbiamo visto, nella SP c'è l'esigenza di puntare al database Gestionale da parte di Ordini e GestionaleTest da parte di OrdiniTest. Esistono varie possibilità che evitano un deploy particolare per ogni ambiente: a) l'applicazione chiamante comunica il tipo di ambiente in cui sta eseguendo, alla SP attraverso un parametro. La SP di conseguenza cambierà in questo modo: … IF (@Ambiente = 'effettivo') INSERT INTO Ordini.dbo.DocumentoOrdini(Numero, Data) SELECT '123_2010', GETDATE() ELSE INSERT INTO OrdiniTest.dbo.DocumentoOrdini(Numero, Data) SELECT '123_2010', GETDATE() ... b) la SP stessa attraverso un check del nome del database corrente, riconosce l'ambiente in cui sta eseguendo sollevando dal compito l'applicazione client. DECLARE @Ambiente varchar(10) IF (db_name() like '%Test') SET @Ambiente = 'test' ELSE SET @Ambiente = 'effettivo' IF (@Ambiente = 'effettivo') ... c) senza infarcire il codice di istruzioni condizionali IF è anche possibile far ricorso alla costruzione dinamica delle query sia nel caso di parametro passato dall'applicazione (caso a) sia nel caso di nome del db costruito dalla SP (caso b) DECLARE @DBName varchar(10) IF (db_name() LIKE '%Test') SET @DBName = 'OrdiniTest' ELSE SET @DBName = 'Ordini' EXEC ('INSERT INTO ' + @DBName + '.dbo.DocumentoOrdini(Numero, Data) SELECT '123_2010', GETDATE() Come si può facilmente intuire nessuna di queste proposte rappresenta una soluzione elegante e soddisfacente, se non altro perché la gestione di ambienti differenti diventa invasiva e sconfina all'interno delle logiche del nostro layer di interrogazione. Si pensi poi al caso in cui ci venga richiesto di approntare un altro ambiente (per presentare una demo ai clienti ad esempio). Dovremmo ritoccare tutte le SP dotate di questi artifici per implementare la nuova redirezione, aumentando inutilmente e pericolosamente il codice necessario. Avrete già capito che la soluzione ideale ricade invece sull'uso dei sinonimi. Creando due sinomini sintatticamente uguali ma con puntamenti diversi ai database Ordini e OrdiniTest avremo modo di accedere alla nostra tabella DocumentoOrdini nello stesso identico modo sia da un database che dall'altro. USE Ordini CREATE SYNONYM dbo.DocumentoOrdini FOR Gestionale.dbo.DocumentoOrdini GO USE OrdiniTest CREATE SYNONYM dbo.DocumentoOrdini FOR GestionaleTest.dbo.DocumentoOrdini GO Con questo piccolo accorgimento la nostra SP sia in ambiente effettivo che in ambiente di test, così come in sviluppo e in demo, tornerà alla sua forma originaria beneficiando anche della sintassi ottimizzata del sinonimo. ... INSERT INTO DocumentoOrdini(Numero, Data) SELECT '123_2010', GETDATE() ... Bibliografia http://msdn.microsoft.com/en-us/library/ms187552.aspx