SQL导入期间的分割行 - 对特定行运行两次INSRERT INTO

时间:2016-06-17 21:54:48

标签: sql sql-server sql-insert union-all

让我们假设一个超简化的数据示例:

ID    AKey   AVal
-----------------
1     AB     94
2     Q      48
3     Z      56
4     AB     12
5     T      77
...   ...    ...

我想分开" AB"分成几行" A"和" B"在我的导入脚本中,我通常会这样做:

INSERT INTO MyNewTable
SELECT 
   SRC.ID as OldIDRef,
   SRC.AKey as NewKey,
   SRC.AVal as NewVal
FROM OldTable as SRC

所以基本上,我想复制" AB"选择中的行并执行一些特定于此行的计算(让我们说将AVal除以2)

我能想到的唯一解决办法就是这样:

INSERT INTO MyNewTable
SELECT 
   SRC.ID as OldIDRef,
   CASE SRC.AKey = 'BA' THEN SUBSTRING(SRC.AKey,1,1) END as NewKey,
   CASE SRC.AKey = 'BA' THEN SRC.AVal / 2 END as NewVal
FROM OldTable as SRC

UNION ALL

SELECT 
   SRC.ID as OldIDRef,
   SUBSTRING(SRC.AKey,2,1) as NewKey,
   SRC.AVal / 4 + 10 as NewVal
FROM OldTable as SRC
WHERE SRC.AKey = 'BA'

我的导入中需要多次这样的过程,所以我想知道,如果我没有错过一些更简单的解决方案?

2 个答案:

答案 0 :(得分:0)

无论如何,这是脚本,它适用于给定的值,应该是UNION ALL的两倍:

;WITH s1 as (
    SELECT ID, LEFT(AKey,1) as NewKey1, AVal / 2 as NewVal1
        , RIGHT(AKey,1) as NewKey2, AVal / 4 + 10 as NewVal2
        , AKey, AVal
    FROM Split)
SELECT ID as OldIDRef,
    CASE NKey.AKey WHEN 'A' THEN NewKey1 WHEN 'B' THEN NewKey2 ELSE s1.AKey END as NewKey,
    CASE NKey.AKey WHEN 'A' THEN NewVal1 WHEN 'B' THEN NewVal2 ELSE s1.AVal END as NewVal
FROM s1 
INNER JOIN (SELECT 'A' as AKey UNION ALL SELECT 'B' UNION ALL SELECT NULL) as NKey 
ON NKey.AKey = NewKey1 or NKey.AKey = NewKey2 or (NKey.AKey is Null and not (NewKey1 = 'A' and NewKey2 = 'B') )

答案 1 :(得分:0)

假设您可能正在寻找可以缩放到密钥长度中的N#个字符的答案,以及将新值分配给从中分割出来的密钥数量的答案。我会用递归的cte把它拉下来。使用您的示例数据添加另一行,其中包含3个字符,如'GHI',并运行此代码并查看结果比例超过2个字符。

;WITH cteRecursive AS (
    SELECT
       Id
       ,AKey
       ,LEFT(AKey,1) AS NewAKey
       ,RIGHT(Akey,LEN(AKey) - 1) AS RemainingKey
       ,AVal
       ,1 AS [Level]
    FROM
       @Table

    UNION ALL

    SELECT
       t.Id
       ,t.AKey
       ,LEFT(c.RemainingKey,1) AS NewAKey
       ,RIGHT(RemainingKey,LEN(RemainingKey) - 1) AS RemainingKey
       ,t.AVal
       ,c.[Level] + 1 AS [Level]
    FROM
       @Table t
       INNER JOIN cteRecursive c
       ON t.Id = c.Id
       AND LEN(c.RemainingKey) > 0
)

SELECT
    Id
    ,AKey AS OriginalAKey
    ,NewAKey
    ,AVal AS OriginalAVal
    ,AVal / 2.00 AS NewVal
    ,AVal / CAST(MAX([Level]) OVER (PARTITION BY Id) AS DECIMAL(4,2)) AS NewValAsPortionOfLevel
    ,AVal / CAST(LEN(AKey) AS DECIMAL(4,2)) AS NewValAsPortionOfKeyLength
FROM
    cteRecursive

以下是我想要的Table变量

DECLARE @Table AS TABLE (Id INT IDENTITY(1,1), AKey VARCHAR(100), AVal INT)
INSERT INTO @Table (AKey, AVal)
VALUES ('AB',94),('Q',48),('Z',56),('AB',12),('T',77),('ghi',100)

如果没有拆分密钥,你实际上可以简化递归cte并走这条路。通过使用Level < LEN(AKey),递归将停在正确的位置,您不需要任何其他字符串操作。

;WITH cteRecursive AS (
    SELECT
       Id
       ,AKey
       ,AVal
       ,1 AS [Level]
    FROM
       @Table

    UNION ALL

    SELECT
       t.Id
       ,t.AKey
       ,t.AVal
       ,c.[Level] + 1 AS [Level]
    FROM
       @Table t
       INNER JOIN cteRecursive c
       ON t.Id = c.Id
       AND c.[Level] < LEN(t.Akey)
)

SELECT
    Id
    ,AKey AS OriginalAKey
    ,AVal AS OriginalAVal
    ,AVal / 2.00 AS NewVal
    ,AVal / CAST(MAX([Level]) OVER (PARTITION BY Id) AS DECIMAL(4,2)) AS NewValAsPortionOfLevel
    ,AVal / CAST(LEN(AKey) AS DECIMAL(4,2)) AS NewValAsPortionOfKeyLength
FROM
    cteRecursive

另一种技术,如果你有一个非常大的数据集,并且不想使用递归,你可以构建一个Tally Table To Join。我很想知道哪个表现更好。我实际上有一个永久的计数表用于我使用的数据仓库ETL的一些记录操作,但你应该使用临时表而不是表变量。无论如何,这是方法。

DECLARE @TallyTable AS TABLE (I INT)

DECLARE @MaxLen INT
SELECT @MaxLen = MAX(LEN(AKey)) FROM @Table

IF (@MaxLen > 0)
BEGIN
    WHILE @MaxLen > 0
    BEGIN
       INSERT INTO @TallyTable (I) VALUES (@MaxLen)
       SET @MaxLen -= 1
    END
END

SELECT
    *
    ,NewValueApportionedByLengthOfKey = CAST(AVal AS DECIMAL) / ISNULL(NULLIF(LEN(AKey),0),1)
FROM
    @Table t
    INNER JOIN @TallyTable tt
    ON LEN(t.AKey) >= tt.I

请注意,所有这些方法都假设AKey永远不会为NULL或0长度,但如果需要,所有这些方法都很容易适应处理。