【问题标题】:Preserving ORDER BY in SELECT INTO在 SELECT INTO 中保留 ORDER BY
【发布时间】:2013-01-03 16:45:46
【问题描述】:

我有一个 T-SQL 查询,它从一个表中获取数据并将其复制到一个新表中,但只有满足特定条件的行:

SELECT VibeFGEvents.* 
INTO VibeFGEventsAfterStudyStart 
FROM VibeFGEvents
LEFT OUTER JOIN VibeFGEventsStudyStart
ON 
    CHARINDEX(REPLACE(REPLACE(REPLACE(logName, 'MyVibe ', ''), ' new laptop', ''), ' old laptop', ''), excelFilename) > 0
    AND VibeFGEventsStudyStart.MIN_TitleInstID <= VibeFGEvents.TitleInstID
    AND VibeFGEventsStudyStart.MIN_WinInstId <= VibeFGEvents.WndInstID
WHERE VibeFGEventsStudyStart.excelFilename IS NOT NULL
ORDER BY VibeFGEvents.id

使用表格的代码依赖于它的顺序,上面的副本没有保留我期望的顺序。 IE。新表VibeFGEventsAfterStudyStart 中的行在从VibeFGEvents.id 复制的VibeFGEventsAfterStudyStart.id 列中不是单调递增的。

在 T-SQL 中,我如何在 VibeFGEventsStudyStart 中保留来自 VibeFGEvents 的行的顺序?

【问题讨论】:

  • 为简单起见,你不能用更短的名字,这样会更清楚吗?
  • @RoyiNamir 短名称 = 意义不大,所以会更清楚。
  • @TonyHopkinson 寻找 VibeFGEventsStudyStart.MIN_TitleInstID 并寻找所有其他人是否同名,这很痛苦。
  • 跟Sql server有什么关系?我说的是提问。我们不在乎他的真实姓名。我们关心他的问题。这就是他在这里的原因。问一个问题。(更清楚 = 对我们来说,SO 用户)
  • 由于关系数据库本身确实没有任何 order 的概念 - 在插入时 preserving 顺序有什么意义?一般来说,任何关系表默认无序如果您在SELECT 中明确定义ORDER BY 子句,则可以订购结果集

标签: sql-server tsql sql-order-by select-into


【解决方案1】:

我知道这有点旧,但我需要做类似的事情。我想将一个表的内容插入到另一个表中,但顺序是随机的。我发现我可以通过使用select top norder by newid() 来做到这一点。如果没有“top n”,则不会保留顺序,并且第二个表的行顺序与第一个表相同。但是,使用“前 n”,顺序(在我的情况下是随机的)被保留。我使用了大于行数的“n”值。所以我的查询是这样的:

insert Table2 (T2Col1, T2Col2)
  select top 10000 T1Col1, T1Col2
  from Table1
  order by newid()

【讨论】:

  • 感谢您的实际回答
【解决方案2】:

为了什么?

Point is – 表中的数据没有排序。在 SQL Server 中,表的内在存储顺序是(如果已定义)聚集索引的顺序。

插入数据的顺序基本上是“不相关的”。数据写入表的那一刻就忘记了。

因此,即使你得到了这些东西,也没有任何收获。如果在处理数据时需要排序,则必须在获取它的 select 上放置 order by 子句。其他任何内容都是随机的 - 即您输入数据的顺序未确定并且可能会更改。

因此,在您尝试实现的插入上具有特定顺序是没有意义的。

SQL 101:集合没有顺序。

【讨论】:

  • 这是 SQL 中的一个核心概念——几乎 SQL 是基于集合的。在实现集合时(在选择中),必须强制执行顺序。除非在 SELECT 中定义了一个顺序;结果是任意的,理论上可以在调用之间改变。一旦数据在表中,数据或插入的顺序就会丢失。没有“隐藏的自然”顺序。这是基于集合的操作的核心。
  • 不,不保证。它可能会发生,也可能不会。如果表 1 上的聚集索引具有另一个顺序,则它可能以该顺序出现。如果由于 where 子句而存在另一个索引,则它以随机顺序出现,具体取决于 sql server 决定搜索事物的方式。在更复杂的查询中,您可能会发现使用不同线程然后合并结果的并行性。不保证意味着您依赖可能会中断的副作用。被称为超级蹩脚的编程。
  • 有时默认情况下以特定顺序查看数据只是很好。不需要,你说得对,但有些人更喜欢它。
  • 没有聚集索引的表怎么样,比如那些有 UUID 主键的表?
  • “What for”对任何人都没有帮助。在很多情况下,我需要将数据保存到临时表中,以便几天后修复数据。它不是作为永久存储,而是作为数据修复的临时解决方案。无论如何,下一个答案应该是投票。
