2017-02-12 70 views
1

我有以下存儲過程是相當廣泛的,因爲動態@Name參數和子查詢。動態參數查詢 - 更好的方法?

有沒有更好的方法來做到這一點?

CREATE PROCEDURE [dbo].[spGetClientNameList] 
@Name varchar(100) 
AS 
BEGIN 
SET NOCOUNT ON; 

SELECT 
* 
FROM 
(
    SELECT 
    ClientID, 
    FirstName + ' ' + LastName as Name 
    FROM 
    Client 
) a 
where a.Name like '%' + @Name + '%' 
+0

此查詢不能使用索引。您可以使用索引的每種方式都有更好的性能 - 因爲內容化字符串和以'%'開頭的類似表達式永遠不會使用索引。這種情況最簡單的方法是使用全文索引和全文搜索。最好的(但更復雜的方式)是分解和規範化你的輸入數據。 – Deadsheep39

+0

如果使用傳統SQL排序規則('SQL [_]%')或二進制排序規則('%[_] BIN%')而不是Windows排序規則,則可以提高使用通配符的全掃描性能。由於全面掃描,查詢仍然很昂貴。 –

+0

您能否詳細說明參數Name?它始終是第一個還是最後一個名字?它可以是部分名稱嗎?它可以是First +''+ Last組合的部分字符串嗎?因爲這個選擇還打開了新的可能性:First ='Scott'Last ='Tiger' - 在你的查詢名稱中''T''也會找到一個匹配項。而與每個人分別比較會改變結果集。 –

回答

0

亞倫伯特蘭從最近的兩篇文章無恥地竊取:

的JIST是創建我們可以使用的東西類似於trigram (or trigraph) in PostgreSQL

阿龍貝特朗還包括免責聲明如下:

「之前,我開始表現出我所提出的解決方案是如何工作的,讓我絕對清楚地表明該解決方案不應該在每一個情況下LIKE使用'%wildcard%'搜索速度很慢。由於我們要將源數據「分解」爲片段的方式,與較大的字符串(如產品說明或會話摘要)相比,它可能在實用性上受限於較小的字符串,例如地址或名稱。「


測試設置:http://rextester.com/IIMT54026

客戶表

使用亞倫貝特朗爆炸串片段
create table dbo.Client (
    ClientId int not null primary key clustered 
    , FirstName varchar(50) not null 
    , LastName varchar(50) not null 
); 
insert into dbo.Client (ClientId, FirstName, LastName) values 
    (1, 'James','') 
, (2, 'Aaron','Bertrand') 
go 

功能(修改後用於輸入大小):

create function dbo.CreateStringFragments(@input varchar(101)) 
returns table with schemabinding 
as return 
    (
    with x(x) as (
     select 1 union all select x+1 from x where x < (len(@input)) 
    ) 
    select Fragment = substring(@input, x, len(@input)) from x 
); 
go 

表來存儲片段爲FirstName + ' ' + LastName

create table dbo.Client_NameFragments (
    ClientId int   not null 
    , Fragment varchar(101) not null 
    , constraint fk_ClientsNameFragments_Client 
     foreign key(ClientId) references dbo.Client 
     on delete cascade 
); 

create clustered index s_cat on dbo.Client_NameFragments(Fragment, ClientId); 
go 

載入與片段表:

insert into dbo.Client_NameFragments (ClientId, Fragment) 
select c.ClientId, f.Fragment 
    from dbo.Client as c 
    cross apply dbo.CreateStringFragments(FirstName + ' ' + LastName) as f; 
go 

創建觸發器保持片段:

create trigger dbo.Client_MaintainFragments 
on dbo.Client 
for insert, update as 
begin 
    set nocount on; 

    delete f from dbo.Client_NameFragments as f 
    inner join deleted as d 
    on f.ClientId = d.ClientId; 

    insert dbo.Client_NameFragments(ClientId, Fragment) 
    select i.ClientId, fn.Fragment 
    from inserted as i 
    cross apply dbo.CreateStringFragments(i.FirstName + ' ' + i.LastName) as fn; 
end 
go 

快速觸發測試S:

/* trigger tests --*/ 
insert into dbo.Client (ClientId, FirstName, LastName) values 
(3, 'Sql', 'Zim') 
update dbo.Client set LastName = 'unknown' where LastName = ''; 
delete dbo.Client where ClientId = 3; 
--select * from dbo.Client_NameFragments order by ClientId, len(Fragment) desc 
/* -- */ 
go 

新程序:

create procedure [dbo].[Client_getNameList] @Name varchar(100) as 
begin 
set nocount on; 
    select 
     ClientId 
     , Name = FirstName + ' ' + LastName 
    from Client c 
    where exists (
     select 1 
     from dbo.Client_NameFragments f 
     where f.ClientId = c.ClientId 
     and f.Fragment like @Name+'%' 
    ) 
end 
go 
exec [dbo].[Client_getNameList] @Name = 'On Bert' 

回報:

+----------+----------------+ 
| ClientId |  Name  | 
+----------+----------------+ 
|  2 | Aaron Bertrand | 
+----------+----------------+ 
+0

謝謝你。一個非常有趣的閱讀和解決方案的問題。 – Philip

0

我猜連接列上的搜索操作有時不會帶索引。我得到了上面的情況,我用OR取代了連接搜索,這使我在大多數時候都有更好的表現。

如果不存在,在FirstNameLastName上創建非聚集索引。

檢查修改類似下面

CREATE PROCEDURE [dbo].[spGetClientNameList] 
@Name varchar(100) 
AS 
BEGIN 
SET NOCOUNT ON; 


    SELECT 
    ClientID, 
    FirstName + ' ' + LastName as Name 
    FROM 
    Client 
    WHERE FirstName LIKE '%' + @Name + '%' 
    OR LastName LIKE '%' + @Name + '%' 
END 

上述步驟並做檢查執行後的績效計劃,以驗證這些指標的使用與否。

+0

謝謝Shakeer。但仍是索引掃描。 – Philip

+0

正在做上述改變使得任何性能差異? @James –

+0

這不等同於問題中的查詢。如果'@ name'參數包含'keer Mir'會怎麼樣?問題中的查詢會找到你的名字,但你的查詢不會。 –

0

這個問題真的歸結爲必須計算列(concat的名字和姓氏),這幾乎迫使sql服務器做一個表的全面掃描,以確定什麼是匹配和什麼是不是。如果您不允許添加索引或更改表格,則必須更改查詢(分別提供firstName和lastName)。如果你是,你可以添加一個計算列和索引:

Create Table client (

    ClientId INT NOT NULL PRIMARY KEY IDENTITY(1,1) 
    ,FirstName VARCHAR(100) 
    ,LastName VARCHAR(100) 
    ,FullName AS FirstName + ' ' + LastName 
) 

Create index FullName ON Client(FullName) 

這至少會加快您的查詢起來做索引而不是尋求全表掃描。這值得麼?很難說沒有看看有多少數據等。

+0

感謝凱文,儘管這仍然是一個掃描。 – Philip

+0

可以掃描此表格或使用idnex。問題在於你的qry,並且像'%'這樣的不能使用索引的表達式開始 - >你用你喜歡的方式強制進行表掃描。 – Deadsheep39

0
where a.Name like '%' + @Name + '%' 

此聲明永遠不能使用索引。在這種情況下它的尤爲明顯使用全文搜索

,如果你可以限制你的like

where a.Name like @Name + '%' 

,機器會自動用索引。此外,您可以使用REVERSE()函數索引的語句,如:

where a.Name like '%' + @Name