Je crée une procédure stockée pour effectuer une recherche dans une table. J'ai beaucoup de champs de recherche différents, qui sont tous facultatifs. Y at-il un moyen de créer une procédure stockée qui gérera cela? Disons que j'ai une table avec quatre champs: ID, Prénom, Nom et Titre. Je pourrais faire quelque chose comme ça:
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = ISNULL(@FirstName, FirstName) AND
LastName = ISNULL(@LastName, LastName) AND
Title = ISNULL(@Title, Title)
END
Ce genre de travaux. Cependant, il ignore les enregistrements où FirstName, LastName ou Title sont NULL. Si Title n'est pas spécifié dans les paramètres de recherche, je veux inclure les enregistrements où Title est NULL - idem pour FirstName et LastName. Je sais que je pourrais probablement le faire avec du SQL dynamique, mais j'aimerais éviter cela.
Le fait de modifier dynamiquement les recherches en fonction des paramètres donnés est un sujet compliqué et le faire d'une manière sur une autre, même avec très peu de différence, peut avoir des conséquences énormes sur les performances. La clé est d’utiliser un index, d’ignorer le code compact, d’ignorer les répétitions de code, vous devez établir un bon plan d’exécution de la requête (utilisez un index).
Lisez ceci et considérez toutes les méthodes. Votre meilleure méthode dépendra de vos paramètres, de vos données, de votre schéma et de votre utilisation réelle:
Conditions de recherche dynamique dans T-SQL par Erland Sommarskog
La malédiction et les bénédictions de SQL dynamique par Erland Sommarskog
Si vous disposez de la version appropriée de SQL Server 2008 (SQL 2008 SP1 CU5 (10.0.2746) et versions ultérieures), vous pouvez utiliser cette petite astuce pour utiliser réellement un index:
Ajoutez OPTION (RECOMPILE)
à votre requête, voir l'article d'Erland , et SQL Server résoudra le OR
à partir de (@LastName IS NULL OR LastName= @LastName)
avant la création du plan de requête en fonction des valeurs d'exécution. des variables locales, et un index peut être utilisé.
Cela fonctionnera pour toutes les versions de SQL Server (renverra les résultats appropriés), mais n'inclura que l'option OPTION (RECOMPILE) si vous utilisez SQL 2008 SP1 CU5 (10.0.2746) ou une version ultérieure. L'OPTION (RECOMPILE) recompilera votre requête. Seule la version répertoriée le recompilera en fonction des valeurs d'exécution actuelles des variables locales, ce qui vous donnera les meilleures performances. Si ce n'est pas sur cette version de SQL Server 2008, laissez cette ligne désactivée.
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(@FirstName IS NULL OR (FirstName = @FirstName))
AND (@LastName IS NULL OR (LastName = @LastName ))
AND (@Title IS NULL OR (Title = @Title ))
OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
END
La réponse de @KM est bonne dans la mesure du possible mais ne donne pas suite à l'un de ses premiers conseils.
..., ignore le code compact, ignore le souci de répéter le code, ...
Si vous souhaitez obtenir les meilleures performances, vous devez écrire une requête sur mesure pour chaque combinaison possible de critères facultatifs. Cela peut sembler extrême, et si vous avez beaucoup de critères optionnels, ce peut être le cas, mais la performance est souvent un compromis entre effort et résultats. En pratique, il peut exister un ensemble commun de combinaisons de paramètres pouvant être ciblé avec des requêtes personnalisées, puis une requête générique (comme pour les autres réponses) pour toutes les autres combinaisons.
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
IF (@FirstName IS NOT NULL AND @LastName IS NULL AND @Title IS NULL)
-- Search by first name only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = @FirstName
ELSE IF (@FirstName IS NULL AND @LastName IS NOT NULL AND @Title IS NULL)
-- Search by last name only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
LastName = @LastName
ELSE IF (@FirstName IS NULL AND @LastName IS NULL AND @Title IS NOT NULL)
-- Search by title only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
Title = @Title
ELSE IF (@FirstName IS NOT NULL AND @LastName IS NOT NULL AND @Title IS NULL)
-- Search by first and last name
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = @FirstName
AND LastName = @LastName
ELSE
-- Search by any other combination
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(@FirstName IS NULL OR (FirstName = @FirstName))
AND (@LastName IS NULL OR (LastName = @LastName ))
AND (@Title IS NULL OR (Title = @Title ))
END
L'avantage de cette approche est que, dans les cas courants traités par des requêtes sur mesure, la requête est aussi efficace que possible - les critères non pris en charge n'ont aucun impact. En outre, les index et autres améliorations des performances peuvent être ciblés sur des requêtes spécifiques sur mesure plutôt que d'essayer de satisfaire toutes les situations possibles.
Vous pouvez faire dans le cas suivant,
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(@FirstName IS NULL OR FirstName = @FirstName) AND
(@LastNameName IS NULL OR LastName = @LastName) AND
(@Title IS NULL OR Title = @Title)
END
cependant, dépendre des données, il vaut parfois mieux créer une requête dynamique et les exécuter.
Cinq ans de retard à la fête.
Il est mentionné dans les liens fournis de la réponse acceptée, mais je pense que cela mérite une réponse explicite sur SO - en construisant de manière dynamique la requête en fonction des paramètres fournis. Par exemple.:
Configuration
-- drop table Person
create table Person
(
PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
FirstName NVARCHAR(64) NOT NULL,
LastName NVARCHAR(64) NOT NULL,
Title NVARCHAR(64) NULL
)
GO
INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'),
('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'),
('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO
Procédure
ALTER PROCEDURE spDoSearch
@FirstName varchar(64) = null,
@LastName varchar(64) = null,
@Title varchar(64) = null,
@TopCount INT = 100
AS
BEGIN
DECLARE @SQL NVARCHAR(4000) = '
SELECT TOP ' + CAST(@TopCount AS VARCHAR) + ' *
FROM Person
WHERE 1 = 1'
PRINT @SQL
IF (@FirstName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @FirstName'
IF (@LastName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @LastName'
IF (@Title IS NOT NULL) SET @SQL = @SQL + ' AND Title = @Title'
EXEC sp_executesql @SQL, N'@TopCount INT, @FirstName varchar(25), @LastName varchar(25), @Title varchar(64)',
@TopCount, @FirstName, @LastName, @Title
END
GO
Utilisation
exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'
Avantages:
Les inconvénients:
Pas de réponse directe, mais liée au problème, alias la grande image
Généralement, ces procédures stockées de filtrage ne sont pas flottantes, mais sont appelées à partir d'une couche de service. Cela laisse l'option d'abandonner la logique métier (filtrage) de SQL vers la couche de service.
Un exemple utilise LINQ2SQL pour générer la requête en fonction des filtres fournis:
public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
{
var query = DataAccess.SomeRepository.AllNoTracking;
// partial and insensitive search
if (!string.IsNullOrWhiteSpace(filters.SomeName))
query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
// filter by multiple selection
if ((filters.CreatedByList?.Count ?? 0) > 0)
query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
if (filters.EnabledOnly)
query = query.Where(item => item.IsEnabled);
var modelList = query.ToList();
var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
return serviceModelList;
}
Avantages:
Les inconvénients:
Étendez votre condition WHERE
:
WHERE
(FirstName = ISNULL(@FirstName, FirstName)
OR COALESCE(@FirstName, FirstName, '') = '')
AND (LastName = ISNULL(@LastName, LastName)
OR COALESCE(@LastName, LastName, '') = '')
AND (Title = ISNULL(@Title, Title)
OR COALESCE(@Title, Title, '') = '')
je. e. combinez différents cas avec des conditions booléennes.