Sql server s.ID=sg.ServerID上的s 按g分组编号 删除表#MServerGroup 结束 其他的 打印“无需细化” 下拉表#组 下拉表#服务器组 drop table#服务器
我建议从Sql server s.ID=sg.ServerID上的s 按g分组编号 删除表#MServerGroup 结束 其他的 打印“无需细化” 下拉表#组 下拉表#服务器组 drop table#服务器,sql-server,sql-server-2012,Sql Server,Sql Server 2012,我建议从@nTries=0和合理的@tolerance(例如0.1、0.05)开始。这里有一个解决方案,它可以产生与@I-one的代码相同的结果,但可能更容易理解(至少对我来说)。我使用“chunk”而不是“group”来避免关键字冲突 前提如下。要创建n个大小均匀的块,请执行以下操作: 按降序排列所有记录 按行号将前n个记录分配给它们的块 循环遍历剩余部分,始终指定给最小的块 我已经将代码上传到SQLFiddle,但它似乎不喜欢table变量 你应该添加你想要的输出。可能你正在寻找这样的东西:
@nTries=0
和合理的@tolerance
(例如0.1、0.05)开始。这里有一个解决方案,它可以产生与@I-one的代码相同的结果,但可能更容易理解(至少对我来说)。我使用“chunk”而不是“group”来避免关键字冲突
前提如下。要创建n个大小均匀的块,请执行以下操作:
你应该添加你想要的输出。可能你正在寻找这样的东西:谢谢你的链接,但这看起来更通用,而不是基于聚合。请显示一些示例输出。如果您的查询达到了预期效果,那么理想的结果集会是什么样子?我很难准确地可视化您想要的内容-只有您可以向我们展示。如果您澄清“大小”是指磁盘上的大小还是组的大小(例如,2个组有3台服务器,1个组有4台服务器),这也会有所帮助。我使用此技术从104338行中1112 GB的总大小生成14个大小相等的块。结果是73-78 GB块大小,除了块2为135 GB。我在OVER条款中订了另一种方式。结果是11个大小为76-77GB的区块,其余3个区块为;117、97和81 GB。(块==组)
declare @Servers table (ServerName sysname, TotalSizeGB decimal (12,2))
insert into @Servers values
('Server1',123.45),
('Server2',234.56),
('Server3',345.67),
('Server4',456.78),
('Server5',567.89),
('Server6',678.90),
('Server7',789.01),
('Server8',890.12),
('Server9',901.23),
('Server10',1023.35)
select GroupNumber, sum(TotalSizeGB) as TotalSizeGB
from (
select ServerName, sum(TotalSizeGB) as TotalSizeGB, ntile(3) over (order by newid()) as GroupNumber
from (
select ServerName, TotalSizeGB from @Servers
) x
group by ServerName
) y
group by GroupNumber
ServerName TotalSizeGB GroupNumber
Server10 1023.35 1
Server1 123.45 1
Server5 567.89 1
Server3 345.67 1
Server4 456.78 2
Server7 789.01 2
Server6 678.90 2
Server2 234.56 3
Server9 901.23 3
Server8 890.12 3
GroupNumber TotalSizeGB
1 2060.36
2 1924.69
3 2025.91
declare @TotalSizeGB decimal;
select @TotalSizeGB = sum(TotalSizeGB) from @Servers;
declare @Count int;
select @Count = count(TotalSizeGB) from @Servers;
declare @GroupSize int;
select @GroupSize = 3;
declare @NoofGroups int;
select @NoofGroups = 3;
declare @UnitSizeGB decimal
Set @UnitSizeGB =(@TotalSizeGB/@Count)*@NoofGroups;
Declare @Remainder decimal;
Set @Remainder = @TotalSizeGB-(@UnitSizeGB*@NoofGroups)
Select GroupNumber,
CASE
WHEN gcount = @GroupSize THEN @UnitSizeGB
WHEN gcount > @GroupSize THEN @UnitSizeGB+@Remainder
END
From (
Select
GroupNumber,count(ServerName) as gcount, @UnitSizeGB as UnitSizeGB from(
Select ServerName,ntile(@GroupSize) over (order by newid()) as GroupNumber
from (
select ServerName, TotalSizeGB from @Servers ) x
group by ServerName ) as d
group by GroupNumber ) as ff
GroupNumber Size
1 2405
2 1803
3 1803
SELECT *
FROM(
SELECT y.TotalSizeGB,
CASE
WHEN y.AnotherGrp%2=0 AND y.PseudoGrpNumber=0 THEN 2
WHEN y.AnotherGrp%2=0 AND y.PseudoGrpNumber=1 THEN 1
WHEN y.AnotherGrp%2=0 AND y.PseudoGrpNumber=2 THEN 0
ELSE y.PseudoGrpNumber
END GrpNumber
FROM(
SELECT
x.ServerName,
x.TotalSizeGB,
(2+ROW_NUMBER() OVER(ORDER BY x.TotalSizeGB DESC))%3 PseudoGrpNumber,
(2+ROW_NUMBER() OVER(ORDER BY x.TotalSizeGB DESC))/3 AnotherGrp,
ROW_NUMBER() OVER(ORDER BY x.TotalSizeGB DESC) RowNum
FROM @Servers x
)y
)z
PIVOT( SUM(z.TotalSizeGB) FOR z.GrpNumber IN([0],[1],[2]) ) pvt;
0 1 2
------- ------- -------
2048.02 1925.80 2037.14
ServerName TotalSizeGB PseudoGrpNumber AnotherGrp GrpNumber RowNum
---------- ------------ --------------- ---------- --------- ------
Server10 1023.35 0 1 0 1
Server9 901.23 1 1 1 2
Server8 890.12 2 1 2 3
Server7 789.01 0 2 2 4
Server6 678.90 1 2 1 5
Server5 567.89 2 2 0 6
Server4 456.78 0 3 0 7
Server3 345.67 1 3 1 8
Server2 234.56 2 3 2 9
Server1 123.45 0 4 2 10
ServerName GroupNo TotalSizeGB
---------- ----------- -----------
Server1 3 123.45
Server2 3 234.56
Server3 2 345.67
Server4 1 456.78
Server5 2 567.89
Server6 1 678.90
Server7 3 789.01
Server8 3 890.12
Server9 1 901.23
Server10 2 1023.35
GroupNo GroupSizeGb
----------- -----------
1 2036.91
2 1936.91
3 2037.14
ServerName GroupNo TotalSizeGB
---------- ----------- -----------
Server1 3 123.45
Server2 3 234.56
Server3 2 345.67
Server4 1 456.78
Server5 3 567.89
Server6 1 678.90
Server7 2 789.01
Server8 2 890.12
Server9 1 901.23
Server10 3 1023.35
GroupNo GroupSizeGb
----------- -----------
1 2036.91
2 2024.80
3 1949.25
set nocount on
-- Parameters
declare
@nGroups int, -- Number of groups to split servers to
@tolerance float, -- let say 0.0 ... 0.1 (0.1 mean that (+/-)10% deviation allowed from target group size)
@nTries int, -- refinement tries 100, 1000, 10000 or as much as you can wait if you are not satisfied with initial solution
@mFactor float, -- refinement param 0.0 ... 1.0
@tolerance2 float -- let say 0.1 ... 0.3
set @nGroups = 3
set @tolerance = 0
set @nTries = 1000
set @mFactor = 0.3
set @tolerance2 = 0.3
-- Initial Data
create table #Servers (ID int identity, ServerName sysname, TotalSizeGB decimal (12,2), primary key clustered(ID))
insert into #Servers (ServerName, TotalSizeGB) values
('Server1',123.45),
('Server2',234.56),
('Server3',345.67),
('Server4',456.78),
('Server5',567.89),
('Server6',678.90),
('Server7',789.01),
('Server8',890.12),
('Server9',901.23),
('Server10',1023.35)
create table #Groups (GroupNo int not NULL, primary key clustered (GroupNo))
insert into #Groups (GroupNo)
select N from (select row_number() over (order by @@spid) from sys.all_columns) S(N) where N <= @nGroups
create table #ServerGroups (ServerID int not NULL, GroupNo int not NULL, primary key clustered(ServerID))
create index #IX_GroupServers_GroupNo on #ServerGroups (GroupNo)
declare
@srvCnt int,
@grSize decimal (12,2),
@grNo int,
@grSz decimal (12,2),
@srvID int
select @srvCnt = count(1), @grSize = sum(TotalSizeGB) / @nGroups from #Servers
select @grSize as [Target approx. group size]
-- Find initial solution
while (select count(1) from #ServerGroups) < @srvCnt
begin
select top 1 @grNo = g.GroupNo
from #Groups g
left join #ServerGroups sg on sg.GroupNo = g.GroupNo
left join #Servers s on s.ID = sg.ServerID
group by g.GroupNo
order by sum(s.TotalSizeGB)
select @grSz = IsNull(sum(s.TotalSizeGB), 0)
from #Groups g
left join #ServerGroups sg on sg.GroupNo = g.GroupNo
left join #Servers s on s.ID = sg.ServerID
where g.GroupNo = @grNo
select top 1 @srvID = ID
from #Servers s
where not exists (select 1 from #ServerGroups where ServerID = s.ID)
order by abs(@grSize - @grSz - s.TotalSizeGB)
insert into #ServerGroups (ServerID, GroupNo) values (@srvID, @grNo)
end
select g.GroupNo, SUM(s.TotalSizeGB) GroupSizeGb
from #Groups g
join #ServerGroups sg on sg.GroupNo = g.GroupNo
join #Servers s on s.ID = sg.ServerID
group by g.GroupNo
-- Refinement
declare @fTarg float
select @fTarg = sum(abs(case when abs(re) > @tolerance then re else 0 end))
from (
select g.GroupNo, SUM(s.TotalSizeGB) GroupSizeGb
from #Groups g
join #ServerGroups sg on sg.GroupNo = g.GroupNo
join #Servers s on s.ID = sg.ServerID
group by g.GroupNo
) t
cross apply (select (GroupSizeGb - @grSize)/@grSize re) p
print @fTarg
if @fTarg > 0
begin
create table #MServerGroups (ServerID int not NULL, GroupNo int not NULL, primary key clustered (ServerID))
insert into #MServerGroups
select ServerID, GroupNo from #ServerGroups
while @nTries > 0
begin
set @nTries = @nTries - 1
begin transaction
;with MS as (
select top (100*@mFactor) percent ServerID, GroupNo
from #MServerGroups
order by checksum(newid())
)
update msg
set
msg.GroupNo = case when msg.ServerID = tt.ServerID1 then tt.NewNo1 else tt.NewNo2 end
from
#MServerGroups msg
join (
select ServerID1, NewNo1, ServerID2, NewNo2
from (
select MS.ServerID as ServerID1, SS.GroupNo as NewNo1, SS.ServerID as ServerID2, MS.GroupNo as NewNo2, row_number() over (partition by SS.ServerID order by @@spid) as rn
from MS
join #Servers s on s.ID = MS.ServerID
cross apply (
select top 1 *
from
#Servers s2
join #MServerGroups ms2 on ms2.ServerID = s2.ID
where
s2.ID != MS.ServerID and ms2.GroupNo != MS.GroupNo and abs(s2.TotalSizeGB - s.TotalSizeGB)/s.TotalSizeGB < @tolerance2
order by checksum(newid())
) SS
) t
where rn = 1
)tt on msg.ServerID in (tt.ServerID1, tt.ServerID2)
if @@rowcount = 0
begin
rollback transaction
continue;
end
declare @fT float
select @fT = sum(abs(case when abs(re) > @tolerance then re else 0 end))
from (
select g.GroupNo, SUM(s.TotalSizeGB) GroupSizeGb
from #Groups g
join #MServerGroups sg on sg.GroupNo = g.GroupNo
join #Servers s on s.ID = sg.ServerID
group by g.GroupNo
) t
cross apply (select (GroupSizeGb - @grSize)/@grSize re) p
if @fT < @fTarg
begin
set @fTarg = @ft
print @fTarg -- the less this number, the better solution is
commit transaction
end
else
rollback transaction
end
update s
set s.GroupNo = m.GroupNo
from #MServerGroups m
join #ServerGroups s on s.ServerID = m.ServerID
select g.GroupNo, SUM(s.TotalSizeGB) GroupSizeGb
from #Groups g
join #ServerGroups sg on sg.GroupNo = g.GroupNo
join #Servers s on s.ID = sg.ServerID
group by g.GroupNo
drop table #MServerGroups
end
else
print 'No refinement needed'
drop table #Groups
drop table #ServerGroups
drop table #Servers
-- Source data:
DECLARE @Servers TABLE (ServerName SYSNAME, TotalSizeGB DECIMAL (12,2))
INSERT INTO @Servers VALUES
('Server1',123.45),
('Server2',234.56),
('Server3',345.67),
('Server4',456.78),
('Server5',567.89),
('Server6',678.90),
('Server7',789.01),
('Server8',890.12),
('Server9',901.23),
('Server10',1023.35)
-- Solution start
DECLARE @ServersChunked TABLE (
ServerName SYSNAME,
TotalSizeGB DECIMAL (12,2),
RowNum INT,
ChunkNo INT
);
DECLARE
@ChunkCount INT = 3,
@MinRowNum INT,
@SmallestChunk INT;
-- Copy table into variable (skip this if the original table can be amended to include the RowNum and ChunkNo fields)
INSERT INTO @ServersChunked
SELECT
*,
RowNum = ROW_NUMBER() OVER (ORDER BY TotalSizeGB DESC),
ChunkNo = NULL
FROM @Servers
-- Assign the initial chunks to largest tables
UPDATE @ServersChunked
SET ChunkNo = RowNum
WHERE RowNum <= @ChunkCount
-- Assign chunks to remaining tables
WHILE EXISTS (SELECT 1 FROM @ServersChunked WHERE ChunkNo IS NULL) BEGIN
-- Find the next table (by descending row count)
SELECT @MinRowNum = MIN(RowNum) FROM @ServersChunked WHERE ChunkNo IS NULL
-- Find the smallest chunk
SELECT TOP 1 @SmallestChunk = ChunkNo
FROM @ServersChunked
WHERE ChunkNo IS NOT NULL
GROUP BY ChunkNo
ORDER BY Sum(TotalSizeGB) ASC
-- Assign the table to the chunk
UPDATE @ServersChunked
SET ChunkNo = @SmallestChunk
WHERE RowNum = @MinRowNum
END
ChunkNo SumTotalSizeGB
1 1936.91
2 2036.91
3 2037.14