T-SQL 저장 프로 시저에서 선택적 매개 변수를 사용하려면 어떻게해야합니까?


185

테이블을 통해 검색하기 위해 저장 프로 시저를 만들고 있습니다. 다양한 검색 필드가 있으며 모두 선택 사항입니다. 이를 처리 할 저장 프로 시저를 만드는 방법이 있습니까? ID, FirstName, LastName 및 Title의 네 가지 필드가있는 테이블이 있다고 가정하겠습니다. 나는 이런 식으로 할 수 있습니다 :

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

이런 종류의 작품. 그러나 이름, 성 또는 제목이 NULL 인 레코드는 무시합니다. 제목이 검색 매개 변수에 지정되어 있지 않으면 제목이 NULL 인 레코드를 포함하고 싶습니다. 이름과성에 동일합니다. 동적 SQL 로이 작업을 수행 할 수는 있지만 피하고 싶습니다.



2
다음 명령문을 시도하십시오. codeISNULL (FirstName, ') = ISNULL (@FirstName,' ')-모든 NULL을 빈 문자열로 만들고 eq를 통해 비교할 수 있습니다. 운영자. 입력 매개 변수가 널인 경우 모든 제목을 가져 오려면 다음과 같이 시도하십시오. codeFirstName = @FirstName 또는 @FirstName IS NULL.
baHI

답변:


257

주어진 매개 변수를 기반으로 검색을 동적으로 변경하는 것은 복잡한 주제이며 약간의 차이 만 있어도 다른 방식으로 검색하면 성능에 큰 영향을 줄 수 있습니다. 핵심은 인덱스를 사용하고, 컴팩트 코드를 무시하고, 코드 반복에 대한 걱정을 무시하는 것입니다. 좋은 쿼리 실행 계획을 만들어야합니다 (인덱스 사용).

이것을 읽고 모든 방법을 고려하십시오. 가장 좋은 방법은 매개 변수, 데이터, 스키마 및 실제 사용량에 따라 다릅니다.

Erland Sommarskog의 T-SQL의 동적 검색 조건

Erland Sommarskog의 동적 SQL의 저주와 축복

적절한 SQL Server 2008 버전 (SQL 2008 SP1 CU5 (10.0.2746) 이상)이있는 경우이 작은 트릭을 사용하여 실제로 인덱스를 사용할 수 있습니다.

추가 OPTION (RECOMPILE), 쿼리에 ERLAND의 문서를 참조 하고, SQL Server는 해결됩니다 OR내에서 (@LastName IS NULL OR LastName= @LastName)쿼리 계획은 지역 변수의 런타임 값을 기준으로 작성 전에 인덱스를 사용할 수있다.

이것은 모든 SQL Server 버전에서 작동하지만 적절한 결과를 반환하지만 SQL 2008 SP1 CU5 (10.0.2746) 이상인 경우 OPTION (RECOMPILE) 만 포함하십시오. OPTION (RECOMPILE)은 쿼리를 다시 컴파일하며, 나열된 버전 만 로컬 변수의 현재 런타임 값을 기반으로 쿼리를 다시 컴파일하여 최상의 성능을 제공합니다. 해당 버전의 SQL Server 2008에 없으면 해당 줄을 그대로 두십시오.

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

15
AND / OR 우선 순위에주의하십시오. AND가 OR보다 우선하므로 적절한 대괄호가 없으면이 예제는 예상 결과를 생성하지 않습니다. 따라서 다음과 같이 읽습니다. (@FirstName IS NULL OR (FirstName = @FirstName)) AND (@LastNameIS NULL OR (LastName = @LastName)) AND (@TitleIS NULL OR (Title = @Title))
Bliek

... (@ FirstName IS NULL OR (FirstName = @FirstName)은 ... (FirstName = Coalesce (@ firstname, FirstName))이어야합니다.
fcm

괄호를 잊지 마십시오. 그렇지 않으면 작동하지 않습니다.
Pablo Carrasco Hernández

27

@KM의 답변은 가능한 한 좋은 것이지만 그의 초기 조언 중 하나를 완전히 따르지 않습니다.

..., 컴팩트 코드를 무시하고, 코드 반복에 대한 걱정을 무시하고 ...

최상의 성능을 얻으려면 선택 가능한 기준의 각 가능한 조합에 대해 맞춤형 쿼리를 작성해야합니다. 이것은 극단적으로 들릴 수도 있고, 선택적인 기준이 많으면 그럴 수도 있지만 성과는 노력과 결과 사이의 절충점 인 경우가 많습니다. 실제로는 맞춤형 쿼리로 타겟팅 할 수있는 일반적인 매개 변수 조합 세트가있을 수 있으며 다른 모든 조합에 대해 일반 쿼리 (다른 답변에 따라)가있을 수 있습니다.

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

이 접근 방식의 장점은 맞춤형 쿼리로 처리되는 일반적인 경우 쿼리가 가능한 한 효율적이라는 것입니다. 제공되지 않은 기준에 의해 영향을받지 않습니다. 또한 인덱스 및 기타 성능 향상은 가능한 모든 상황을 만족시키기보다는 특정 맞춤형 쿼리를 대상으로 할 수 있습니다.


각 경우마다 별도의 저장 프로 시저를 작성하는 것이 좋습니다. 그런 다음 스푸핑 및 재 컴파일에 대해 걱정하지 마십시오.
Jodrell

5
이 접근 방식이 빠르게 유지 보수의 악몽이된다는 것은 말할 나위도 없습니다.
Atario

3
@Atario 유지 보수와 성능의 용이성은 공통적 인 단점입니다.이 답변은 성능에 맞춰져 있습니다.
Rhys Jones

26

다음과 같은 경우에 할 수 있습니다

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

그러나 때로는 데이터에 의존하여 동적 쿼리를 생성하고 실행하는 것이 좋습니다.


10

파티에 5 년 늦었 어

허용 된 답변의 제공된 링크에 언급되어 있지만 SO에 대한 명시 적 답변이 필요하다고 생각합니다. 제공 된 매개 변수를 기반으로 쿼리를 동적으로 작성합니다. 예 :

설정

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

순서

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

용법

exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'

장점 :

  • 작성하고 이해하기 쉽다
  • 유연성-까다로운 필터링에 대한 쿼리를 쉽게 생성 (예 : 동적 TOP)

단점 :

  • 제공된 매개 변수, 색인 및 데이터 볼륨에 따라 가능한 성능 문제

직접적인 대답은 아니지만 큰 그림으로 알려진 문제와 관련이 있습니다.

일반적으로 이러한 필터링 저장 프로시 저는 플로팅되지 않지만 일부 서비스 계층에서 호출됩니다. 이것은 비즈니스 로직 (필터링)을 SQL에서 서비스 계층으로 이동시키는 옵션을 남깁니다.

한 가지 예는 LINQ2SQL을 사용하여 제공된 필터를 기반으로 쿼리를 생성하는 것입니다.

    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;
    }

장점 :

  • 제공된 필터를 기반으로 동적으로 생성 된 쿼리 매개 변수 스니핑 또는 재 컴파일 힌트 필요 없음
  • OOP 세계의 사람들을 위해 작성하기가 다소 쉬움
  • "간단한"쿼리가 발행되므로 일반적으로 성능 친화적입니다 (적절한 인덱스가 여전히 필요함)

단점 :

  • LINQ2QL 제한에 도달하고 경우에 따라 LINQ2Object로 다운 그레이드하거나 강제 SQL 솔루션으로 되돌아 갈 수 있음
  • 부주의 한 LINQ 작성은 끔찍한 쿼리 (또는 탐색 속성이로드 된 경우 많은 쿼리)를 생성 할 수 있습니다.

1
중간 문자열이 모두 ''가 아닌 N ''인지 확인하십시오. SQL이 8000자를 초과하면 잘림 문제가 발생합니다.
Alan Singfield

1
또한 사용자에게 직접 SELECT 권한을 거부 한 경우 저장 프로 시저에 "WITH EXECUTE AS OWNER"절을 넣어야 할 수도 있습니다. 이 절을 사용하는 경우 SQL 삽입을 피하도록주의하십시오.
Alan Singfield

8

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, '') = '')

즉, 다른 경우를 부울 조건과 결합합니다.


-3

이것은 또한 작동합니다 :

    ...
    WHERE
        (FirstName IS NULL OR FirstName = ISNULL(@FirstName, FirstName)) AND
        (LastName IS NULL OR LastName = ISNULL(@LastName, LastName)) AND
        (Title IS NULL OR Title = ISNULL(@Title, Title))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.