【问题标题】:How to avoid large in clause?如何避免大in子句?
【发布时间】:2011-05-14 09:10:55
【问题描述】:

我有 3 张桌子:

table_product (30 000 row)
---------
ID
label

_

table_period (225 000 row)
---------
ID 
date_start
date_end
default_price
FK_ID_product

table_special_offer (10 000 row)
-----
ID
label
date_start,
date_end,
special_offer_price
FK_ID_period

所以我需要从所有这些表中加载数据,所以这就是我要做的: 1/ 像这样从“table_product”加载数据

select *
from table_product
where label like 'gun%'

2/ 像这样从“table_period”加载数据

select *
from table_period
where FK_ID_product IN(list of all the ids selected in the 1)

3/ 像这样从“table_special_offer”加载数据

select *
from table_special_offer
where FK_ID_period IN(list of all the ids selected in the 2)

您可能认为第 3 点中的 IN 子句可能非常大(例如 75 000 大),所以我有很多机会获得超时或类似“已达到表达式服务限制”的内容.

你有没有遇到过这样的事情,你是怎么避免的?

PS: 上下文:SQL server 2005,.net 2.0 (请不要告诉我我的设计不好,或者我不应该做“选择*”,我只是简化了我的问题,所以它比描述我的业务的 500 页简单一点)。

谢谢。

