MSSQL 动态 for 循环
MSSQL Dynamic for loop
首先,我绝不是SQL大师,虽然我有点涉猎...
我的问题如下;我有一个包含
的 table 的大型数据库
iteminfo(基本物品描述)(.[itemno])
solditems(物品销售信息)(.[itemno] .[ratecode] .[qty] .[price])
费率(基本上是商品的销售价格)(.[ratecode])
我需要生成一份报告(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 JOIN
ed到外部查询(这听起来有点可怕),或者直接连接到 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
首先,我绝不是SQL大师,虽然我有点涉猎...
我的问题如下;我有一个包含
的 table 的大型数据库iteminfo(基本物品描述)(.[itemno])
solditems(物品销售信息)(.[itemno] .[ratecode] .[qty] .[price])
费率(基本上是商品的销售价格)(.[ratecode])
我需要生成一份报告(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 JOIN
ed到外部查询(这听起来有点可怕),或者直接连接到 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