You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server多对多关系下高效查询优化方案问询

高效筛选符合条件的账户(SQL Server 2014 SP3)

使用SQL Server 2014 SP3,数据库结构如下:

  • Account与Customer通过Account_Customer构成多对多关系
  • Customer与Car通过Customer_Car构成多对多关系
  • Customer与Pet通过Customer_Pet构成多对多关系

需求:筛选出**所有关联客户均未养猫(PetName以Cat开头)且未开道奇车(CarName以Dodge开头)**的账户列表。现有查询可满足需求,但因多次访问同表,在千万级数据场景下效率不足,寻求更优方案。


表结构与测试数据脚本

USE tempdb;

-- 创建表
IF OBJECT_ID('Account') IS NOT NULL DROP TABLE Account;
CREATE TABLE Account (AccountId INT, AccountName VARCHAR(24))

IF OBJECT_ID('Customer') IS NOT NULL DROP TABLE Customer;
CREATE TABLE Customer (CustomerId INT, CustomerName VARCHAR(24))

IF OBJECT_ID('Pet') IS NOT NULL DROP TABLE Pet;
CREATE TABLE Pet (PetId INT, PetName VARCHAR(24))

IF OBJECT_ID('Car') IS NOT NULL DROP TABLE Car;
CREATE TABLE Car (CarId INT, CarName VARCHAR(24))

IF OBJECT_ID('Account_Customer') IS NOT NULL DROP TABLE Account_Customer;
CREATE TABLE Account_Customer (AccountId INT, CustomerId INT)

IF OBJECT_ID('Customer_Pet') IS NOT NULL DROP TABLE Customer_Pet;
CREATE TABLE Customer_Pet (CustomerId INT, PetId INT)

IF OBJECT_ID('Customer_Car') IS NOT NULL DROP TABLE Customer_Car;
CREATE TABLE Customer_Car (CustomerId INT, CarId INT)

-- 插入测试数据
INSERT [dbo].[Account]([AccountId], [AccountName])
VALUES (1, 'Account1'), (2, 'Account2')

INSERT [dbo].[Customer]([CustomerId], [CustomerName])
VALUES (1, 'Customer1'), (2, 'Customer2'), (3, 'Customer3'),  (4, 'Customer4')

INSERT [dbo].[Pet]([PetId], [PetName])
VALUES (1, 'Cat1'), (2, 'Cat2'), (3, 'Dog3'),  (4, 'Dog4')

INSERT [dbo].[Car]([CarId], [CarName])
VALUES (1, 'Ford1'), (2, 'Ford2'), (3, 'Kia3'),  (4, 'Dodge4')

INSERT [dbo].[Account_Customer] ([AccountId], [CustomerId])
VALUES (1,1), (1,2), (2, 2), (2,3), (2,4)

INSERT [dbo].[Customer_Pet] ([CustomerId], [PetId])
VALUES (2,3), (3,1), (3, 2), (4,3), (4,4)

INSERT [dbo].[Customer_Car] ([CustomerId], [CarId])
VALUES (1,2), (2,2), (3,1), (3, 2), (3, 4)

