2017-08-23 136 views
1

我正在使用JPA 2.1和SQL Server數據庫。下面是表(語法是PostgreSQL的,因爲它是最簡單的的時刻爲我寫):如何在SQL Server中調整或重寫此查詢?

CREATE TABLE users 
(
    id serial primary key, 
    locked boolean, 
    name varchar(20) 
); 

CREATE TABLE subscriptions 
(
    id serial, 
    name varchar(20), 
    id_user integer references users 
); 

我要選擇那些沒有訂閱具有特定的所有活動(未鎖定)用戶名稱。用戶可能沒有訂閱,也可能有多個訂閱。

我的JPQL查詢,我目前所面對的SQL版本是

SELECT * 
FROM users 
WHERE locked = false 
    AND id NOT IN (SELECT id_user 
       FROM subscriptions 
       WHERE name = 'premium') 

我讀的地方,一個SQL Server將執行嵌套SELECT對於外部選擇的每一個結果行。即使嵌套SELECT的結果集不會像查詢在這種情況下一樣被執行,這是否正確?

隨着表的增長,此查詢的運行時性能非常糟糕。如何在SQL Server中重寫或優化此查詢?也許使用JOIN?

+0

SQL Server優化器是基於成本的,所以它應該(理論上)爲語義相同的查詢生成最優化的計劃。將主鍵,唯一約束和索引添加到您的問題中。缺乏適當的指標可能會導致性能問題。表格大小導致性能下降的症狀表明全面掃描。 –

+3

「我讀過某處SQL Server將爲外部SELECT的每個結果行執行嵌套SELECT」 - 不正確。它會盡可能地使用基於集合的操作。 –

+0

@SurprisedCoconut您可以發佈樣本數據嗎?雖然上面的評論是正確的,但您也可以用簡單的連接重新編寫它。 – Eli

回答

0

已讀的地方,一個SQL Server將執行嵌套SELECT對於外部的每個結果行選擇

根本不是真的。

而這個查詢:

SELECT * 
FROM users 
WHERE locked = false 
    AND id NOT IN (SELECT id_user 
       FROM subscriptions 
       WHERE name != 'premium') 

會給你誰不會被鎖定所有用戶,誰沒有非優質訂閱。

要獲得所有不具有高級訂閱誰非鎖定的用戶會是這樣的:

SELECT * 
FROM users u 
WHERE locked = 0 
    AND NOT EXISTS (SELECT * 
       FROM subscriptions 
       where id_user = u.id 
       and name = 'premium') 

和SQL Server測試這樣的東西,讓SQL Server Management Studio中(或類似),並運行一個這樣的腳本:

use tempdb 
go 
--drop table subscriptions 
--drop table users 
go 

CREATE TABLE users (
    id int primary key, 
    locked bit, 
    name varchar(20) 
); 

CREATE TABLE subscriptions (
    id int primary key, 
    name varchar(20), 
    id_user int references users 
); 

go 

with N as 
(
select row_number() over (order by (select null)) i 
from sys.objects o, sys.columns c, sys.columns c2 
) 
insert into users (id,locked,name) 
select top 10000 i, case when i%100 = 0 then 1 else 0 end, concat('user',i) 
from N; 


with N as 
(
select row_number() over (order by (select null)) i 
from sys.objects o, sys.columns c, sys.columns c2 
) 
insert into subscriptions(id,name,id_user) 
select top 100000 i, case when i%100 = 0 then 'premium' else 'standard' end, i%10000 + 1 
from N; 


go 

SELECT * 
FROM users u 
WHERE locked = 0 
    AND NOT EXISTS (SELECT * 
       FROM subscriptions 
       where id_user = u.id 
       and name = 'premium') 
1

您可以將您的查詢獲得性能上的not exists()

select * 
from dbo.users u 
where locked = 0 
    and not exists (
    select 1 
    from dbo.subscriptions s 
    where s.id_user = u.id 
     and s.name = 'premium' 
) 

您還可以通過具有適當的配套指標提高性能在subscriptions,例如:

create nonclustered index ix_subscriptions_id_user_name 
    on dbo.subscriptions (id_user) 
    include (name); 

您可以更進一步,使其成爲過濾索引,但這可能不會顯着提升性能。

0

假設你有聯接的列上創建的索引,嘗試:

SELECT * 
FROM users AS A 
LEFT JOIN subscriptions as B 
ON B.id_user = A.id 
WHERE A.locked = 'false' AND B.name != 'premium' AND B.id_user IS NULL 
0

更好地利用LEFT JOIN,當然如果創建聯接條件索引,查詢變得這麼快。

SELECT u.* 
FROM users u 
LEFT JOIN subscriptions s 
ON s.id_user = u.id 
WHERE u.locked = 'false' 
AND s.name != 'premium' 
AND s.id_user IS NULL 
0

首先在訂閱上創建一個聚集索引。如果表格非常大,那麼在堆上維護索引可能是一場噩夢。我可能會建議在id_User上進行羣集。

CREATE TABLE subscriptions 
(
    id INT IDENTITY(1,1) PRIMARY KEY NONCLUSTERED, 
    name VARCHAR(20), 
    id_user INT 
); 

CREATE CLUSTERED INDEX CL_id_User ON Subscriptions (id_User) 

CREATE TABLE users 
(
    id INT IDENTITY(1,1), 
    locked BIT, 
    name varchar(20), 
    CONSTRAINT PK_users PRIMARY KEY CLUSTERED (id) 
); 

然後在訂閱和用戶上創建非聚集索引。包括用於如果您想要從兩個表中撤回所有列。

CREATE NONCLUSTERED INDEX IX_Users_Locked ON Users (Locked) INCLUDE (Name); 
CREATE NONCLUSTERED INDEX IX_Subscriptions_Name ON Subscriptions (name); 

,然後讓查詢看起來類似下面 -

SELECT * 
    FROM users u 
WHERE u.id NOT IN (SELECT s.id_user 
         FROM Subscriptions s 
        WHERE s.name = 'Premium') 
    AND u.locked = 0; 

再向前邁進一步,而使用一個ID,以確定訂閱類型。索引整數遠遠優於索引SQL Server中的字符串。