【问题标题】:Problem using ROW_NUMBER() to get records randomly (SQL Server 2005)使用 ROW_NUMBER() 随机获取记录的问题 (SQL Server 2005)
【发布时间】:2010-03-01 10:49:35
【问题描述】:

我想从一个表中随机获取1000条记录,所以我使用:

SELECT top 1000 
       mycol1
     , mycol2
     , ROW_NUMBER() OVER (ORDER BY NEWID()) rn
FROM mytable

但是,我不想在我的结果集中看到rn,所以我这样做了:

SELECT mycol1
     , mycol2
FROM (
    SELECT top 1000 
           mycol1
         , mycol2
         , ROW_NUMBER() OVER (ORDER BY NEWID()) rn
    FROM mytable
) a

当我这样做时,结果不再随机出现。他们来了,好像我只是说前 10000 个没有使用 row_number() 随机化。

当我将查询更改为

SELECT mycol1
     , mycol2
     , rn
FROM (
    SELECT top 1000 
           mycol1
         , mycol2
         , ROW_NUMBER() OVER (ORDER BY NEWID()) rn
    FROM mytable
) a

它们又是随机的。

我猜sql server做了某种优化,说“嘿,这家伙反正不需要rn列,所以忽略它”。但在这种情况下,这会导致意外行为。有什么办法可以避免吗?

PS:我使用 ROW_NUMBER() 技巧是因为 mytable 有 10 个 mio。行和

SELECT top 10000 *
FROM mytable
ORDER BY NEWID()

永远运行,而使用 ROW_NUMBER() 最多只需要 30 秒。

【问题讨论】:

  • 喜欢你的“ROW_NUMBER() OVER (ORDER BY NEWID())”。已经用过几次了,效果很好。

标签: sql sql-server-2005 query-optimization


【解决方案1】:

你也可以尝试在一些琐碎的 where 子句中使用 rn 字段,例如

WHERE rn > 0 在您的外部查询中,这可能会强制编译器通过 RN 字段。

另外我认为,如果您想对整百万条记录进行随机抽样,您的整体查询将成为一个问题。这只会抓取“第一个磁盘外”记录块,虽然不能保证相同,但通常会是相同的 10000 个。

我建议在 MIN(PrimaryKey) 和 MAX(PrimaryKey) 之间创建一组 10,000 个随机数,然后执行 WHERE PrimaryKey IN (...) 或类似操作

【讨论】:

  • 感谢您指出它只会占用表格的一部分。但是,在这个例子中,我可以接受它。
  • 他可以添加 WHERE rn
【解决方案2】:

Where rn Is Not Null 之类的内容添加到外部查询中,以便 rn 将其包含在查询计划中而不是优化

【讨论】:

    【解决方案3】:

    我一直在努力解决同样的问题。我用 CROSS APPLY 和 TOP 解决了它。请记住,CROSS APPLY 将我的外部表拉入派生表的范围,我知道必须有一种方法可以做到这一点。

    以下代码会导致根据制造商添加 3(*) 个随机相关产品。

    INSERT INTO     ProductGroup (
                        ParentId,
                        ChildId
                    )
    SELECT          DISTINCT
                    P.ProductId,
                    CandidateInner.ChildId
    FROM            ProductRelated PR 
    JOIN            Product P
    ON              PR.ChildId = P.ProductId
    CROSS APPLY     
                    (   
                        SELECT      DISTINCT TOP 3
                                    NewId() AS RandId,
                                    Product.ManufacturerId,
                                    ProductRelated.ChildId 
                        FROM        ProductRelated 
                        JOIN        Product 
                        ON          Product.ProductId = ProductRelated.ChildId
                        WHERE       ManufacturerId IS NOT NULL
                        AND         Product.ManufacturerId = P.ManufacturerId
                        ORDER BY    NewId()
                    ) CandidateInner
    LEFT JOIN       (
                        SELECT      DISTINCT TOP 100 PERCENT
                                    ParentId,
                                    COUNT(DISTINCT ChildId) AS Ct
                        FROM        ProductGroup 
                        GROUP BY    ParentId
                        HAVING      COUNT(DISTINCT ChildId) >= 3
                    ) AlreadyGrouped
    ON              P.ProductId = AlreadyGrouped.ParentId
    WHERE           P.ProductId <> CandidateInner.ChildId
    AND             AlreadyGrouped.ParentId IS NULL
    ORDER BY        P.ProductId
    

    *请注意,这将在以下 2 种情况下插入少于 3 个:

    1) 制造商相关的产品有

    上面的(1)是不可避免的。

    我在上面处理 (2) 的方式是运行两次,然后删除重复项。这仍然不是 100%,但从统计上来说,这对于我的要求来说已经绰绰有余了。这是在夜间运行的脚本中,但我仍然喜欢在 CROSS APPLY 之外使用 的速度 - 任何拉入检查范围的操作都会导致扫描制造商连接产生的派生表,即使将其拉入将意味着 (2) 不再是一个问题,但它与正确索引的即时相比慢得令人痛苦。

    【讨论】:

      猜你喜欢
      • 2011-04-03
      • 1970-01-01
      • 1970-01-01
      • 2021-02-12
      • 1970-01-01
      • 2011-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多