【解决方案3】:

我发现了一个特定场景,我们希望在列内容中以特定顺序创建新表:

  • 行数非常大(从 200 到 20 亿行),因此我们使用 SELECT INTO 而不是 CREATE TABLE + INSERT,因为需要尽可能快地加载(最小日志记录)。我们已经测试了使用trace flag 610 加载已创建的带有聚集索引的空表,但仍然比以下方法花费更长的时间。
  • 为了查询性能,我们需要按特定列对数据进行排序,因此我们在表加载后创建一个CLUSTERED INDEX。我们放弃创建非聚集索引,因为它需要再次读取未包含在索引中的有序列中的数据,并且我们放弃创建全覆盖非聚集索引,因为它实际上会使所需空间量翻倍拿着桌子。

如果您设法以某种方式创建具有已“排序”列的表,则创建聚集索引(具有相同顺序)所花费的时间比没有数据时要少得多订购。有时(您必须测试您的案例),对SELECT INTO 中的行进行排序比无序加载并稍后创建聚集索引要快。

问题是 SQL Server 2012+ 在执行 INSERT INTO 或执行 SELECT INTO 时将忽略 ORDER BY 列列表。如果您在 SELECT INTO 上指定 IDENTITY 列,或者如果插入的表具有 IDENTITY 列,它将考虑 ORDER BY 列,但只是为了确定标识值 而不是基础表中的实际存储顺序。在这种情况下,排序可能会发生但不能保证,因为它高度依赖于执行计划。

我们发现的一个技巧是使用UNION ALL 的结果执行SELECT INTO 会使引擎执行SORT(并不总是显式的SORT 运算符,有时是MERGE JOIN CONCATENATION 等)如果您有ORDER BY 列表。这样,select into 已经按照我们稍后创建聚集索引的顺序创建了新表,因此创建索引所需的时间更少。

所以你可以重写这个查询:

SELECT
    FirstColumn = T.FirstColumn,
    SecondColumn = T.SecondColumn
INTO
    #NewTable
FROM
    VeryBigTable AS T
ORDER BY            -- ORDER BY is ignored!
    FirstColumn,
    SecondColumn

SELECT
    FirstColumn = T.FirstColumn,
    SecondColumn = T.SecondColumn
INTO
    #NewTable
FROM
    VeryBigTable AS T

UNION ALL

-- A "fake" row to be deleted
SELECT
    FirstColumn = 0,
    SecondColumn = 0

ORDER BY
    FirstColumn,
    SecondColumn

我们已经使用过这个技巧几次,但我不能保证它总是会排序。我只是将其发布为一种可能的解决方法,以防有人遇到类似情况。