【问题讨论】:

    标签: sql sql-server optimization


    【解决方案1】:

    切换到使用连接:

    SELECT <FieldList>
    FROM Table_Product prod
        JOIN Table_Period per ON prod.Id = per.FK_ID_Product
        JOIN Table_Special_Offer spec ON per.ID = spec.FK_ID_Period
    WHERE prod.label LIKE 'gun%'
    

    您应该注意的是 IN、JOIN 和 EXISTS 的区别 - great article here.

    【讨论】:

    • 您的解决方案的问题 我将获取每个周期行和每个特价商品(笛卡尔积)的产品信息。所以如果 1 个产品的信息真的很大,我将有 30 000 * 225 000 * 10 000 =675*10^11 行而不是 30 000 + 225 000 + 10 000 = 265*10^3 行(定量为 3*10^7)。也许我错了……
    • ...我错了,在您的示例中它将是 225 000 行,但在每一行中,我们将获得产品信息 + 期间 + 可能还有特价,你也是认为如果有很多产品信息,会导致一些性能问题?
    【解决方案2】:

    终于有了我的答案:表变量(有点像@smirkingman 的解决方案,但不是 cte)所以:

    declare @product(id int primary key,label nvarchar(max))
    declare @period(id int primary key,date_start datetime,date_end datetime,defaultprice real)
    declare @special_offer(id int,date_start datetime,date_end datetime,special_offer_price real)
    
    insert into @product
    select * 
    from table_product
    where label like 'gun%'
    
    insert into @period
    select * 
    from table_period
    where exists(
    select * from @product p where p.id = table_period.FK_id_product
    )
    
    insert into @special_offer
    select * 
    from table_special_offer
    where exists(
    select * from @period p where p.id = table_special_offer.fk_id_period
    )
    
    select * from @product
    select * from @period
    select * from @special_offer
    

    这是针对 sql 的,在 c# 中我使用 sqldatareader 类的 ExecuteReader、Read 和 NextResult

    http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.aspx

    我得到了我想要的一切: - 我的数据 - 我没有太多数据(与加入的解决方案不同) - 我不执行两次相同的查询(如带有子查询的解决方案) - 我不必更改我的映射代码(1row = 1 个业务对象)

    【讨论】:

      【解决方案3】:

      不要在IN 子句中使用明确的值列表。相反,写你的查询像

      ... FK_ID_product IN (select ID
      from table_product
      where label like 'gun%')
      

      【讨论】:

      • 这将是一个解决方案,可能是存在而不是存在(se @atathedev 链接)。但这里的问题是,如果我有很多产品/时期,这个解决方案会很有效,如果我只选择几个,我最好做一个明确的。
      • 抱歉,您仍然有一个很大的 IN 子句,并且可能会收到“已达到表达式服务限制”错误。这就是我们有 JOIN 和 EXISTS 的原因。
      • 此外,除非列名相同,否则它将尝试将 table_product 中的整行与FK_ID_product table_period 进行比较。您不会匹配任何记录,因为您没有为 FK_ID_product 提供一列数据以在 IN 标准内进行匹配
      • 失败。那应该是“从 table_product 中选择 ID”而不是“选择 *”
      【解决方案4】:
      SELECT * 
          FROM 
              table_product tp
              INNER JOIN table_period tper
                  ON tp.ID = tper.FK_ID_product
              INNER JOIN table_special_offer so
                  ON tper.ID  = so.FK_ID_period
          WHERE 
              tp.label like 'gun%'" 
      

      【讨论】:

        【解决方案5】:

        首先是一些代码...

        使用 JOIN:

        SELECT 
          table_product.* --'Explicit table calls just for organisation sake'
        , table_period.*
        , table_special_offer.*
            FROM 
                table_product
                INNER JOIN table_period
                    ON table_product.ID = table_period.FK_ID_product
                INNER JOIN table_special_offer
                    ON table_period.ID  = table_special_offer.FK_ID_period
            WHERE 
                tp.label like 'gun%'" 
        

        使用 IN :

        SELECT 
            * 
        FROM 
            table_special_offer 
        WHERE FK_ID_period IN 
            (
            SELECT 
                FK_ID_period 
            FROM 
                table_period 
            WHERE FK_ID_product IN
                (
                SELECT 
                    FK_ID_product 
                FROM 
                    table_product 
                WHERE label like '%gun'
                ) AS ProductSub
            ) AS PeriodSub
        

        根据您的表的索引情况,两者都可以使用。正如其他人所建议的,内部联接在执行查询和返回 3 个表的所有数据方面绝对有效。如果您只需要使用来自 table_producttable_period 的 ID,那么使用嵌套的“IN”语句可以很好地适应索引表上的搜索条件(如果使用的条件是整数,就像我假设的那样,使用 IN 可以你的 FK_ID_product 是)。

        要记住的重要一点是,每个数据库和关系表设置都会有不同的行为,您不会在一个数据库中获得相同的优化结果。尝试手头的所有可能性并使用最适合您的一种。当您需要检查性能时,查询分析器会非常有用。

        当我们尝试通过 ID 连接和基于链接表的条件将客户帐户连接到他们的适当地址时,我遇到了这种情况(我们有另一个表显示客户使用某些设备,我们必须对其进行字符串搜索.) 奇怪的是,在一个查询中使用这两种方法对我们来说更快:

        -- 使用 IN 子句将带有 WHERE Desc LIKE '%Equipment%' 的查询“加入”到客户端表,然后将其加入到地址表中:

        SELECT 
            Address.*
        ,   Customers_Filtered.*
        FROM
            Address AS Address
        INNER JOIN
            (SELECT Customers.* FROM Customers WHERE ID IN (SELECT CustomerID FROM Equipment WHERE Desc LIKE '%Equipment search here%') AS Equipment ) AS Customers_Filtered
        ON Address.CustomerID = Customers_Filtered.ID
        

        这种查询方式(如果我的语法不完全正确,我深表歉意)最终在整个查询变得更加复杂之后变得更加高效和易于组织。

        希望这有帮助 - 关注 @AdaTheDev 的文章链接,这绝对是一个很好的资源。

        【讨论】:

          【解决方案6】:

          JOIN 为您提供相同的结果。

          SELECT  so.Col1
                  , so.Col2
          FROM    table_product pt
                  INNER JOIN table_period pd ON pd.FK_ID_product = pt.ID_product
                  INNER JOIN table_special_offer so ON so.FK_ID_Period = pd.ID_Period
          WHERE   pt.lable LIKE 'gun%'
          

          【讨论】:

          • @remi:重复?它不再显示,但我已经在之前 AdaTheDev 发布了这个。看来我们俩的想法是一样的。
          • 这就是我所说的“重复”,您的想法很简单,代码几乎相同,所以感谢您提供答案,但您已经 3 岁了,使用相同的解决方案 :)
          • 也许它解决方案:)。说真的,它可能有性能问题,但绝不会像使用 IN 子句那样接近。
          • 如果我在 in 子句中有 5 个项目,则 in 子句获胜...如果我在 in 子句中有 60 000 个项目,您的解决方案可能会获胜。您的解决方案的问题在于它使映射代码更复杂(是的,我知道 NH)并且效率更低(在我的解决方案中 1 行 = 1 个对象)。
          • 您了解新罕布什尔州吗?我不。什么是新罕布什尔州?
          【解决方案7】:

          我很想知道这是否会有所改进:

          WITH products(prdid) AS (
              SELECT
                  ID
              FROM
                  table_product
              WHERE
                  label like 'gun%'
          ),
          periods(perid) AS (
              SELECT
                  ID
              FROM
                  table_period
                  INNER JOIN products
                      ON id = prdid
          ),
          offers(offid) AS (
              SELECT
                  ID
              FROM    
                  table_special_offer
                  INNER JOIN periods
                      ON id = perid
          )
          

          ...只是一个建议...

          【讨论】:

            猜你喜欢
            • 2011-07-25
            • 2011-12-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-02-20
            • 1970-01-01
            • 1970-01-01
            • 2017-05-27
            相关资源
            最近更新 更多