Domanda

Una delle "migliori pratiche" sta accedendo ai dati tramite procedure memorizzate. Capisco perché questo scenario è buono. La mia motivazione è il database diviso e la logica dell'applicazione (le tabelle possono essere modificate, se il comportamento delle procedure memorizzate è lo stesso), difesa per l'iniezione SQL (gli utenti non possono eseguire "selezionare * da some_tables", possono solo chiamare procedure memorizzate), e sicurezza (nella procedura memorizzata può essere "tutto" ciò che protegge, che l'utente non può selezionare / inserire / aggiornare / eliminare i dati, che non fa per loro).

Quello che non so è come accedere ai dati con filtri dinamici.

Sto usando MSSQL 2005.

Se ho una tabella:

CREATE TABLE tblProduct (
   ProductID uniqueidentifier -- PK
   , IDProductType uniqueidentifier -- FK to another table
   , ProductName nvarchar(255) -- name of product
   , ProductCode nvarchar(50) -- code of product for quick search
   , Weight decimal(18,4)
   , Volume decimal(18,4)
)

quindi dovrei creare 4 stored procedure (creare / leggere / aggiornare / eliminare).

La procedura memorizzata per " create " è facile.

CREATE PROC Insert_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   INSERT INTO tblProduct ( ProductID, IDProductType, ... etc .. ) VALUES ( @ProductID, @IDProductType, ... etc ... )
END

La procedura memorizzata per " elimina " è anche facile.

CREATE PROC Delete_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
    DELETE tblProduct WHERE ProductID = @ProductID AND IDProductType = @IDProductType AND ... etc ...
END

La procedura memorizzata per " aggiorna " è simile a " elimina " ;, ma non sono sicuro che questo sia il modo giusto, come farlo. Penso che l'aggiornamento di tutte le colonne non sia efficiente.

CREATE PROC Update_Product( @ProductID uniqueidentifier, @Original_ProductID uniqueidentifier, @IDProductType uniqueidentifier, @Original_IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   UPDATE tblProduct SET ProductID = @ProductID, IDProductType = @IDProductType, ... etc ...
      WHERE ProductID = @Original_ProductID AND IDProductType = @Original_IDProductType AND ... etc ...
END

E l'ultima procedura memorizzata per " leggi " è un piccolo mistero per me. Come passare i valori di filtro per condizioni complesse? Ho qualche suggerimento:

Utilizzo del parametro XML per passare dove condition:

CREATE PROC Read_Product ( @WhereCondition XML ) AS BEGIN
    DECLARE @SELECT nvarchar(4000)
    SET @SELECT = 'SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'

    DECLARE @WHERE nvarchar(4000)
    SET @WHERE = dbo.CreateSqlWherecondition( @WhereCondition ) --dbo.CreateSqlWherecondition is some function which returns text with WHERE condition from passed XML

    DECLARE @LEN_SELECT int
    SET @LEN_SELECT = LEN( @SELECT )
    DECLARE @LEN_WHERE int
    SET @LEN_WHERE = LEN( @WHERE )
    DECLARE @LEN_TOTAL int
    SET @LEN_TOTAL = @LEN_SELECT + @LEN_WHERE
    IF @LEN_TOTAL > 4000 BEGIN
        -- RAISE SOME CONCRETE ERROR, BECAUSE DYNAMIC SQL ACCEPTS MAX 4000 chars
    END

    DECLARE @SQL nvarchar(4000)
    SET @SQL = @SELECT + @WHERE

    EXEC sp_execsql @SQL
END

Ma penso che la limitazione di "4000" i caratteri per una query sono brutti.

Il prossimo suggerimento sta usando le tabelle di filtro per ogni colonna. Inserire i valori del filtro nella tabella dei filtri e quindi chiamare la procedura memorizzata con ID dei filtri:

CREATE TABLE tblFilter (
   PKID uniqueidentifier -- PK
   , IDFilter uniqueidentifier -- identification of filter
   , FilterType tinyint -- 0 = ignore, 1 = equals, 2 = not equals, 3 = greater than, etc ...
   , BitValue bit , TinyIntValue tinyint , SmallIntValue smallint, IntValue int
   , BigIntValue bigint, DecimalValue decimal(19,4), NVarCharValue nvarchar(4000)
   , GuidValue uniqueidentifier, etc ... )

CREATE TABLE Read_Product ( @Filter_ProductID uniqueidentifier, @Filter_IDProductType uniqueidentifier, @Filter_ProductName uniqueidentifier, ... etc ... ) AS BEGIN
   SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume
   FROM tblProduct
   WHERE ( @Filter_ProductID IS NULL
            OR ( ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 1 ) AND NOT ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 2 ) )
      AND ( @Filter_IDProductType IS NULL
            OR ( ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 1 ) AND NOT ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 2 ) )
      AND ( @Filter_ProductName IS NULL OR ( ... etc ... ) ) 
END

Ma questo suggerimento è poco complicato, credo.