【讨论】:

    【解决方案4】:

    只需将top 添加到您的 sql 中,其数字大于实际行数即可:

    SELECT top 25000 * 
    into spx_copy
      from SPX
      order by date
    

    【讨论】:

    • 通过添加 TOP,我可以运行 SQL。但是,它们仍然被加载到目标表中,就好像“ORDER BY”不存在一样......我单独测试了该命令,作为选择它仍然可以正常工作,并对其进行排序。有什么想法吗?
    【解决方案5】:

    您不能使用 ORDER BY 执行此操作,但如果您在 SELECT INTO 之后在 VibeFGEvents.id 上创建聚集索引,则该表将按 VibeFGEvents.id 在磁盘上排序。

    【讨论】:

      【解决方案6】:

      我在 MS SQL 2012 上进行了测试,它清楚地向我表明,插入...选择...顺序是有意义的。这是我所做的:

      create table tmp1 (id int not null identity, name sysname);
      create table tmp2 (id int not null identity, name sysname);
      
      insert into tmp1 (name) values ('Apple');
      insert into tmp1 (name) values ('Carrot');
      insert into tmp1 (name) values ('Pineapple');
      insert into tmp1 (name) values ('Orange');
      insert into tmp1 (name) values ('Kiwi');
      insert into tmp1 (name) values ('Ananas');
      insert into tmp1 (name) values ('Banana');
      insert into tmp1 (name) values ('Blackberry');
      
      select * from tmp1 order by id;
      

      我得到了这份清单:

      • 1 个苹果
      • 2 根胡萝卜
      • 3 菠萝
      • 4 橙
      • 5 猕猴桃
      • 6 凤梨
      • 7 香蕉
      • 8 黑莓

      这里没有惊喜。然后我以这种方式从 tmp1 复制到 tmp2:

      insert into tmp2 (name)
      select name
      from tmp1
      order by id;
      
      select * from tmp2 order by id;
      

      我得到了和以前一样的确切响应。苹果到黑莓。 现在颠倒顺序来测试它:

      delete from tmp2;
      
      insert into tmp2 (name)
      select name
      from tmp1
      order by id desc;
      
      select * from tmp2 order by id;
      
      • 9 黑莓
      • 10 香蕉
      • 11 凤梨
      • 12 猕猴桃
      • 13橙
      • 14 菠萝
      • 15根胡萝卜
      • 16 苹果

      所以 tmp2 中的顺序也颠倒了,所以 order by 有意义当目标表中有标识列时

      【讨论】:

        【解决方案7】:

        之所以需要这个(特定顺序)是因为您无法在子查询中定义顺序,因此,想法是,如果您创建一个表变量,然后从该表变量进行查询,您会认为您会保留顺序(例如,连接必须按顺序排列的行 - 例如对于 XML 或 json),但您不能。 所以你会怎么做? 答案是通过在您的选择中使用 TOP 来强制 SQL 对其进行排序(只需选择一个足够高的数字以覆盖所有行)。

        【讨论】:

        • Another answer 已经提到了这一点。请详细说明为什么使用TOP 使其工作
        • 这是设计使然(这很直观,因为如果没有返回集的排序,TOP 将毫无意义)。就像我在上面的评论中澄清的那样:“......强制 SQL 使用 TOP ......”在我上面的评论中(是的,这已经提到过,但我希望为其他人澄清它。
        【解决方案8】:

        我遇到了同样的问题,我需要保留订单的一个原因是当我尝试使用 ROLLUP 根据原始数据而不是该列中的数据的平均值来获取加权平均值时。例如,假设我想查看基于四个商店位置销售的单位数量的平均利润?通过创建等式利润/#Units = Avg,我可以很容易地做到这一点。现在我在我的 GROUP BY 中包含一个 ROLLUP,这样我还可以查看所有位置的平均值。现在我对自己想,“这是很好的信息,但我希望按照从最佳平均到最差的顺序来查看它,并将整体保持在列表的底部(或顶部)。” ROLLUP 在这方面会让你失望,所以你采取不同的方法。

        为什么不根据您需要保留的顺序(顺序)创建行号?

            SELECT OrderBy = ROW_NUMBER() OVER(PARTITION BY 'field you want to count' ORDER BY 'field(s) you want to use ORDER BY')
            , VibeFGEvents.*  
            FROM VibeFGEvents
            LEFT OUTER JOIN VibeFGEventsStudyStart
            ON 
                CHARINDEX(REPLACE(REPLACE(REPLACE(logName, 'MyVibe ', ''), ' new laptop', ''), ' old laptop', ''), excelFilename) > 0
                AND VibeFGEventsStudyStart.MIN_TitleInstID <= VibeFGEvents.TitleInstID
                AND VibeFGEventsStudyStart.MIN_WinInstId <= VibeFGEvents.WndInstID
            WHERE VibeFGEventsStudyStart.excelFilename IS NOT NULL
        

        现在您可以使用表中的 OrderBy 字段来设置值的顺序。我从上面的查询中删除了 ORDER BY 语句,因为它不会影响数据如何加载到表中。

        【讨论】:

          【解决方案9】:

          尝试使用INSERT INTO 而不是SELECT INTO

          INSERT INTO VibeFGEventsAfterStudyStart 
          SELECT VibeFGEvents.* 
          FROM VibeFGEvents
          LEFT OUTER JOIN VibeFGEventsStudyStart
          ON 
              CHARINDEX(REPLACE(REPLACE(REPLACE(logName, 'MyVibe ', ''), ' new laptop', ''), ' old laptop', ''), excelFilename) > 0
              AND VibeFGEventsStudyStart.MIN_TitleInstID <= VibeFGEvents.TitleInstID
              AND VibeFGEventsStudyStart.MIN_WinInstId <= VibeFGEvents.WndInstID
          WHERE VibeFGEventsStudyStart.excelFilename IS NOT NULL
          ORDER BY VibeFGEvents.id`
          

          【讨论】:

          • 一张表没有顺序。请参阅此问题中的其他 cmets。
          • 我知道在这种情况下表格没有顺序。关键是可以使用 INSERT INTO 插入行,并带有可以排序的子查询。
          • 一个简单的观察:在这种情况下,ORDER BY 子句用于SELECT VibeFGEvents.* FROM ... 语句而不是INSERT 语句。
          • 插入什么顺序都没有关系。选择时,该顺序消失了。你不能再把它“拿出来”。
          • 除了第一列的隐式排序通常可以是顺序整数标识主键。
          猜你喜欢
          • 2013-05-15
          • 1970-01-01
          • 2017-10-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-27
          • 1970-01-01
          相关资源
          最近更新 更多