【问题标题】:SQL: Repeat a result row multiple times, and number the rowsSQL:多次重复结果行,并对行进行编号
【发布时间】:2012-05-12 12:27:00
【问题描述】:

我有一个 SQL 查询,结果如下:

value | count
------+------
foo   |     1
bar   |     3
baz   |     2

现在我想扩展它,以便count 大于 1 的每一行出现多次。我还需要对这些行进行编号。所以我会得到:

value | count | index
------+-------+------
foo   |     1 |     1
bar   |     3 |     1
bar   |     3 |     2
bar   |     3 |     3
baz   |     2 |     1
baz   |     2 |     2

我必须在所有主要数据库(Oracle、SQL Server、MySQL、PostgreSQL 甚至更多)上进行这项工作。因此,一个适用于不同数据库的解决方案将是理想的,但可以通过巧妙的方法使其适用于任何数据库。

【问题讨论】:

    标签: mysql sql sql-server oracle postgresql


    【解决方案1】:

    你可以使用数字表

    SELECT value, count, number
    FROM table
        JOIN Numbers 
            ON table.count >= Numbers.number
    

    Here is a SQLFiddle using MSSQL

    【讨论】:

    • 对为什么这样做有任何见解吗?顺便说一句,4 年后对我的解决方案效果很好。
    • 最简单的解释是,它不是加入 1 行,而是加入所有行,直到计数后的数字。 IE。 Count = 3,然后匹配 1,2,3,创建 3|1、3|2 和 3|3 结果
    • 什么是数字表??是系统表吗?
    【解决方案2】:

    对于 MySQL,使用穷人的generate_series,这是通过视图完成的。 MySQL 是big four 中唯一没有任何 CTE 功能的 RDBMS。

    实际上你可以在支持视图的数据库上使用这种技术。这就是几乎所有的数据库

    生成器技术来源:http://use-the-index-luke.com/blog/2011-07-30/mysql-row-generator#mysql_generator_code

    我们所做的唯一小改动是我们将原始技术中的按位(左移按位或)技术分别替换为仅乘法和加法;因为 Sql Server 和 Oracle 没有左移运算符。

    这种抽象 99% 保证适用于所有数据库,除了 Oracle; Oracle 的SELECT 没有任何表是无法运行的,为此,需要从虚拟表中进行选择,Oracle 已经提供了一张,称为DUAL 表。数据库可移植性是白日梦:-)

    这是适用于所有 RDBMS 的抽象视图,没有按位操作(在这种情况下这实际上不是必需的)和功能细微差别(我们删除了 CREATE VIEW 上的 OR REPLACE,只有 Postgresql 和 MySQL 支持它们)在所有主要数据库中。

    Oracle 警告:只需将 FROM DUAL 放在每个 SELECT 表达式之后

    CREATE VIEW generator_16
    AS SELECT 0 n UNION ALL SELECT 1  UNION ALL SELECT 2  UNION ALL 
       SELECT 3   UNION ALL SELECT 4  UNION ALL SELECT 5  UNION ALL
       SELECT 6   UNION ALL SELECT 7  UNION ALL SELECT 8  UNION ALL
       SELECT 9   UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL
       SELECT 12  UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL 
       SELECT 15;
    
    CREATE VIEW generator_256
    AS SELECT ( ( hi.n * 16 ) + lo.n ) AS n
         FROM generator_16 lo, generator_16 hi;
    
    CREATE VIEW generator_4k
    AS SELECT ( ( hi.n * 256 ) + lo.n ) AS n
         FROM generator_256 lo, generator_16 hi;
    
    CREATE VIEW generator_64k
    AS SELECT ( ( hi.n * 256 ) + lo.n ) AS n
         FROM generator_256 lo, generator_256 hi;
    
    CREATE VIEW generator_1m
    AS SELECT ( ( hi.n * 65536 ) + lo.n ) AS n
         FROM generator_64k lo, generator_16 hi;
    

    然后使用这个查询:

    SELECT t.value, t.cnt, i.n
    FROM tbl t
    JOIN generator_64k i 
    ON i.n between 1 and t.cnt
    order by t.value, i.n
    

    Postgresql:http://www.sqlfiddle.com/#!1/1541d/1

    甲骨文:http://www.sqlfiddle.com/#!4/26c05/1

    Sql 服务器:http://www.sqlfiddle.com/#!6/84bee/1

    MySQL:http://www.sqlfiddle.com/#!2/78f5b/1

    【讨论】:

    • 我最喜欢这个答案,因为它是便携的。
    • 这个答案帮助我解决了我们查询中的一个主要问题,并帮助我使用此代码构建了一个全新的报告范围。希望我能做的不仅仅是投票这个答案。 :)
    【解决方案3】:

    您要求提供与 db 无关的解决方案,@Justin 给了您一个不错的解决方案。
    你还要求

    使其适用于任何数据库的巧妙方法

    PostgreSQL 有一个:generate_series() 可以满足您开箱即用的要求:

    SELECT val, ct, generate_series(1, ct) AS index
    FROM   tbl;
    

    顺便说一句,我宁愿不使用 valuecount 作为列名。使用reserved words 作为标识符是不好的做法。改用valct

    【讨论】:

    • 太棒了!我希望这在任何地方都有效。是的,您对保留字的看法是正确的,感谢您指出。
    【解决方案4】:

    MySQL 确实是数据库世界的 IE,在标准和功能方面它是如此的顽固。

    适用于除 MySQL 之外的所有主要 RDBMS:

    with 
    -- Please add this on Postgresql:
    -- RECURSIVE
    tbl_populate(value, cnt, ndx) as
    (
      select value, cnt, 1 from tbl
    
      union all
    
      select t.value, t.cnt, tp.ndx + 1
      from tbl t
      join tbl_populate tp 
      on tp.value = t.value  
      and tp.ndx + 1 <= t.cnt
    )
    select * from tbl_populate
    order by cnt, ndx
    

    SQL 服务器:http://www.sqlfiddle.com/#!6/911a9/1

    甲骨文:http://www.sqlfiddle.com/#!4/198cd/1

    Postgresql:http://www.sqlfiddle.com/#!1/0b03d/1

    【讨论】:

    • 很好,它工作,虽然我不太明白它在做什么:-)
    • 呵呵,真的吗?现在我受到启发,写了一篇关于 CTE 递归的博客。但基本上,行生成器是最简单的 CTE 递归,它遵循尾递归的逻辑;尾递归几乎与循环一一对应。 CTE 上的分层递归和对象图递归有点难以理解
    • with之后不应该有recursive关键字吗?
    • @vyegorov true,仅适用于 Postgresql :-) 事实上,我的 sqlfiddle 中包含该关键字,请检查此答案的最后一个链接(Postgresql)。其他数据库可以推断递归并且不需要 recursive 指令。数据库可移植性真的是一个白日梦:-)
    【解决方案5】:

    创建一个数字表 - 它的定义可能会因平台而略有不同(这是针对 SQL Server):

    CREATE TABLE Numbers(Number INT PRIMARY KEY);
    
    INSERT Numbers 
    SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY name)
    FROM sys.all_columns;
    

    现在这个 temp 也是 SQL Server,但演示了应该在您指定的 RDBMS 中有效的连接语法(尽管我承认我不使用它们,所以我无法测试):

    DECLARE @foo TABLE(value VARCHAR(32), [count] INT);
    
    INSERT @foo SELECT 'foo', 1
    UNION ALL SELECT 'bar', 3
    UNION ALL SELECT 'baz', 2;
    
    SELECT f.value, f.[count], [index] = n.Number
    FROM @foo AS f, Numbers AS n
    WHERE n.Number <= f.[count];
    

    结果(同样是 SQL Server):

    value | count | index
    ------+-------+------
    foo   |     1 |     1
    bar   |     3 |     1
    bar   |     3 |     2
    bar   |     3 |     3
    baz   |     2 |     1
    baz   |     2 |     2
    

    【讨论】:

      【解决方案6】:

      仅供欣赏,SQL Server 2005 及更高版本可以递归处理:

      declare @Stuff as Table ( Name VarChar(10), Number Int )
      insert into @Stuff ( Name, Number ) values ( 'foo', 1 ), ( 'bar', 3 ), ( 'baz', 2 )
      
      select * from @Stuff
      
      ; with Repeat ( Name, Number, Counter ) as (
        select Name, Number, 1
          from @Stuff
          where Number > 0
        union all
        select Name, Number, Counter + 1
          from Repeat
          where Counter < Number
        )
      select *
        from Repeat
        order by Name, Counter -- Group by name.
        option ( maxrecursion 0 )
      

      【讨论】:

        【解决方案7】:

        通过一个简单的JOIN,您可以达到重复记录 n 次的目的。
        以下查询将每条记录重复 20 次。

        SELECT  TableName.*
        FROM    TableName
        JOIN    master.dbo.spt_values on type = 'P' and number < 20
        


        master.dbo.spt_values on type = 'P' 的注意事项:
        该表用于获取以type='P'为条件硬编码在其中的一系列数字。

        【讨论】:

          【解决方案8】:

          您可以使用 CTE:

          WITH Numbers(Num) AS
          (
              SELECT 1 AS Num
              UNION ALL 
              SELECT Num + 1
              FROM   Numbers c
              WHERE  c.Num < 1000
          )
          
          SELECT VALUE,COUNT, number
          FROM   TABLE
                 JOIN Numbers
                      ON  TABLE.count >= Numbers.Num
          OPTION(MAXRECURSION 1000)
          

          【讨论】:

            【解决方案9】:

            在 Oracle 中,我们可以使用 LEVELCROSS JOIN 的组合。

              SELECT *
                FROM yourtable
                     CROSS JOIN (    SELECT ROWNUM index_t
                                       FROM DUAL
                                 CONNECT BY LEVEL <= (SELECT MAX (count_t) FROM yourtable))
               WHERE index_t <= count_t
            ORDER BY VALUE, index_t;
            

            DEMO

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-02-02
              • 1970-01-01
              • 1970-01-01
              • 2021-12-09
              • 2013-05-25
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多