Need to generate n rows based on a value in a colu

2020-02-09 03:31发布

问题:

I have the following table

TABLE A

ID | QUANTITY
------------
1  | 3
2  | 2

What I need is

TABLE B

ID | Ref No.
------------
1  | MyRef1
1  | MyRef2
1  | MyRef3
2  | AnotherRef1
2  | AnotherRef2

i.e. I need to generate Table B with the same number of rows as the quantity in A with an ascending ref no. on each row.

I can do it with cursors or UDFs but is there a more graceful solution?

回答1:

I'll assume

  1. MyRef etc is a column in TableA
  2. You have a numbers table

Something like:

SELECT * INTO #TableA
FROM
 (
    SELECT  1 AS ID, 3 AS QUANTITY, 'MyRef' AS refColumn
    UNION ALL
    SELECT 2, 2, 'AnotherRef'
) T


;WITH Nbrs ( Number ) AS (
    SELECT 1 UNION ALL
    SELECT 1 + Number FROM Nbrs WHERE Number < 99
)
SELECT
   A.ID, A.refColumn + CAST(N.Number AS varchar(10))
FROM
   #TableA A
   JOIN
   Nbrs N ON N.Number <= A.QUANTITY


回答2:

This will create the number of rows you want, in SQL Server 2005+, though I'm not sure exactly how you want to determine what MyRef and AnotherRef should be...

WITH
  expanded
AS
(
  SELECT id, Quantity FROM myTable

UNION ALL

  SELECT id, Quantity - 1 FROM expanded WHERE Quantity > 1
)

SELECT
  *,
  ROW_NUMBER() OVER (ORDER BY id, Quantity) AS unique_ref
FROM
  expanded
ORDER BY
  id,
  Quantity


回答3:

Following gives you the correct number of rows but my observation is the same as @Dems: How do you determine MyRef and AnotherRef?

Note that this requires at least SQL Server 2005

SQL Statement

;WITH TableA (ID, Quantity) AS (
    SELECT  1, 3
    UNION ALL SELECT 2, 2
)
, q AS (
    SELECT  ID
            , Number = 1
            , Quantity
    FROM    TableA
    UNION ALL 
    SELECT  ID
            , Number = Number + 1
            , Quantity
    FROM    q
    WHERE   Quantity > Number
)
SELECT  ID
        , CASE WHEN ID = 1 THEN 'MyRef' + CAST(Number AS VARCHAR(1))
               WHEN ID = 2 THEN 'AnotherRef' + CAST(Number AS VARCHAR(1))
          END AS [Ref No.]
FROM    q       
ORDER BY
        ID

Output

ID  Ref No.
1   MyRef1
1   MyRef2
1   MyRef3
2   AnotherRef1
2   AnotherRef2


回答4:

A Numbers table solution will work in MS Access as long as the number of records in tblNumbers exceeds the maximum of Quantity values found in TableA:

SELECT TableA.ID, TableA.Quantity, tblNumbers.RecNum FROM TableA LEFT JOIN tblNumbers ON tblNumbers.RecNum <= TableA.Quantity ORDER BY TableA.ID, tblNumbers.RecNum;

NOTE: tblNumbers.RecNum is a Long Integer starting with row 1 = 1, row 2 = 2, etc.



回答5:

Single select to generate rows by column TableA.quantity. Used only ISO/ANSI SQL Standard syntax 2003 (DB must support window function).

TableA definition:

|----|----------|---------------|
| id | quantity | another_value |
|----|----------|---------------|
|  1 |         3|       value_a |
|  2 |         2|       value_b |
|  3 |         6|       value_c |
|----|----------|---------------|

CREATE TABLE TableA AS
  (SELECT 1 as ID, 3 AS quantity, 'value_a' AS another_value 
  UNION SELECT 2, 2, 'value_b' 
  UNION SELECT 3, 6, 'value_c');

The following query can be used for quantity value up to 1000. For quantity up to 10000 extend query by statement CROSS JOIN ten AS rank10000...

SELECT 
  ROW_NUMBER() OVER(order by id) as unique_id, 
  id as original_id, 
  another_value || ROW_NUMBER() OVER (PARTITION BY id) as another_value
FROM TableA
INNER JOIN
  (SELECT row_number() OVER () AS rnum FROM
    (WITH ten AS (SELECT 1 AS id UNION SELECT 2 
         UNION SELECT 3 UNION SELECT 4 
         UNION SELECT 5 UNION SELECT 6 
         UNION SELECT 7 UNION SELECT 8 
         UNION SELECT 9 UNION SELECT 10)
     SELECT * 
     FROM ten AS rank10 
     CROSS JOIN ten AS rank100 
     CROSS JOIN ten AS rank1000
    ) helper
  ) help ON help.rnum <= TableA.quantity

SQL Result:

|-----------|-------------|---------------|
| unique_id | original_id | another_value |
|-----------|-------------|---------------|
|         1 |           1 |      value_a1 |
|         2 |           1 |      value_a2 |
|         3 |           1 |      value_a3 |
|         4 |           2 |      value_b1 |
|         5 |           2 |      value_b2 |
|         6 |           3 |      value_c1 |
|         7 |           3 |      value_c2 |
|         8 |           3 |      value_c3 |
|         9 |           3 |      value_c4 |
|        10 |           3 |      value_c5 |
|        11 |           3 |      value_c6 |
|-----------|-------------|---------------|

It should be work on PostgreSQL, Oracle or MSSQL (tested on PostgreSQL 9.0)

Edited: Optimized query using statement WITH RECURSIVE (idea from MatBailie):

SELECT 
  ROW_NUMBER() OVER() as unique_id, 
  id as original_id, 
  another_value || ROW_NUMBER() OVER (PARTITION BY id) as another_value
FROM 
  (WITH RECURSIVE helper AS 
    (SELECT id, quantity, another_value FROM TableA 
     UNION ALL 
     SELECT id, quantity-1, another_value FROM helper WHERE quantity > 1
    ) SELECT * FROM helper ORDER BY id, quantity
  ) TableB


回答6:

This would also do the trick. It uses recursion, creates a table with rows 1-100.

WITH NBR ( NUM ) AS (
    SELECT 1 UNION ALL
    SELECT 1 + NUM FROM NBR 
    WHERE NUM < 100
)
SELECT * into NUMBERS from NBR