Esistono alcune "best practice" fare questo tipo di stored procedure?

È stato utile?

Soluzione

Primo: per la tua routine di eliminazione, la tua clausola where dovrebbe includere solo la chiave primaria.

Secondo: per la routine di aggiornamento, non tentare di ottimizzare prima di avere un codice funzionante. In effetti, non tentare di ottimizzare fino a quando non è possibile creare un profilo dell'applicazione e vedere dove si trovano i colli di bottiglia. Posso dirti con certezza che l'aggiornamento di una colonna di una riga e l'aggiornamento di tutte le colonne di una riga sono quasi identici nella velocità. Ciò che richiede tempo in un DBMS è (1) trovare il blocco del disco in cui scrivere i dati e (2) bloccare altri writer in modo che la scrittura sia coerente. Infine, scrivere il codice necessario per aggiornare solo le colonne che devono essere modificate sarà generalmente più difficile da fare e più difficile da mantenere. Se vuoi davvero diventare pignolo, dovresti confrontare la velocità di capire quali colonne sono cambiate rispetto al solo aggiornamento di ogni colonna. Se li aggiorni tutti, non devi leggerli.

Terzo: tendo a scrivere una procedura memorizzata per ciascun percorso di recupero. Nel tuo esempio, ne farei uno per chiave primaria, uno per ogni chiave esterna e quindi aggiungerei uno per ogni nuovo percorso di accesso di cui avevo bisogno nell'applicazione. Sii agile; non scrivere codice che non ti serve. Concordo anche con l'utilizzo delle visualizzazioni anziché delle stored procedure, tuttavia è possibile utilizzare una stored procedure per restituire più set di risultati (in alcune versioni di MSSQL) o per modificare le righe in colonne, il che può essere utile.

Se è necessario ottenere, ad esempio, 7 righe per chiave primaria, sono disponibili alcune opzioni. È possibile chiamare la procedura memorizzata che ottiene una riga per chiave primaria sette volte. Questo può essere abbastanza veloce se si mantiene aperta la connessione tra tutte le chiamate. Se sai che non hai mai bisogno di più di un certo numero (diciamo 10) di ID alla volta, puoi scrivere una procedura memorizzata che include una clausola where come " e ID in (arg1, arg2, arg3 ...) " e assicurarsi che gli argomenti non utilizzati siano impostati su NULL. Se decidi che devi generare SQL dinamico, non mi preoccuperei di una procedura memorizzata perché TSQL è facile commettere un errore come qualsiasi altro linguaggio. Inoltre, non ottieni alcun vantaggio dall'utilizzo del database per eseguire la manipolazione delle stringhe: è quasi sempre il tuo collo di bottiglia, quindi non ha senso dare al DB più lavoro del necessario.

Altri suggerimenti

Per leggere i dati, non è necessaria una procedura memorizzata per motivi di sicurezza o per separare la logica, è possibile utilizzare le viste.

Concedi solo selezionare nella vista.

Puoi limitare i record mostrati, cambiare i nomi dei campi, unire più tabelle in una logica "tabella", ecc.

Non sono d'accordo sul fatto che creare procedure di inserimento / aggiornamento / selezione memorizzate sia una "best practice". A meno che l'intera applicazione non sia scritta in SP, utilizzare un livello di database nell'applicazione per gestire queste attività CRUD. Meglio ancora, usa una tecnologia ORM per gestirli per te.

Il mio suggerimento è di non provare a creare una procedura memorizzata che fa tutto ciò che potrebbe essere necessario o necessario. Se è necessario recuperare una riga in base alla chiave primaria della tabella, scrivere una procedura memorizzata per farlo. Se devi cercare tutte le righe che soddisfano una serie di criteri, scopri quali potrebbero essere tali criteri e scrivi una procedura memorizzata per farlo.

Se provi a scrivere un software che risolve ogni possibile problema piuttosto che un insieme specifico di problemi, di solito non riuscirai a fornire qualcosa di utile.

la procedura memorizzata selezionata può essere eseguita come segue per richiedere solo un proc memorizzato ma un numero qualsiasi di elementi diversi nella clausola where. Passa uno qualsiasi o una combinazione dei parametri e otterrai TUTTI gli articoli che corrispondono, quindi ti serve solo un proc memorizzato.

Create sp_ProductSelect
(
 @ProductID int = null,
 @IDProductType int = null,
 @ProductName varchar(50) = null,
 @ProductCode varchar(10) = null,
 ...
 @Volume int = null
)
AS
SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'  
Where
  ((@ProductID is null) or (ProductID = @ProductID)) AND
  ((@ProductName is null) or (ProductName = @ProductName)) AND
  ...
  ((@Volume is null) or (Volume= @Volume))

In SQL 2005, supporta nvarchar (max), che ha un limite di 2G, ma accetta virtualmente tutte le operazioni di stringa su nvarchar normale. Potresti voler verificare se questo può adattarsi a ciò di cui hai bisogno nel primo approccio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top