2008-10-29 13 views
4

L'une des «meilleures pratiques» consiste à accéder aux données via des procédures stockées. Je comprends pourquoi ce scénario est bon. Ma motivation est de diviser la base de données et la logique applicative (les tables peuvent être changées, si le comportement des procédures stockées est identique), défense pour l'injection SQL (les utilisateurs ne peuvent pas exécuter "select * from some_tables" et la sécurité (dans la procédure stockée peut être "n'importe quoi" qui sécurisé, cet utilisateur ne peut pas sélectionner/insérer/mettre à jour/supprimer des données, ce qui n'est pas pour eux).Accès aux données avec des procédures stockées

Ce que je ne sais pas, c'est comment accéder aux données avec des filtres dynamiques.

J'utilise MSSQL 2005.

Si j'ai le tableau:

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

alors je devrait créer 4 procédures stockées (création/lecture/mise à jour/supprimer).

La procédure stockée pour "créer" est facile.

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

La procédure stockée pour "supprimer" est également facile.

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

La procédure stockée pour « mise à jour » est similaire pour « supprimer », mais je ne suis pas sûr que ce soit la bonne façon, comment le faire. Je pense que la mise à jour de toutes les colonnes n'est pas efficace.

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 

Et la dernière procédure stockée pour "lire" est un petit mystère pour moi. Comment passer les valeurs de filtre pour des conditions complexes? J'ai quelques suggestions:

l'aide du paramètre XML pour passer où la 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 

Mais, je pense que la limitation des caractères « 4000 » pour une requête est laid.

La suggestion suivante consiste à utiliser des tables de filtres pour chaque colonne. Insérer des valeurs de filtre dans la table de filtre, puis appeler la procédure stockée avec ID de filtres:

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 

Mais cette suggestion est littlebit compliqué je pense.

Existe-t-il une «meilleure pratique» pour effectuer ce type de procédures stockées?

Répondre

5

d'abord: pour votre routine de suppression, votre clause where ne devrait inclure la clé primaire. Deuxièmement: pour votre routine de mise à jour, n'essayez pas d'optimiser avant d'avoir du code de travail. En fait, n'essayez pas d'optimiser jusqu'à ce que vous puissiez profiler votre application et voir où se trouvent les goulots d'étranglement.Je peux vous assurer que la mise à jour d'une colonne d'une ligne et la mise à jour de toutes les colonnes d'une ligne sont presque identiques en vitesse. Ce qui prend du temps dans un SGBD est (1) de trouver le bloc de disque où vous allez écrire les données et (2) de verrouiller les autres écrivains afin que votre écriture soit cohérente. Enfin, l'écriture du code nécessaire pour mettre à jour uniquement les colonnes qui doivent être modifiées sera généralement plus difficile à faire et plus difficile à maintenir. Si vous vouliez vraiment être pointilleux, vous devrez comparer la vitesse de calcul des colonnes changées par rapport à la simple mise à jour de chaque colonne. Si vous les mettez à jour tous, vous n'avez pas à les lire. Troisième: J'ai tendance à écrire une procédure stockée pour chaque chemin de récupération. Dans votre exemple, j'en créerais une par clé primaire, une par clé étrangère, puis j'en ajouterais une pour chaque nouveau chemin d'accès, comme j'en avais besoin dans l'application. Soyez agile; n'écris pas de code dont tu n'as pas besoin. Je suis également d'accord avec l'utilisation de vues au lieu de procédures stockées, cependant, vous pouvez utiliser une procédure stockée pour renvoyer plusieurs ensembles de résultats (dans certaines versions de MSSQL) ou pour changer les lignes en colonnes, ce qui peut être utile.

Si vous devez obtenir, par exemple, 7 lignes par clé primaire, vous avez quelques options. Vous pouvez appeler la procédure stockée qui obtient une ligne par clé primaire sept fois. Cela peut être assez rapide si vous gardez la connexion ouverte entre tous les appels. Si vous savez que vous n'avez jamais besoin de plus d'un certain nombre (disons 10) d'ID à la fois, vous pouvez écrire une procédure stockée qui inclut une clause where comme "et ID dans (arg1, arg2, arg3 ...)" et faire Assurez-vous que les arguments inutilisés sont définis sur NULL. Si vous décidez que vous avez besoin de générer du SQL dynamique, je ne voudrais pas déranger avec une procédure stockée car TSQL est tout aussi facile de faire une erreur que n'importe quel autre langage. De plus, vous n'avez aucun avantage à utiliser la base de données pour manipuler les chaînes - c'est presque toujours votre goulot d'étranglement, donc il ne sert à rien de donner plus de travail que nécessaire à la base de données.

+0

Première: Vous avez propably vérité. La vérification pour "un autre utilisateur a changé des données après que vous ayez reçu des données et avant que vous ayez sauvegardé des données" ne vaut rien pour la suppression. Deuxièmement: je pense à la mise à jour d'une grande colonne. Comme "ntext". Dans certains cas, il peut y avoir un texte RTF avec des images. – TcKs

+0

Troisième: Quand je veux obtenir 7 lignes de tblProduct. Est-ce bon moyen d'appeler "Read_Product" 7 fois avec divers "@ProductID" valeur ou devrais-je faire quelque chose d'autre? – TcKs

+0

Pour ntext, les données sont souvent stockées dans un bloc différent du reste de la ligne, donc oui, dans ce cas, vous pouvez avoir une procédure stockée dans laquelle vous ne passez pas la colonne ntext. Cependant, comparer une valeur ntext à une valeur passée sera plus lent que simplement écraser. –

6

Pour lire des données, vous n'avez pas besoin d'une procédure stockée pour la sécurité ou pour séparer la logique, vous pouvez utiliser des vues.

Accordez uniquement une sélection sur la vue.

Vous pouvez limiter les enregistrements affichés, modifier les noms de champ, joignez-vous plusieurs tables dans une logique « table », etc.

+0

Dois-je utiliser des vues ou des fonctions? Les fonctions sont beaucoup plus flexibles je pense. Y a-t-il une raison pour ne pas utiliser des fonctions au lieu de vues ou n'est-ce pas important? – TcKs

+0

Souvent, l'optimiseur de SGBD comprend mieux les vues que les fonctions. –

+0

encore une fois, il peut ne pas - dans une grande base de données de production nous avons eu des améliorations de performances très significatives lorsque nous avons basculé certaines requêtes complexes plus grandes de vues en fonctions ... –

0

En SQL 2005, il prend en charge nvarchar (max), qui a une limite de 2G, mais accepte pratiquement toutes les opérations de chaîne sur un nvarchar normal. Vous voudrez peut-être tester si cela peut correspondre à ce dont vous avez besoin dans la première approche.

2

Ma suggestion est que vous n'essayez pas de créer une procédure stockée qui fait tout ce que vous pourriez maintenant ou jamais besoin de faire. Si vous devez récupérer une ligne basée sur la clé primaire de la table, alors écrivez une procédure stockée pour le faire. Si vous devez rechercher toutes les lignes répondant à un ensemble de critères, recherchez ce que ces critères peuvent être et écrivez une procédure stockée pour le faire.

Si vous essayez d'écrire un logiciel qui résout tous les problèmes possibles plutôt qu'un ensemble spécifique de problèmes, vous échouerez généralement à fournir quelque chose d'utile.

2

votre procédure stockée select peut être effectuée comme suit pour ne nécessiter qu'un seul proc stocké mais un nombre quelconque d'éléments différents dans la clause where. Transmettez n'importe lequel ou une combinaison des paramètres et vous obtiendrez TOUS les articles qui correspondent - de sorte que vous avez seulement besoin d'un proc stocké.

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)) 
+0

Mais de cette façon, puis-je filtrer uniquement pour l'égalité. – TcKs

+0

Je l'utiliserais pour obtenir un sous-ensemble de données, puis autoriser le filtrage dynamique en utilisant l'aperçu de données dans votre code. Cela vous fournira la solution la plus flexible sans introduire de risques de sécurité potentiels. – Mauro

3

Je ne partage pas que créer Insérer/Mettre à jour/Sélectionnez les procédures stockées sont « meilleures pratiques ». À moins que votre application entière ne soit écrite dans des SP, utilisez une couche de base de données dans votre application pour gérer ces activités CRUD. Mieux encore, utilisez une technologie ORM pour les gérer pour vous.

+0

En accord - Les SP sont les meilleurs pour les opérations plus complexes, ce qui est plus efficace/sûr/facile à faire directement sur la base de données. Il n'y a presque rien à gagner avec les SP pour les déclarations CRUD simples, mieux utiliser un niveau intermédiaire pour cela. –

Questions connexes