如何在SQL Server中调整或重写此查询?

时间:2017-08-23 19:48:15

标签: sql sql-server

我正在使用JPA 2.1和SQL Server数据库。以下是表格(语法适用于PostgreSQL,因为目前最容易为我编写):

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。即使嵌套SELECT的结果集不会像在这种情况下执行查询那样改变,这是真的吗?

随着表的增长,此查询具有可怕的运行时性能。如何在SQL Server中重写或调整此查询?也许使用JOIN?

5 个答案:

答案 0 :(得分: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);

您可以更进一步,使其成为过滤索引,但这可能不会显着提升性能。

答案 1 :(得分: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

答案 2 :(得分:0)

  

已经在某处读过SQL Server将为外部SELECT

的每个结果行执行嵌套的SELECT

根本不是真的。

这个查询:

'\h'

会为所有未锁定且没有非高级订阅的用户提供。

要获得所有未获得高级订阅的非锁定用户,请执行以下操作:

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

要在SQL Server中测试这样的东西,得到SQL Server Management Studio(或类似的),并运行如下脚本:

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

答案 3 :(得分: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

答案 4 :(得分: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中的索引字符串。