我有一个数据库,其中有几个表跟踪电话呼叫/短信/数据和津贴,如果可以在不诉诸游标的情况下分配调用,我正试图解决,但我无法弄清楚一种构造SQL的方法。我的尝试中没有任何有用的SQL,因为我似乎无法理解如何处理它!问题是,对我而言,这似乎是一个固有的迭代过程,如果有一种合理的方法将其转化为基于集合的方法,我就无法解决。我考虑过使用窗口函数,但是当我们跟踪2个表中的累计总数并且总数是相互依赖时,我看不出怎么做。我正在尝试最小化运行此过程的时间以及对其他查询的影响,因为我们要经常重新运行它并且表格变得非常大。
这是一个简化的结构......
记录所有电话
每份合约有哪些不同的免税额
如何使用配额 - 这是一个列出允许组合的联结表
我故意没有记录所有细节以保持简单。我希望一切都很明显,但如果没有,请告诉我。
如果我正在迭代地处理这个问题,那么我的psueodocode就像: -
For each Call ordered by DateTime
Declare a as Allowance
Do
Set a = First Allowance Where Allowance.ContractID=Call.ContractID And Allowance.QuantityUsed<Allowance.Quantity Order by Priority Descending
If a != NULL
Declare n as Integer
Set n = a.Quantity-a.QuantityUsed
If Call.Quantity-Call.QuantityFromAllowances<n
Set n = Call.Quantity-Call.QuantityFromAllowances
End if
Set Call.QuantityFromAllowances = Call.QuantityFromAllowances + n
If Call.FirstAllowanceUsedID == NULL Then
Set Call.FirstAllowanceUsedID = a.ID
End if
Set a.QuantityUsed = a.QuantityUsed + n
End if
Loop while a != NULL AND Call.QuantityFromAllowances<Call.Quantity
Next Call
随意告诉我,我正在接近问题,或者这实际上是游标的一个很好的候选者。我只是在寻找最好的解决方案。
举个例子: -
Call ID ContractID ChargeGroupID DateTime Quantity QuantityFromAllowances FirstAllowanceUsedID 1 1 1 2016-11-01 100 0 NULL 2 1 2 2016-11-02 500 0 NULL 3 1 1 2016-11-03 500 0 NULL 4 1 3 2016-11-04 100 0 NULL 5 1 1 2016-11-05 100 0 NULL 6 2 1 2016-11-01 100 0 NULL Allowance ID ContractID Priority Quantity QuantityUsed 1 1 1 500 0 2 1 0 500 0 3 2 1 500 0 4 2 0 500 0 AllowanceChargeGroup ID AllowanceID ChargeGroupID 1 1 1 2 1 2 3 2 1 4 2 2 5 3 1
在我的例子中,我会按如下方式计算: -
之后,表格应如下所示(只有更改为Call.QuantityFromAllowances,Call.FirstAllowanceUsedID,Allowance.QuantityUsed ...
Call ID ContractID ChargeGroupID DateTime Quantity QuantityFromAllowances FirstAllowanceUsedID 1 1 1 2016-11-01 100 100 1 2 1 2 2016-11-02 500 500 1 3 1 1 2016-11-03 500 400 2 4 1 3 2016-11-04 100 0 NULL 5 1 1 2016-11-05 100 0 NULL 6 2 1 2016-11-01 100 100 3 Allowance ID ContractID Priority Quantity QuantityUsed 1 1 1 500 500 2 1 0 500 500 3 2 1 500 100 4 2 0 500 0 AllowanceChargeGroup ID AllowanceID ChargeGroupID 1 1 1 2 1 2 3 2 1 4 2 2 5 3 1
答案 0 :(得分:2)
您想要同时更新呼叫表和限额表,每次更新都取决于之前的更新 只有一个sql语句是不可能的,所以你需要循环 您不需要游标,您可以在过程中使用顺序设置操作来解决它。
首先,一些声明并准备一些数据:
declare @todo as table (callID int primary key, qt int, done bit, unique (done, qt, callid))
declare @id1 int, @id2 int, @q1 int, @q2 int
-- prepare job list
insert into @todo
select id, Quantity-QuantityFromAllowances, 0
from [call]
where Quantity>QuantityFromAllowances
然后主循环调用:
set @id1=0
set @q1= null
while not(@id1 is null) begin
set @id1=null
select top 1 @id1 = callID, @q1=qt from @todo where done=0 and qt>0 order by callID
if not(@id1 is null) begin
set @id2 = null
select top 1 @id2 = a.id, @q2 = a.Quantity - a.QuantityUsed
from [call] c
inner join AllowanceChargeGroup g on g.ChargeGroupID = c.ChargeGroupID
inner join allowance a on (a.ID = g.AllowanceID) and (a.Quantity>a.QuantityUsed)
where c.ID=@id1
order by c.ID,[Priority] desc, (a.Quantity-a.QuantityUsed) desc
if not(@id2 is null) begin
if @q2 < @q1 set @q1 = @q2
update a set QuantityUsed = QuantityUsed + @q1
from allowance a
where a.ID=@id2
update c set QuantityFromAllowances = QuantityFromAllowances + @q1, FirstAllowanceUsedID = isnull(FirstAllowanceUsedID, @id2)
from [call] c
where c.ID=@id1
update t set qt = qt-@q1, done = IIF(qt-@q1=0,1,0)
from @todo t
where t.callID=@id1
end else begin
-- unable to complete
update t set done = 1
from @todo t
where t.callID=@id1
end
end
end
最后输出:
select * from [call]
select * from allowance
与要求相同
答案 1 :(得分:1)
正如我在评论中所说,您可以采用不同的方法实现目标,无循环,但您需要不相交的AllowanceChargeGroup ,这意味着一个津贴可以是只在一个组中。
通过津贴和收费组之间的这种独特关系,我们可以将请求(呼叫)与津贴联系起来。
我们的想法是列出并加权(订购)调用所需的每个单元,并列出和重量(订购)每个单元可用的配额,最后将它们并排连接。
例如,假设有此Calls,Allowances和ChargeGroups:
ID ChargeGroupID Quantity QuantityFromAllowances FirstAllowanceUsedID
1 1 3 0 NULL
2 1 3 0 NULL
3 2 5 0 NULL
ID Priority Quantity QuantityUsed
1 1 4 0
2 0 1 0
3 0 6 0
ID AllowanceID ChargeGroupID
1 1 1
2 2 1
4 3 2
现在根据行数量分解n行中的每一行(因此我们将有3行用于CallID 1和CallID 2以及5行用于CallID 3)。 在爆炸时,标记行以标识它们(添加两个不同的列,其中行号用于组和呼叫/允许)
ChargeGroupID GroupRowN CallID CallRowN
1 1 1 1
1 2 1 2
1 3 1 3
1 4 2 1
1 5 2 2
1 6 2 3
2 1 3 1
2 2 3 2
2 3 3 3
2 4 3 4
2 5 3 5
ChargeGroupID GroupRowN AllowanceID AllowanceRowN
1 1 1 1
1 2 1 2
1 3 1 3
1 4 1 4
1 5 2 1
2 1 3 1
2 2 3 2
2 3 3 3
2 4 3 4
2 5 3 5
2 6 3 6
现在只需将这些爆炸集合加入组行号(GroupRowN)即可 在这里,您可以看到电话津贴的分配情况。
(我添加了水平破折号以更好地显示CallIDs的分布):
ChargeGroupID GroupRowN CallID CallRowN ChargeGroupID GroupRowN AllowanceID AllowanceRowN
1 1 1 1 1 1 1 1
1 2 1 2 1 2 1 2
1 3 1 3 1 3 1 3
-----------------------------------------------------------------------------------------------------
1 4 2 1 1 4 1 4
1 5 2 2 1 5 2 1
1 6 2 3 NULL NULL NULL NULL
-----------------------------------------------------------------------------------------------------
2 1 3 1 2 1 3 1
2 2 3 2 2 2 3 2
2 3 3 3 2 3 3 3
2 4 3 4 2 4 3 4
2 5 3 5 2 5 3 5
NULL NULL NULL NULL 2 6 3 6
现在让我们汇总这个结果以得到一些总数:
CallID Max(CallN) AllowanceID Max(AllowanceN)
1 3 1 3
2 1 1 4
2 2 2 1
3 5 3 5
最后,从上一次输出我们可以得到更新调用的信息,一个限额表:
CallID QtUsed FirstUsed
1 3 1
2 2 1
3 5 3
AllowanceID QtUsed
1 4
2 1
3 5
确定,
这就是理论,现在让我们看一些代码(使用上面的数据)。
注意FN_NUMBERS(n),它是一个函数,只返回一个数字从1到n的列,你需要它在你的数据库中,有很多方法可以做到,只需google for&#34; tally表&#34;或look here 我使用以下内容:
CREATE FUNCTION FN_NUMBERS(
@MAX INT
)
RETURNS @N TABLE (N INT NOT NULL PRIMARY KEY)
BEGIN
WITH
Pass0 as (select '1' as C union all select '1'), --2 rows
Pass1 as (select '1' as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select '1' as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select '1' as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select TOP (@MAX) '1' as C from Pass3 as A, Pass3 as B) --65536 rows
,Tally as (select TOP (@MAX) '1' as C from Pass4 as A, Pass2 as B, Pass1 as C) --4194304 rows
--,Tally as (select TOP (@MAX) '1' as C from Pass4 as A, Pass3 as B) --16777216 rows
--,Tally as (select TOP (@MAX) '1' as C from Pass4 as A, Pass4 as B) --4294836225 rows
INSERT INTO @N
SELECT TOP (@MAX) ROW_NUMBER() OVER(ORDER BY C) AS N
FROM Tally
RETURN
END
回到sql ..
declare @res as table (id int identity primary key, CallID int, CallN int , AllowanceID int, AllowanceN int, unique (callId, id), unique (allowanceID, id))
;with
cx as (
select c.ID, c.Quantity, c.ChargeGroupID, n, ROW_NUMBER() over (partition by ChargeGroupID order by id,n) rn
from [call] c
join FN_NUMBERS(1000) n on n.N<=(c.Quantity-c.QuantityFromAllowances)
),
ax as (
select a.ID, a.Quantity, ChargeGroupID, N, ROW_NUMBER() over (partition by g.ChargeGroupID order by [priority] desc, a.id,n) rn
from Allowance a
join AllowanceChargeGroup g on g.AllowanceID = a.ID
join FN_NUMBERS(1000) n on n.N <= (a.Quantity-a.QuantityUsed)
),
j as (
select
cx.ID CallID, cx.Quantity CallQt, cx.N CallN, cx.rn CallRn, ax.ID AllowanceID, ax.Quantity AllowanceQt, ax.N AllowanceN, ax.rn AllowanceRn
from cx
join ax on cx.rn = ax.rn and (cx.ChargeGroupID = ax.ChargeGroupID)
)
insert into @res
select CallID, MAX(CallN) CallN, AllowanceID, MAX(AllowanceN) AllowanceN
from j
group by CallID,AllowanceID
这将在@res表中填入用于更新的最终聚合数据。
所以我们只需要执行实际的更新:
-- updates Allowance table
;with
ar as (
select AllowanceID, MAX(AllowanceN) QtUsed
from @res
group by AllowanceID
)
update a set a.QuantityUsed = a.QuantityUsed + ar.QtUsed
select ar.*
from Allowance a
join ar on a.ID = ar.AllowanceID
-- updates Call table
;with
fu as (
select CallID id, min(calln) FirstUsed
from @res
group by CallID
),
cr as (
select CallID, MAX(CallN) QtUsed, MIN(AllowanceID) FirstUsed
from @res r1
left join fu r2 on r1.CallID=r2.id and r1.CallN = r2.FirstUsed
group by CallID
)
update c set QuantityFromAllowances = c.QuantityFromAllowances + QtUsed, FirstAllowanceUsedID = ISNULL(FirstAllowanceUsedID, FirstUsed)
select cr.*
from [call] c
join cr on c.ID = cr.CallID
全部,临时表中的一个插入和两个更新,没有循环,没有游标..
答案 2 :(得分:0)
如果没有表格设计,样本数据和预期输出,总是很难回答问题。如果此答案无法帮助您查看此guide from the help pages和此answer from Meta,请参阅有关如何编辑问题的提示。
我怀疑这是一个谷物问题。因为每个表中的粒度级别不同,所以很难合并。
我建议您遵循以下基本模式:
类似的东西:
-- Update allowance with usage.
WITH Used AS
(
-- Retrieve usage.
SELECT
ID,
ContractID,
AllowanceID,
SUM(Quantity) AS Quantity
FROM
Call
GROUP BY
ID,
ContractID,
AllowanceID
),
UPDATE
a
SET
a.QuantityUsed = a.QuantityUsed + u.Quantity
FROM
Allowance AS a
INNER JOIN Used AS u ON u.ID = a.ID
AND u.ContractID = a.ContractID
AND u.AllowanceID = a.AllowanceID
;
当然,你需要充实这一点。我看不到收费的空间,所以我没有包含AllowanceChargeGroup
表。
看起来您的津贴表正在拉动双重职责。它包含合同条款(可能不经常更改)和使用的数量(这将更频繁地改变 - 可能是每月?)。我建议你拆分这些功能。
我理解它是一个简化的模型,如果简化的答案是不够的,那么道歉。