sql查询将单个列值拆分为多行

时间:2011-04-26 18:30:32

标签: sql-server sql-server-2005 sql-server-2008

我想创建一个sql查询,将单个列值拆分为多行,如:

SELECT ID, PRODUCT_COUNT FROM MERCHANT WHERE ID = 3050

ID          PRODUCT_COUNT
----------- -------------
3050        591

根据这个结果,我想要6行如下:

ID      RANGE    
3050    0-100
3050    101-200
3050    201-300
3050    301-400
3050    401-500
3050    501-591

如何在查询中实现此目的?

5 个答案:

答案 0 :(得分:2)

WITH cte AS (
  SELECT
    m.ID,
    PRODUCT_COUNT,
    LoBound = (v.number - 1) * 100 + 1,
    HiBound = v.number * 100
  FROM MERCHANT m
    INNER JOIN master..spt_values v
      ON v.type = 'P' AND v.number BETWEEN 1 AND (m.PRODUCT_COUNT - 1) / 100 + 1
  WHERE m.ID = 3050
)
SELECT
  ID,
  RANGE = CAST(CASE LoBound
                 WHEN 1 THEN 0
                 ELSE LoBound
               END AS varchar)
        + '-'
        + CAST(CASE
                 WHEN HiBound < PRODUCT_COUNT THEN HiBound
                 ELSE PRODUCT_COUNT
               END AS varchar)
FROM cte

第一个CASE确保第一个范围以0开头,而不是1,与样本输出中的相同。

答案 1 :(得分:1)

抱歉,代码已删除。我犯了一个错误,如果Product_Count可以被100整除,它会给出一个不正确的最后一行。

更新: Andriy的代码仍然正确。我错过了一个“-1”。我修复了它并重新发布了测试设置和替代解决方案。

Andriy和我的代码都以正确的顺序生成了这个实验的输出,但我添加了一个ORDER BY来保证它。

以下是测试设置的代码......

    --===== Conditionally drop and create a test table for
         -- everyone to work against.
         IF OBJECT_ID('tempdb..#Merchant','U') IS NOT NULL
            DROP TABLE #Merchant
    ;
     SELECT TOP 10000
            ID            = IDENTITY(INT,1,1),
            Product_Count = ABS(CHECKSUM(NEWID()))%100000
       INTO #Merchant
       FROM sys.all_columns ac1
      CROSS JOIN sys.all_columns ac2
    ;
      ALTER TABLE #Merchant
        ADD PRIMARY KEY CLUSTERED (ID)
    ;
    --===== Make several entries where there's a known test setup.
     UPDATE #Merchant
        SET Product_Count = CASE
                                WHEN ID = 1 THEN 0
                                WHEN ID = 2 THEN 1
                                WHEN ID = 3 THEN 99
                                WHEN ID = 4 THEN 100
                                WHEN ID = 5 THEN 101
                                WHEN ID = 6 THEN 99999
                                WHEN ID = 7 THEN 100000
                                WHEN ID = 8 THEN 100001
                            END
  WHERE ID < = 8
 ;

这是我之前发布的-1校正的替代方案。

WITH
cteCreateRanges AS
(--==== This determines what the ranges are
 SELECT m.ID,
        RangeStart = t.Number*100+SIGN(t.Number),
        RangeEnd   =(t.Number+1)*100,
        Product_Count
   FROM master.dbo.spt_Values t
  CROSS JOIN #Merchant        m
  WHERE t.Number BETWEEN 0 AND (m.Product_Count-1)/100
    AND t.Type = 'P'
    AND m.ID BETWEEN 1 AND 8 -- = @FindID -<<<---<<< Or use a single variable to find.
)--==== This makes the output "pretty" and sorts in correct order
 SELECT ID,
        [Range] = CAST(RangeStart AS VARCHAR(10)) + '-'
                + CASE
                  WHEN RangeEnd <= Product_Count
                  THEN CAST(RangeEnd AS VARCHAR(10))
                  ELSE CAST(Product_Count AS VARCHAR(10))
                  END
   FROM cteCreateRanges
  ORDER BY ID, RangeStart
;

对于之前的错误感到抱歉。谢谢,Andriy,抓住它。

答案 2 :(得分:0)

您可以创建一个这样的表(我正在更改第一个范围以包含其他100个元素以使其更容易,并将其基于一个,以便索引将与总计数相匹配):

CountRangeBoundary

MinIndexInRange
---------------
1
101
201
301
401
501
601
...

然后像这样做一个θ-join:

SELECT m.ID,
       crb.MinIndexInRange AS RANGE_MIN,
       MIN( crb.MinIndexInRange + 100, m.PRODUCT_COUNT) AS RANGE_MAX
FROM   MERCHANT m
       JOIN CountRangeBoundry crb ON crb.MinIndexInRange <= m.PRODUCT_COUNT
WHERE  m.ID = 3050

答案 3 :(得分:0)

看起来这些范围是一段数据,所以它们应该真的在一个表中(即使你不指望它们会改变,因为它们会)。这使得这项任务变得微不足道有好处:

CREATE TABLE My_Ranges (    -- Use a more descriptive name
    range_start    SMALLINT    NOT NULL,
    range_end      SMALLINT    NOT NULL,
    CONSTRAINT PK_My_Ranges PRIMARY KEY CLUSTERED (range_start)
)
GO

SELECT
    P.id,
    R.range_start,
    CASE
        WHEN R.range_end < P.product_count THEN R.range_end
        ELSE P.product_count
    END AS range_end
FROM
    Products P
INNER JOIN My_Ranges R ON
    R.range_start <= P.product_count

如果您的范围始终是连续的,则可以省略range_end列。您的查询会变得有点复杂,但您不必担心范围重叠或范围内的差距。

答案 4 :(得分:0)

您可以尝试递归CTE。

WITH CTE AS
(
    SELECT Id, 0 MinB, 100 MaxB, [Range]
    FROM YourTable
    UNION ALL
    SELECT Id, CASE WHEN MinB = 0 THEN MinB+101 ELSE MinB+100 END, MaxB + 100, [Range]
    FROM CTE
    WHERE MinB < [Range]
)

SELECT  Id, 
        CAST(MinB AS VARCHAR) + ' - ' + CAST(CASE WHEN MaxB>[Range] THEN [Range] ELSE MaxB END AS VARCHAR) [Range]
FROM CTE
WHERE MinB < [Range]
ORDER BY Id, [Range]
OPTION(MAXRECURSION 5000)

我对5000的递归级别设置了一个限制,但你可以改变它(或者将它保持为零,这意味着基本上要继续进行递归,直到它可以)