MSSQL 动态 for 循环

MSSQL Dynamic for loop

首先,我绝不是SQL大师,虽然我有点涉猎...

我的问题如下;我有一个包含

的 table 的大型数据库

我需要生成一份报告(crystal 份报告),显示每件商品在每个费率下的销售次数以及总价值。我 运行 遇到的问题是 [rates] table 对于不同的数据库可能有不同的大小,可以添加更多的速率或删除未使用的速率。

我构建了以下 SQL 查询来获取此信息,但如果数据库中有 10 个不同的费率,我需要复制粘贴以下代码 10 次,使查询变得非常混乱,当然,它不考虑第 11 个速率,如果速率被移除(或者更糟,如果速率 nr 5 被移除并且 6 变为 5、7 变为 6,等等)。

注:s.itemno指的是主查询,是被查询的具体商品的itemnumber。

(select count(solditems.ratecode) 
 from solditems 
 where solditems.itemno = s.itemno 
   and solditems.ratecode = (
       select [ratecode] 
       from (
          select row_number() over (order by [ratecode]) as  rownumber, * 
          from dbo.rates
       ) as mytable 
       where rownumber = '**1**'
  )
) as ratqty**1**,


(select sum(solditems.qty * solditems.price) from solditems 
where solditems.itemno = s.itemno and solditems.ratecode = (select 
    [ratecode] from (select row_number() over (order by [ratecode]) as 
    rownumber, * from dbo.rates) as mytable where rownumber = '**1**')) 
as rateval**1**,
...

如果我使用像 C 这样的编程语言,我只会写一个 for 循环,例如(伪代码):int maxi = count(rates.ratecode) for i = 0 and i!=maxi {do stuff for rownumber i} i++.

Q1:有没有办法像SQL那样构建for循环(必须兼容crystal报表,越简单越好)

问题 2:如果某个比率被移除并且随后的 table 上升了一个点(6 变为 5,7 变为 6,等等),我将如何避免出现问题。

Bonus Q: 上面的代码不是很优雅,有没有更简单的方法?

请求的示例输出:

商品编号 1 在费率代码 1 下以每件 1000 欧元的价格售出 5 次,在费率代码 2 下以每件 100 欧元的价格售出 1 次 itemno 2 已在价格代码 1 下以 500 欧元的价格售出 6 次, 等等

sampleoutput

为了避免循环和复制粘贴,您需要一个 dynamic pivot 用于两个聚合值,例如:

DECLARE
  @cmd NVARCHAR(MAX) = N'',
  @qty_cols NVARCHAR(MAX) = N'',
  @val_cols NVARCHAR(MAX) = N'',
  @n int = 0

    -- this can be done in any other manner like via spt_values or FOR XML and so on
    SELECT TOP 100 PERCENT
      @n += 1,
      @qty_cols += ', [rateqty-' + CAST(@n AS VARCHAR(10)) + ']',
      @val_cols += ', [rateval-' + CAST(@n AS VARCHAR(10)) + ']',
      @cmd += ', MAX([rateqty-' + CAST(@n AS VARCHAR(10)) + ']) as [rateqty-' + CAST(@n AS VARCHAR(10)) + ']'
       + ', MAX([rateval-' + CAST(@n AS VARCHAR(10)) + ']) as [rateval-' + CAST(@n AS VARCHAR(10)) + ']'
    FROM #rates r
    ORDER BY r.ratecode

set @cmd = 'SELECT ' + STUFF(@cmd, 1, 2, '') + '
  from 
  (
    select 
      ''rateqty-'' + CAST(rn AS VARCHAR(10)) ratecode_qty, 
      ''rateval-'' + CAST(rn AS VARCHAR(10)) ratecode_val, 
      rateqty, rateval
    from (
      select ratecode, rateqty, rateval, ROW_NUMBER() OVER(ORDER BY r.ratecode) rn
      from #rates r
    ) r
  ) r
  pivot (max(r.rateqty) for ratecode_qty in (' + STUFF(@qty_cols, 1, 2, '') + ' )) p1
  pivot (max(p1.rateval) for ratecode_val in (' + STUFF(@val_cols, 1, 2, '') + ' )) p2
  order by 1'

exec(@cmd)
;

对于这些示例数据:

insert into rates(ratecode)
values (1), (2), (3)

insert into solditems(ratecode, qty, price)
values
 (1, 5, 22),
 (1, 2, 22),
 (3, 1, 33)

输出将是:

| rateqty-1 | rateval-1 | rateqty-2 | rateval-2 | rateqty-3 | rateval-3 |
|-----------|-----------|-----------|-----------|-----------|-----------|
|         2 |       154 |         0 |         0 |         1 |        33 |

完整来源: http://sqlfiddle.com/#!18/6f390/42

关于在包含更多源表的报告中简化子查询的问题,我的猜测(因为您只显示了部分代码)是 ratecodes 必须 CROSS JOINed到外部查询(这听起来有点可怕),或者直接连接到 solditems 并聚合( 然后 旋转)像这样:

SELECT 
  ...
FROM ... <outer query>
CROSS APPLY
(
  select
     r.ratecode,
     count(1) rateqty,  -- i don't quite understand what is this supposed to be; number of transactions?
     sum(si.qty * si.price) rateval
  from solditems si
  INNER JOIN dbo.rates r
   ON si.ratecode = r.ratecode
  where si.itemno = s.itemno
  group by r.ratecode
) s

upd:在发布这个子查询后,我意识到 dbo.rates 除了 ROWNUMBER 之外没有其他地方被使用。因此,如果您进行旋转,则无需在此处加入 rates

SELECT 
  ...
FROM ... <outer query>
CROSS APPLY
(
  select
     si.ratecode,
     count(1) rateqty,
     sum(si.qty * si.price) rateval
  from solditems si
  where si.itemno = s.itemno
  group by si.ratecode
) s