J'ai une table Je veux interroger la table peut être effectué à l'aide de tables temporaires, tant que je peux l'exécuter sous forme de script ou de procédure stockée. p> EDIT: Voici l'exemple concret: p> entrée: p> sortie: p> donc je veux faire 3 groupes de la moyenne est presque identique dans chaque groupe et près de t_stats code> avec colonne
ID (int) code> et colonne
ratio (décimal (8,4)) code>.
ID code> est unique.
t_stats code> afin de sélectionner 3 groupes avec le même
avg (ratio) code> (le plus proche possible ). P>
n code> valeurs et viser une valeur moyenne spécifique
x code>. (Exemple avec
n = 30 code> et
0.75
0,75
id code> ne peut appartenir à 1 groupe.) p>
x code>: p>
4 Réponses :
Essayez ceci résultat strong>
édité strong>
J'ai essayé avec les données Sampel que vous avez fournies (DDL inconnue pour votre référence) p> et le résultat est p> Le temps pris pour l'exécution est de 27 secondes. Veuillez tester de votre fin (aussi le résultat) et laissez-moi savoir. P> édité fort> p> 75 ddl ddl p> p>
p>
Je ne peux pas tester votre solution avec mes données (110 lignes au lieu de 9 sont trop nombreuses lignes pour la clause avec ...). la déclaration terminée. La récursion maximale 100 a été épuisée avant la fin de la déclaration. Code>
J'aimerais essayer de l'essayer et voir les résultats, mais avec une table de 75 rangées, elle fonctionnait pendant 20 minutes et toujours en cours d'exécution. Peut-être que quelqu'un pourrait optimiser votre solution?
Pourriez-vous s'il vous plaît me donner les données avec la sortie respectée.Je optimisera ma solution et vous revenir à vous.1 Conseils si ... stockez plutôt que vous stockez le résultat dans une table temporelle intermédiaire avec index créé et essayez-le.
@Francis P, modifié la requête
Désolé peut-être que je n'étais pas clair dans mon explication, mais vous avez mal compris les exigences. 1st: J'ai besoin de 3 groupes d'éléments N, pas n groupes de 3 éléments. 2ème: l'élément ne peut pas être en 2 groupes.
Pouvez-vous fournir une petite quantité de données d'entrée avec la sortie que vous attendez. Ce sera utile.
SQL n'est vraiment pas le meilleur outil pour ce type de problème.
Cependant, il est parfois amusant de baster des vis avec le marteau TSQL !! P>
Voici un effort qui obtient les éléments suivants sur vos données d'exemple de 75 lignes: p> en dessous une seconde sur ma machine.
Juste une mise en garde: cette méthode comporte des défauts massifs, mais si vous avez besoin de le faire dans SQL, vous pouvez probablement gaffer bande sur eux un peu, je n'ai tout simplement pas eu le temps de. P> -- **Expects data in table t_stats (id, ratio)**
if OBJECT_ID('tempdb..#data') is not null drop table #data
if OBJECT_ID('tempdb..#pairsets') is not null drop table #pairsets
if OBJECT_ID('tempdb..#pairseed') is not null drop table #pairseed
if OBJECT_ID('tempdb..#match') is not null drop proc #match
go
-- rather horrible routine using dsql to find either:
-- 1) groups of values that sum to exactly @targetsum (only if @targetsum non null)
-- 2) the group containing the least values that includes data id @includeid and where the sum is within +- @targetsumrange
create proc #match(@targetsum DECIMAL(8,4), @includeid int, @targetsumrange DECIMAL(8,4)) as
begin
set nocount on
declare @nearestmatch bit = 0
if @targetsum is null set @nearestmatch = 1
declare @combination table (value int, asstring varchar(10), alias varchar(50))
declare @savedpairseed int = (select pairseed from #pairseed)
declare @stmtTemplate varchar(max) = 'declare @pairseed int = (select pairseed from #pairseed)
declare @DistSum DECIMAL(8,4)
<DeclareVars>
declare candloop cursor for select <SelectList>, <DistanceSum> as Dist_sum from <TableList> where <IdCheck> <SumCheck>
open candloop
fetch next from candloop into <VarsList>, @DistSum
while @@fetch_status = 0
begin
if (select count(*) from #data where id in (<VarsList>)) = <VarsCount>
begin
<DeleteData>
<InsertPairs>
set @pairseed = @pairseed + 1
end
fetch next from candloop into <VarsList>, @DistSum
end
close candloop
deallocate candloop
update #pairseed set pairseed = @pairseed '
declare @combinations int = 1
declare @maxcombinations int = 8
while @combinations <= @maxcombinations
begin
insert @combination select @combinations, cast(@combinations as varchar(10)), char(ascii('a') + @combinations-1)
declare @DeclareVars varchar(max) = ''
declare @SelectList varchar(max) = ''
declare @TableList varchar(max) = ''
declare @IdCheck varchar(max) = ''
declare @DistanceSum varchar(max) = ''
declare @InsertPairs varchar(max) = ''
declare @VarsList varchar(max) = ''
declare @SumCheck varchar(max) = ''
declare @DeleteData varchar(max) = 'delete #data where id in (<VarsList>)'
select @DeclareVars = @DeclareVars + 'declare @id'+asstring+ ' int ' from @combination
select @SelectList = @SelectList + alias +'.id, ' from @combination
set @SelectList = SUBSTRING(@selectlist, 1, LEN(@SelectList)-1)
select @TableList = @TableList + '#data '+alias+', ' from @combination
set @TableList = SUBSTRING(@TableList, 1, LEN(@TableList)-1)
select @IdCheck = @IdCheck + a.alias+'.id < '+b.alias+'.id and '
from @combination a join @combination b on a.value+1 = b.value
if LEN(@IdCheck) > 4
set @IdCheck = SUBSTRING(@IdCheck, 1, LEN(@IdCheck)-4) + ' and '
select @DistanceSum = @DistanceSum + alias+'.targetdistance + ' from @combination
set @DistanceSum = SUBSTRING(@DistanceSum, 1, LEN(@DistanceSum)-2)
select @VarsList = @VarsList + '@id'+asstring+ ', ' from @combination
set @VarsList = SUBSTRING(@VarsList, 1, LEN(@VarsList)-1)
select @InsertPairs = @InsertPairs + 'insert #pairsets select @pairseed, @id'+asstring+ ', @DistSum'+ CHAR(10) from @combination
set @SumCheck = @DistanceSum + ' = '+ cast(@Targetsum as varchar(20))
if @nearestmatch = 1
begin
set @SumCheck = '('
select @SumCheck = @SumCheck + alias+'.id = '+CAST(@includeid as varchar(10))+' or ' from @combination
if LEN(@SumCheck) > 4
set @SumCheck = SUBSTRING(@SumCheck, 1, LEN(@SumCheck)-3)
set @SumCheck = @SumCheck + ')'
set @DeleteData = ''
end
declare @stmt varchar(max)
set @stmt = REPLACE(@stmtTemplate, '<DeclareVars>', @DeclareVars)
set @stmt = REPLACE(@stmt, '<DeleteData>', @DeleteData)
set @stmt = REPLACE(@stmt, '<SelectList>', @SelectList)
set @stmt = REPLACE(@stmt, '<TableList>', @TableList)
set @stmt = REPLACE(@stmt, '<IdCheck>', @IdCheck)
set @stmt = REPLACE(@stmt, '<DistanceSum>', @DistanceSum)
set @stmt = REPLACE(@stmt, '<InsertPairs>', @InsertPairs)
set @stmt = REPLACE(@stmt, '<VarsList>', @VarsList)
set @stmt = REPLACE(@stmt, '<VarsCount>', cast(@combinations as varchar(10)))
set @stmt = REPLACE(@stmt, '<SumCheck>', @SumCheck)
exec (@stmt)
set @combinations = @combinations + 1
end
if @nearestmatch = 1
begin
-- above will have recorded all possible matches within range
-- remove all but the closest and reindex the pair ids
declare @bestmatch int
select top 1 @bestmatch = pairid from #pairsets where pairid >= @savedpairseed and ABS(distsum) < @targetsumrange
delete #pairsets where pairid >= @savedpairseed and pairid <> ISNULL(@bestmatch, -1)
delete #data where id in (select id from #pairsets where pairid = @bestmatch)
update #pairsets set pairid = @savedpairseed where pairid = @bestmatch
update #pairseed set pairseed = @savedpairseed+1
end
end
go
set nocount on
-- set the parameters
declare @xmin DECIMAL(8,4) = 0.75
declare @xmax DECIMAL(8,4) = 0.85
declare @xrange DECIMAL(8,4) = @xmax - @xmin
declare @xtarg DECIMAL(8,4) = (@xmin+@xmax) / 2
declare @ngroups int = 3
declare @targetgroupsize int = 25
declare @maxbalancedpair int
-- copy the ratio data (using 75 row data from updated question)
select *, ratio-@xtarg as targetdistance, abs(ratio - @xtarg) as targetdistanceabsolute into #data from t_stats
create table #pairseed (pairseed int)
create table #pairsets (pairid int, id int, distsum DECIMAL(8,4) )
insert #pairseed select 1
-- due to the 2 decimal points and distribution of the data we can find many n-tuples that sum to zero
exec #match 0, 0, 0
select @maxbalancedpair = pairseed-1 from #pairseed
declare @deviants table (id int)
declare @most_deviant int
while exists(select * from #data where id not in (select id from @deviants))
begin
select top 1 @most_deviant = id from #data where id not in (select id from @deviants) order by targetdistanceabsolute desc
insert @deviants select @most_deviant
exec #match null, @most_deviant, @xrange
end
-- in general there would have to be some backtracking here
-- now its a box-packing problem, but for simplicity just assign them round robin
declare @output_group_pairs table (groupid int, pairid int)
declare @groupidx int = 1
declare @numgroups int = 3
declare @pairid int
select @pairid = pairseed-1 from #pairseed
while @pairid >= 0
begin
insert @output_group_pairs select @groupidx, @pairid
set @pairid = @pairid - 1
set @groupidx = (@groupidx % @numgroups) + 1
end
-- wimpy effort at redistributing the groups evenly
-- todo: many cases will not work, should use a proper algorithm
declare @maxiter int = 100
declare @previouspairs table (pairid int)
declare @previousgroups table (groupid int)
while exists(select groupid from @output_group_pairs a join #pairsets b on a.pairid = b.pairid group by groupid having COUNT(id) < @targetgroupsize)
begin
set @maxiter = @maxiter-1
if @maxiter = 0 break
declare @groupid int = -1
declare @amountout int
select @groupid = groupid, @amountout = @targetgroupsize-COUNT(*)
from @output_group_pairs a join #pairsets b on a.pairid = b.pairid
where groupid not in (select groupid from @previousgroups)
group by groupid having COUNT(*) < @targetgroupsize
if @groupid = -1 break
declare @targetpair int = -1
select @targetpair = a.pairid from @output_group_pairs a
join (select pairid from #pairsets group by pairid having COUNT(*) <= @amountout) b on a.pairid = b.pairid
join (select groupid, count(id) groupcount from @output_group_pairs a join #pairsets b on a.pairid = b.pairid group by groupid) group_counts on a.groupid = group_counts.groupid
where a.pairid not in (select pairid from @previouspairs)
order by abs(@amountout - groupcount) asc
if @targetpair = -1
begin
insert @previousgroups select @groupid
end
else
begin
insert @previouspairs select @targetpair
update @output_group_pairs set groupid = @groupid where pairid = @targetpair
end
end
set @maxiter = 100
delete @previouspairs
delete @previousgroups
while exists(select groupid from @output_group_pairs a join #pairsets b on a.pairid = b.pairid group by groupid having COUNT(id) > @targetgroupsize)
begin
set @maxiter = @maxiter-1
if @maxiter = 0 break
set @groupid = -1
set @amountout = null
select @groupid = groupid, @amountout = COUNT(*)-@targetgroupsize
from @output_group_pairs a join #pairsets b on a.pairid = b.pairid
where groupid not in (select groupid from @previousgroups)
group by groupid having COUNT(*) > @targetgroupsize
if @groupid = -1 break
set @targetpair = -1
select @targetpair = a.pairid from @output_group_pairs a
join (select pairid from #pairsets group by pairid having COUNT(*) <= @amountout) b on a.pairid = b.pairid
join (select groupid, count(id) groupcount from @output_group_pairs a join #pairsets b on a.pairid = b.pairid group by groupid) group_counts on a.groupid = group_counts.groupid
where a.pairid not in (select pairid from @previouspairs)
order by abs(@amountout - groupcount) asc
if @targetpair = -1
begin
insert @previousgroups select @groupid
end
else
begin
insert @previouspairs select @targetpair
delete @output_group_pairs where pairid = @targetpair
end
end
-- output groups and their stats
select GroupId, Id from @output_group_pairs a join #pairsets b on a.pairid = b.pairid order by 1, 2
select a.GroupId, AVG(c.ratio) as [Average] , count(*) as [Count]
from @output_group_pairs a
join #pairsets b on a.pairid = b.pairid
join t_stats c on b.id = c.id
group by a.groupid
go
drop table #data
drop table #pairsets
drop table #pairseed
drop proc #match
Merci beaucoup ... je suis impressionné! Comment puis-je modifier votre script afin de spécifier combien d'articles par groupe je veux? (Faites 3 groupes de 20 valeurs à partir des valeurs 75 dans T_STATS CODE>)
Une autre chose: vous avez défini @ xmin = 0.75 code> et
@ xmax = 0.85 code>, qui donne des groupes ayant
avg (rapport) code> environ 0,79 et < Code> Nombre (*) = 25 code> (parfait). Si je change ces valeurs à
@ xmin = 0,78 code> et
@ xmax = 0.80 code>, je reçois ensuite
avg (rapport) = 0,90 code> et
et
et
et
Count (*) = 9672 code> (c'est un problème).
Oh .. votre exemple était d'obtenir 3 groupes de 30 sur 75 de 75 ans, j'ai donc ignoré l'exigence de «n» d'être variable, mais elle pourrait être ajoutée à la partie de l'emballage de la boîte assez facilement (c'est probablement la pièce avec les principaux problèmes. tel qu'il est). Le bogue qui affecte la caisse de 0,78 à 0,80 est dans les lignes 131 à 137, commencez-les et il s'abstient d'ajouter des ID à plusieurs groupes.
Vous pouvez donc aider à adapter la pièce d'emballage de la boîte à sélectionner «N» éléments?
J'ai mis à jour le code pour créer une simple tentative "gourmande" d'obtenir les groupes à la taille de la cible.
oh et si le nombre d'éléments d'un groupe est une exigence précise, voici une version qui utilise la même correspondance exhaustive pour la partie d'emballage de la boîte, c'est beaucoup plus lente cependant.
-- **Expects data in table t_stats (id, ratio)** if OBJECT_ID('tempdb..#data') is not null drop table #data if OBJECT_ID('tempdb..#data_pairs') is not null drop table #data_pairs if OBJECT_ID('tempdb..#pairsets') is not null drop table #pairsets if OBJECT_ID('tempdb..#pairseed') is not null drop table #pairseed if OBJECT_ID('tempdb..#match') is not null drop proc #match go -- rather horrible routine using dsql to find either: -- 1) groups of values that sum to exactly @targetsum (only if @targetsum non null) -- 2) the group containing the least values that includes data id @includeid and where the sum is within +- @targetsumrange create proc #match(@targetsum DECIMAL(8,4), @maxcombinations int, @includeid int, @targetsumrange DECIMAL(8,4)) as begin set nocount on declare @nearestmatch bit = 0 if @targetsum is null set @nearestmatch = 1 declare @combination table (value int, asstring varchar(10), alias varchar(50)) declare @savedpairseed int = (select pairseed from #pairseed) declare @stmtTemplate varchar(max) = 'declare @pairseed int = (select pairseed from #pairseed) declare @DistSum DECIMAL(8,4) <DeclareVars> declare candloop cursor for select <SelectList>, <DistanceSum> as Dist_sum from <TableList> where <IdCheck> <SumCheck> open candloop fetch next from candloop into <VarsList>, @DistSum while @@fetch_status = 0 begin if (select count(*) from #data where id in (<VarsList>)) = <VarsCount> begin <DeleteData> <InsertPairs> set @pairseed = @pairseed + 1 end fetch next from candloop into <VarsList>, @DistSum end close candloop deallocate candloop update #pairseed set pairseed = @pairseed ' declare @combinations int = 1 while @combinations <= @maxcombinations begin insert @combination select @combinations, cast(@combinations as varchar(10)), CHAR(ASCII('a')+ (@combinations-1)%26) + char(ascii('a') + @combinations-1) declare @DeclareVars varchar(max) = '' declare @SelectList varchar(max) = '' declare @TableList varchar(max) = '' declare @IdCheck varchar(max) = '' declare @DistanceSum varchar(max) = '' declare @InsertPairs varchar(max) = '' declare @VarsList varchar(max) = '' declare @SumCheck varchar(max) = '' declare @DeleteData varchar(max) = 'delete #data where id in (<VarsList>)' select @DeclareVars = @DeclareVars + 'declare @id'+asstring+ ' int ' from @combination select @SelectList = @SelectList + alias +'.id, ' from @combination set @SelectList = SUBSTRING(@selectlist, 1, LEN(@SelectList)-1) select @TableList = @TableList + '#data '+alias+', ' from @combination set @TableList = SUBSTRING(@TableList, 1, LEN(@TableList)-1) select @IdCheck = @IdCheck + a.alias+'.id < '+b.alias+'.id and ' from @combination a join @combination b on a.value+1 = b.value if LEN(@IdCheck) > 4 set @IdCheck = SUBSTRING(@IdCheck, 1, LEN(@IdCheck)-4) + ' and ' select @DistanceSum = @DistanceSum + alias+'.targetdistance + ' from @combination set @DistanceSum = SUBSTRING(@DistanceSum, 1, LEN(@DistanceSum)-2) select @VarsList = @VarsList + '@id'+asstring+ ', ' from @combination set @VarsList = SUBSTRING(@VarsList, 1, LEN(@VarsList)-1) select @InsertPairs = @InsertPairs + 'insert #pairsets select @pairseed, @id'+asstring+ ', @DistSum'+ CHAR(10) from @combination set @SumCheck = @DistanceSum + ' = '+ cast(@Targetsum as varchar(20)) if @nearestmatch = 1 begin set @SumCheck = '(' select @SumCheck = @SumCheck + alias+'.id = '+CAST(@includeid as varchar(10))+' or ' from @combination if LEN(@SumCheck) > 4 set @SumCheck = SUBSTRING(@SumCheck, 1, LEN(@SumCheck)-3) set @SumCheck = @SumCheck + ')' set @DeleteData = '' end declare @stmt varchar(max) set @stmt = REPLACE(@stmtTemplate, '<DeclareVars>', @DeclareVars) set @stmt = REPLACE(@stmt, '<DeleteData>', @DeleteData) set @stmt = REPLACE(@stmt, '<SelectList>', @SelectList) set @stmt = REPLACE(@stmt, '<TableList>', @TableList) set @stmt = REPLACE(@stmt, '<IdCheck>', @IdCheck) set @stmt = REPLACE(@stmt, '<DistanceSum>', @DistanceSum) set @stmt = REPLACE(@stmt, '<InsertPairs>', @InsertPairs) set @stmt = REPLACE(@stmt, '<VarsList>', @VarsList) set @stmt = REPLACE(@stmt, '<VarsCount>', cast(@combinations as varchar(10))) set @stmt = REPLACE(@stmt, '<SumCheck>', @SumCheck) exec (@stmt) set @combinations = @combinations + 1 end if @nearestmatch = 1 begin -- above will have recorded all possible matches within range -- remove all but the closest and reindex the pair ids declare @bestmatch int select top 1 @bestmatch = pairid from #pairsets where pairid >= @savedpairseed and ABS(distsum) < @targetsumrange delete #pairsets where pairid >= @savedpairseed and pairid <> ISNULL(@bestmatch, -1) delete #data where id in (select id from #pairsets where pairid = @bestmatch) update #pairsets set pairid = @savedpairseed where pairid = @bestmatch update #pairseed set pairseed = @savedpairseed+1 end end go set nocount on -- set the parameters declare @xmin DECIMAL(8,4) = 0.75 declare @xmax DECIMAL(8,4) = 0.85 declare @xrange DECIMAL(8,4) = @xmax - @xmin declare @xtarg DECIMAL(8,4) = (@xmin+@xmax) / 2 declare @ngroups int = 3 declare @targetgroupsize int = 5 declare @maxbalancedpair int -- copy the ratio data (using 75 row data from updated question) select *, ratio-@xtarg as targetdistance, abs(ratio - @xtarg) as targetdistanceabsolute into #data from t_stats create table #pairseed (pairseed int) create table #pairsets (pairid int, id int, distsum DECIMAL(8,4) ) insert #pairseed select 1 -- due to the 2 decimal points and distribution of the data we can find many n-tuples that sum to zero exec #match 0, 8, 0, 0 select @maxbalancedpair = pairseed-1 from #pairseed declare @deviants table (id int) declare @most_deviant int while exists(select * from #data where id not in (select id from @deviants)) begin select top 1 @most_deviant = id from #data where id not in (select id from @deviants) order by targetdistanceabsolute desc insert @deviants select @most_deviant exec #match null, 8, @most_deviant, @xrange end select * into #data_pairs from #pairsets delete #data delete #pairsets update #pairseed set pairseed = 1 insert #data select pairid, COUNT(*), COUNT(*), COUNT(*) from #data_pairs group by pairid if (select SUM(ratio) from #data) < @targetgroupsize * @ngroups begin raiserror('Cannot match - not enough data', 16, 1) return end -- find the minimum number of matches that will reach targetgroupsize declare @maxmatches int = -1 declare @matchcount int declare @matchsum int = 0 declare maxmatchcount cursor for select CAST(ratio as int) from #data order by ratio asc open maxmatchcount fetch next from maxmatchcount into @matchcount while @@FETCH_STATUS = 0 and @matchsum <= @targetgroupsize begin set @maxmatches = @maxmatches + 1 set @matchsum = @matchsum + @matchcount fetch next from maxmatchcount into @matchcount end close maxmatchcount deallocate maxmatchcount exec #match @targetgroupsize, @maxmatches, null, null declare @output_group_pairs table (groupid int, pairid int) insert @output_group_pairs select pairid, id from #pairsets where pairid <= @ngroups -- output groups and their stats select GroupId, Id from @output_group_pairs a join #data_pairs b on a.pairid = b.pairid order by 1, 2 select a.GroupId, AVG(c.ratio) as [Average] , count(*) as [Count] from @output_group_pairs a join #data_pairs b on a.pairid = b.pairid join t_stats c on b.id = c.id group by a.groupid go drop table #data drop table #data_pairs drop table #pairsets drop table #pairseed drop proc #match
Je me demande comment votre code fonctionne ... je l'exécute pour 3 groupes de 25 avec 0.75
avg (rapport) = 0,79 code> . Mais si je le change de 3 groupes de 25 éléments, mais
0,7
Dans le pire des cas, il faudra beaucoup de mémoire en raison de la brute forçant les combinaisons. Peut-être devriez-vous exécuter tous les algorithmes suggérés et choisir la meilleure réponse après X secondes ... :)
Voici une version procédurale T-SQL qui est quelque peu similaire à un projet , seul le projet de commande est optimisé chaque cycle en fonction de la nécessité.
La nature "compétitive" de cela semble conduire à des ratios légèrement inférieurs à la perfectionner si tous les articles doivent être choisis, mais le contournement est que vous avez essentiellement un O (n ^ 2) algorithme puisqu'il s'agit essentiellement d'une fonction min dans une boucle (peut-être que c'est optimiste en considérant le groupe par code> clauses). Il est également déterministe et devrait être assez simple à mettre en œuvre dans une autre couche si nécessaire. P>
Merci, bien qu'il y ait beaucoup d'espace pour l'optimisation ici. Veuillez nous mettre à jour si vous faites n'importe quoi pour améliorer cela mieux.
C'est comme le problème du vendeur itinérant. Vous auriez besoin de comparer toutes les combinaisons et de choisir le meilleur interprète. Et pour même des jeux de données de taille modérée, il s'agit d'un grand nombre de combinaisons! Sauf si vous êtes heureux de faire une estimation naïve? [commande par taille, les lignes 1-3 vont aux groupes 1-3, puis les rangées 4-6 vont aux groupes 1-3, etc., etc.] i>
Pour le moment, c'est ce que j'utilise. Mais j'espérais une solution moins naïve.
Vous devez examiner les algorithmes d'optimisation. SQL n'est pas comme le meilleur environnement.
@Dems: j'ai ajouté
c # code> et
linq-to-sql code> sur les balises, si cela peut aider à fournir une solution plus optimisée.