Comment créer une procédure stockée dans laquelle rechercher éventuellement des colonnes?
-
03-07-2019 - |
Question
Je travaille sur une application pour le travail qui va interroger notre base de données d'employés. Les utilisateurs finaux souhaitent pouvoir effectuer des recherches sur la base des critères de nom / service standard, mais ils souhaitent également pouvoir interroger toutes les personnes portant le prénom "James". cela fonctionne dans le département de la santé. Ce que je veux éviter, c’est que la procédure stockée prenne une liste de paramètres et génère une instruction SQL à exécuter, ce qui ouvrirait la porte à l’injection SQL au niveau interne.
Cela peut-il être fait?
La solution
Bien que l'astuce COALESCE
soit simple, ma méthode préférée est:
CREATE PROCEDURE ps_Customers_SELECT_NameCityCountry
@Cus_Name varchar(30) = NULL
,@Cus_City varchar(30) = NULL
,@Cus_Country varchar(30) = NULL
,@Dept_ID int = NULL
,@Dept_ID_partial varchar(10) = NULL
AS
SELECT Cus_Name
,Cus_City
,Cus_Country
,Dept_ID
FROM Customers
WHERE (@Cus_Name IS NULL OR Cus_Name LIKE '%' + @Cus_Name + '%')
AND (@Cus_City IS NULL OR Cus_City LIKE '%' + @Cus_City + '%')
AND (@Cus_Country IS NULL OR Cus_Country LIKE '%' + @Cus_Country + '%')
AND (@Dept_ID IS NULL OR Dept_ID = @DeptID)
AND (@Dept_ID_partial IS NULL OR CONVERT(varchar, Dept_ID) LIKE '%' + @Dept_ID_partial + '%')
Ce type de SP peut facilement être généré par un code (et régénéré pour les modifications de table).
Vous disposez de quelques options pour gérer les nombres. Vous pouvez choisir une sémantique exacte ou une sémantique de recherche.
Autres conseils
Le moyen le plus efficace d'implémenter ce type de recherche est d'utiliser une procédure stockée. L'instruction présentée ici crée une procédure qui accepte les paramètres requis. Lorsqu'une valeur de paramètre n'est pas fournie, elle est définie sur NULL.
CREATE PROCEDURE ps_Customers_SELECT_NameCityCountry
@Cus_Name varchar(30) = NULL,
@Cus_City varchar(30) = NULL,
@Cus_Country varchar(30) =NULL
AS
SELECT Cus_Name,
Cus_City,
Cus_Country
FROM Customers
WHERE Cus_Name = COALESCE(@Cus_Name,Cus_Name) AND
Cus_City = COALESCE(@Cus_City,Cus_City) AND
Cus_Country = COALESCE(@Cus_Country,Cus_Country)
Extrait de cette page: http://www.sqlteam.com / article / implementation-a-dynamic-where-clause
Je l'ai déjà fait Cela fonctionne bien.
L'article d'Erland Sommarskog Les conditions de recherche dynamique dans T-SQL constituent une bonne référence. sur comment faire cela. Erland présente un certain nombre de stratégies permettant de le faire sans utiliser de code SQL dynamique (blocs IF, OR, COALESCE, etc.) et répertorie même les caractéristiques de performance de chaque technique.
Au cas où vous auriez à mordre la balle et parcourir le chemin du SQL dynamique, vous devriez également lire le Curse d'Erland. et bénédictions de SQL dynamique où il donne quelques conseils sur la façon d'écrire correctement des SQL dynamiques
Cela peut être fait, mais ces procédures d’évier de cuisine génèrent généralement des plans de requête médiocres.
Cela dit, voici la tactique la plus couramment utilisée pour "facultatif". paramètres. L’approche normale consiste à traiter NULL comme étant "omis".
SELECT
E.EmployeeID,
E.LastName,
E.FirstName
WHERE
E.FirstName = COALESCE(@FirstName, E.FirstName) AND
E.LastName = COALESCE(@LastName, E.LastName) AND
E.DepartmentID = COALESCE(@DepartmentID, E.DepartmentID)
EDIT: Une approche bien meilleure serait des requêtes paramétrées. Voici un article de blog d'une des plus grandes autorités mondiales dans ce domaine, Frans Bouma de LLBLGen Pro fame:
L'utilisation de la méthode COALESCE pose un problème, car si votre colonne a une valeur NULL, le fait de passer une condition de recherche NULL (ce qui signifie ignorer la condition de recherche) ne renverra pas la ligne dans de nombreuses bases de données.
Par exemple, essayez le code suivant sur SQL Server 2000:
CREATE TABLE dbo.Test_Coalesce (
my_id INT NOT NULL IDENTITY,
my_string VARCHAR(20) NULL )
GO
INSERT INTO dbo.Test_Coalesce (my_string) VALUES (NULL)
INSERT INTO dbo.Test_Coalesce (my_string) VALUES ('t')
INSERT INTO dbo.Test_Coalesce (my_string) VALUES ('x')
INSERT INTO dbo.Test_Coalesce (my_string) VALUES (NULL)
GO
DECLARE @my_string VARCHAR(20)
SET @my_string = NULL
SELECT * FROM dbo.Test_Coalesce WHERE my_string = COALESCE(@my_string, my_string)
GO
Vous ne récupérerez que deux lignes car dans les lignes où la colonne my_string est NULL, vous obtenez efficacement:
my_string = COALESCE(@my_string, my_string) =>
my_string = COALESCE(NULL, my_string) =>
my_string = my_string =>
NULL = NULL
Mais bien entendu, NULL n'est pas égal à NULL.
J'essaie de rester avec:
SELECT
my_id,
my_string
FROM
dbo.Test_Coalesce
WHERE
(@my_string IS NULL OR my_string = @my_string)
Bien sûr, vous pouvez ajuster cela en utilisant des caractères génériques ou tout ce que vous voulez faire.
Copier ceci à partir de mon article de blog:
USE [AdventureWorks]
GO
CREATE PROCEDURE USP_GET_Contacts_DynSearch
(
-- Optional Filters for Dynamic Search
@ContactID INT = NULL,
@FirstName NVARCHAR(50) = NULL,
@LastName NVARCHAR(50) = NULL,
@EmailAddress NVARCHAR(50) = NULL,
@EmailPromotion INT = NULL,
@Phone NVARCHAR(25) = NULL
)
AS
BEGIN
SET NOCOUNT ON
DECLARE
@lContactID INT,
@lFirstName NVARCHAR(50),
@lLastName NVARCHAR(50),
@lEmailAddress NVARCHAR(50),
@lEmailPromotion INT,
@lPhone NVARCHAR(25)
SET @lContactID = @ContactID
SET @lFirstName = LTRIM(RTRIM(@FirstName))
SET @lLastName = LTRIM(RTRIM(@LastName))
SET @lEmailAddress = LTRIM(RTRIM(@EmailAddress))
SET @lEmailPromotion = @EmailPromotion
SET @lPhone = LTRIM(RTRIM(@Phone))
SELECT
ContactID,
Title,
FirstName,
MiddleName,
LastName,
Suffix,
EmailAddress,
EmailPromotion,
Phone
FROM [Person].[Contact]
WHERE
(@lContactID IS NULL OR ContactID = @lContactID)
AND (@lFirstName IS NULL OR FirstName LIKE '%' + @lFirstName + '%')
AND (@lLastName IS NULL OR LastName LIKE '%' + @lLastName + '%')
AND (@lEmailAddress IS NULL OR EmailAddress LIKE '%' + @lEmailAddress + '%')
AND (@lEmailPromotion IS NULL OR EmailPromotion = @lEmailPromotion)
AND (@lPhone IS NULL OR Phone = @lPhone)
ORDER BY ContactID
END
GO
Nous pouvons utiliser le paramètre @Search générique et lui transmettre toute valeur pour la recherche.
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: --
-- Create date:
-- Description: --
-- =============================================
CREATE PROCEDURE [dbo].[usp_StudentList]
@PageNumber INT = 1, -- Paging parameter
@PageSize INT = 10,-- Paging parameter
@Search VARCHAR(MAX) = NULL, --Generic Search Parameter
@OrderBy VARCHAR(MAX) = 'FirstName', --Default Column Name 'FirstName' for records ordering
@SortDir VARCHAR(MAX) = 'asc' --Default ordering 'asc' for records ordering
AS
BEGIN
SET NOCOUNT ON;
--Query required for paging, this query used to show total records
SELECT COUNT(StudentId) AS RecordsTotal FROM Student
SELECT Student.*,
--Query required for paging, this query used to show total records filtered
COUNT(StudentId) OVER (PARTITION BY 1) AS RecordsFiltered
FROM Student
WHERE
--Generic Search
-- Below is the column list to add in Generic Serach
(@Search IS NULL OR Student.FirstName LIKE '%'+ @Search +'%')
OR (@Search IS NULL OR Student.LastName LIKE '%'+ @Search +'%')
--Order BY
-- Below is the column list to allow sorting
ORDER BY
CASE WHEN @SortDir = 'asc' AND @OrderBy = 'FirstName' THEN Student.FirstName END,
CASE WHEN @SortDir = 'desc' AND @OrderBy = 'FirstName' THEN Student.FirstName END DESC,
CASE WHEN @SortDir = 'asc' AND @OrderBy = 'LastName' THEN Student.LastName END,
CASE WHEN @SortDir = 'desc' AND @OrderBy = 'LastName' THEN Student.LastName END DESC,
OFFSET @PageSize * (@PageNumber - 1) ROWS FETCH NEXT @PageSize ROWS ONLY;
END
Ma première pensée a été d’écrire une requête comme celle-ci ...
SELECT EmpId, NameLast, NameMiddle, NameFirst, DepartmentName
FROM dbo.Employee
INNER JOIN dbo.Department ON dbo.Employee.DeptId = dbo.Department.Id
WHERE IdCrq IS NOT NULL
AND
(
@bitSearchFirstName = 0
OR
Employee.NameFirst = @vchFirstName
)
AND
(
@bitSearchMiddleName = 0
OR
Employee.NameMiddle = @vchMiddleName
)
AND
(
@bitSearchFirstName = 0
OR
Employee.NameLast = @vchLastName
)
AND
(
@bitSearchDepartment = 0
OR
Department.Id = @intDeptID
)
... qui demanderait alors à l'appelant de fournir un indicateur de bit s'il souhaite effectuer une recherche dans un champ particulier, puis de fournir la valeur s'il souhaite le rechercher, mais je ne sais pas si cela crée un relâchement O clause ou si je peux sortir avec une déclaration CASE dans la clause WHERE.
Comme vous pouvez le constater, ce code est en T-SQL, mais je regarderai volontiers certains codes PL-SQL / MySQL et les adapterai en conséquence.
Je préférerais utiliser la méthode NULL / COALESCE sur les requêtes AdHoc, puis effectuer un test pour vous assurer que vous n’avez pas de problèmes de performances.
S'il s'avère que les requêtes sont lentes, car elles effectuent une analyse de table lorsque vous effectuez une recherche sur les colonnes indexées, vous pouvez toujours compléter la procédure stockée de recherche générique par des procédures spécifiques supplémentaires permettant la recherche dans ces champs indexés. . Par exemple, vous pouvez avoir un SP spécial qui effectue des recherches par ID client ou par nom / prénom.
Ecrivez une procédure pour insérer toutes les données d'employé dont le nom commence par A dans la table ??