-- 查看关联后的全量数据(非规范化)
SELECT [A].[AccountId], [A].[AccountName],
       [C].[CustomerId], [C].[CustomerName],
       [CP].[PetId], [P].[PetName],
        [C2].[CarId], [C2].[CarName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
LEFT JOIN [dbo].[Customer_Pet] AS [CP] ON [CP].[CustomerId] = [C].[CustomerId]
LEFT JOIN [dbo].[Pet] AS [P] ON [P].[PetId] = [CP].[PetId]
LEFT JOIN [dbo].[Customer_Car] AS [CC] ON [CC].[CustomerId] = [C].[CustomerId]
LEFT JOIN [dbo].[Car] AS [C2] ON [C2].[CarId] = [CC].[CarId]
ORDER BY [A].[AccountId], [AC].[CustomerId]

现有可满足需求但效率待优化的查询

-- 预期仅返回Account1
SELECT DISTINCT
       [A].[AccountId],
       [A].[AccountName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
EXCEPT
-- 筛选出关联客户养猫或开道奇的账户
SELECT 
       [A].[AccountId],
       [A].[AccountName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
WHERE 
(
    EXISTS (SELECT TOP (1) 1  
            FROM [dbo].[Customer] AS [C2]
            JOIN [dbo].[Customer_Pet] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
            JOIN [dbo].[Pet] AS [P2] ON [P2].[PetId] = [CP2].[PetId]
            WHERE [C2].[CustomerId] = [C].[CustomerId]
            AND [P2].[PetName] LIKE 'Cat%'
            )
    OR
    EXISTS (SELECT TOP (1) 1  
            FROM [dbo].[Customer] AS [C2]
            JOIN [dbo].[Customer_Car] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
            JOIN [dbo].[Car] AS [P2] ON [P2].[CarId] = [CP2].[CarId]
            WHERE [C2].[CustomerId] = [C].[CustomerId]
            AND [P2].[CarName] LIKE 'Dodge%'
            )
)

无法满足需求的错误查询

-- 此查询不符合需求:
SELECT DISTINCT 
       [A].[AccountId],
       [A].[AccountName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
WHERE 
(
    NOT EXISTS (SELECT TOP (1) 1  
            FROM [dbo].[Customer] AS [C2]
            JOIN [dbo].[Customer_Pet] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
            JOIN [dbo].[Pet] AS [P2] ON [P2].[PetId] = [CP2].[PetId]
            WHERE [C2].[CustomerId] = [C].[CustomerId]
            AND [P2].[PetName] LIKE 'Cat%'
            )
    AND
    NOT EXISTS (SELECT TOP (1) 1  
            FROM [dbo].[Customer] AS [C2]
            JOIN [dbo].[Customer_Car] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
            JOIN [dbo].[Car] AS [P2] ON [P2].[CarId] = [CP2].[CarId]
            WHERE [C2].[CustomerId] = [C].[CustomerId]
            AND [P2].[CarName] LIKE 'Dodge%'
            )
)

错误原因:该查询仅过滤了当前行的客户是否符合条件,但账户可能关联多个客户。只要有一个客户不符合要求(养猫/开道奇),整个账户就应该被排除,但此查询会因为存在符合条件的客户而错误保留这类账户(比如测试数据中的Account2)。


优化方案

方案1:从Account出发,直接排除关联问题客户的账户

SELECT a.AccountId, a.AccountName
FROM Account a
WHERE NOT EXISTS (
    SELECT 1
    FROM Account_Customer ac
    JOIN Customer c ON ac.CustomerId = c.CustomerId
    WHERE ac.AccountId = a.AccountId
    AND (
        -- 客户养猫
        EXISTS (
            SELECT 1
            FROM Customer_Pet cp
            JOIN Pet p ON cp.PetId = p.PetId
            WHERE cp.CustomerId = c.CustomerId
            AND p.PetName LIKE 'Cat%'
        )
        OR
        -- 客户开道奇车
        EXISTS (
            SELECT 1
            FROM Customer_Car cc
            JOIN Car car ON cc.CarId = car.CarId
            WHERE cc.CustomerId = c.CustomerId
            AND car.CarName LIKE 'Dodge%'
        )
    )
)

优势:

  • 从Account表起始,仅扫描必要的关联表,避免重复访问同一张表
  • NOT EXISTS逻辑直接排除存在问题客户的账户,减少中间结果集的生成
  • 无需DISTINCT,因为Account表的主键保证唯一性

索引优化建议(千万级数据必备)

为了进一步提升查询效率,创建以下索引:

  • CREATE NONCLUSTERED INDEX IX_Account_Customer_AccountId ON Account_Customer(AccountId, CustomerId);
  • CREATE NONCLUSTERED INDEX IX_Customer_Pet_CustomerId ON Customer_Pet(CustomerId);
  • CREATE NONCLUSTERED INDEX IX_Pet_PetId_PetName ON Pet(PetId) INCLUDE(PetName);
  • CREATE NONCLUSTERED INDEX IX_Customer_Car_CustomerId ON Customer_Car(CustomerId);
  • CREATE NONCLUSTERED INDEX IX_Car_CarId_CarName ON Car(CarId) INCLUDE(CarName);

这些索引可以大幅减少查询时的表扫描次数,提升关联和过滤的效率。


内容的提问来源于stack exchange,提问作者SQL_Guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 08:48:21