按国家/地区过滤产品的最快查询

T.G*_*ang 3 sql-server query-performance

我有一个很大的Product表:

产品编号 产品名称
1 产品一
2 产品二
... ...
n 产品编号

而 n 大约为 2,000,000。

为了限制某个国家/地区的产品可用性,我有第二个ProductCountryRestriction表:

产品编号 国家代码 包含排除
1 美国 0
1 英国 0
2 澳大利亚 1
2 新西兰 1

如果产品仅限于一个或多个国家/地区,我会将包含产品的国家/地区添加到此表中,其中 IncludeExclude = 0。例如,产品 1 仅适用于美国和英国。

如果产品对除某些国家/地区以外的所有国家/地区都有效,那么我将使用 IncludeExclude = 1 将产品 - 排除的国家/地区添加到此表中。例如,产品 2 适用于除 AUS 和 NZ 之外的所有国家/地区。

与产品对应的所有国家/地区代码必须设置为 IncludeExclude = 0 或 IncludeExclude = 1。不允许混合使用 0 和 1。例如,对于产品 1,有 IncludeExclude = 0 的记录,我无法将 ProductID = 1 和 IncludeExclude = 1 的记录添加到此表中。

该系统的用户可以选择多个国家进行工作。用户首选项存储在UserCountry表中:

用户身份 国家代码
1 美国
1 新西兰

所以用户 1 可以看到产品 1,因为它在美国可用。他还可以看到产品 2,因为产品 2 也可用于美国(尽管它被排除在 NZ 之外)。这是我为用户获取所有可用产品的查询:

DECLARE @UserID int = 1;

SELECT P.*
FROM Product P
WHERE 
    EXISTS
    (
        SELECT * FROM ProductCountryRestriction PCR 
        WHERE 
            PCR.ProductId = P.ProductId
            AND PCR.IncludeExclude = 0 
            AND PCR.CountryCode IN (SELECT CountryCode FROM UserCountry WHERE UserID = @UserID)
    )
    OR EXISTS 
    (
        SELECT * FROM UserCountry UC
        WHERE UserID = @UserID
            AND UC.CountryCode NOT IN (SELECT CountryCode FROM ProductCountryRestriction PCR WHERE PCR.ProductId = P.ProductId AND PCR.IncludeExclude = 1)
    )
Run Code Online (Sandbox Code Playgroud)

此查询按预期工作,但性能不佳。我能做些什么来改善它?我不介意改变数据库设计。感谢您阅读我的问题!如果您能提供任何帮助,我将不胜感激。

2020 年 12 月 31 日编辑 - 添加了@JD 建议的执行计划 请查看此计划的链接:https : //www.brentozar.com/pastetheplan/?id=BywbFaqaw

sti*_*bit 7

既然写了你不会介意改变设计...改变设计。

而不是带有标志和隐式包含/排除的复杂逻辑,只需有一个将产品映射到国家/地区的表格。让我们简单地称之为productcountry。当且仅当存在产品的某个国家/地区的记录且productcountry该产品在该国家/地区可用时。

然后查询只使用一些JOINs 和 a WHERE

SELECT DISTINCT 
       p.*
       FROM product p
            INNER JOIN productcountry pc
                       ON pc.productid = p.productid
            INNER JOIN usercountry uc
                       ON uc.countrycode = pc.countrycode
       WHERE uc.userid = @userid;
Run Code Online (Sandbox Code Playgroud)

为此,您应该在usercountry (userid, countrycode),productcountry (countrycode, productid)和上尝试索引product (id)

它需要一个DISTINCT虽然,因为一种产品可以在用户使用的多个国家/地区提供。(我在这里默默地假设产品本身是不同的,即它们有一个键。)如果您产生更好的计划,如果您改为使用EXISTS相关子查询,您可以进行试验。

SELECT p.*
       FROM product p
       WHERE EXISTS (SELECT *
                            FROM productcountry pc
                                 INNER JOIN usercountry uc
                                            ON uc.countrycode = pc.countrycode
                            WHERE uc.userid = @userid
                                  AND pc.productid = p.productid);
Run Code Online (Sandbox Code Playgroud)

在这里,您可以在usercountry (userid, countrycode)和上尝试索引productcountry (productid, countrycode)

  • @T.Giang:我怀疑过类似的事情。但是(至少在这种情况下),不要将字体结束逻辑应用于数据库设计。数据库是应该以最佳方式存储数据的部分。以最佳方式呈现它是前端的工作,而不是数据库的工作。通常一对一映射是不可能的,必须进行一些转换。在这里,您可以尝试通过过程或可更新视图在数据库中进行转换。但我认为在您的应用程序中这样做可能更容易。即使转换逻辑转到数据库,也不会改变底层数据库设计。 (2认同)
  • 有时这样的设计是最好的方法。例如,如果添加了一个国家/地区,我们是否会自动将其包含在所有产品中?如果是这样,则此设置是正确的。我会做两个单独的表:包括和排除 (2认同)