En a obtenu une tâche de programmation dans le domaine de T-SQL
.
Tâche:
Question: Quelle est la façon la plus efficace de résoudre ce problème? Si le bouclage est correct, y a-t-il place à amélioration?
J'ai utilisé une boucle et # tables temporaires, voici ma solution:
set rowcount 0
-- THE SOURCE TABLE "LINE" HAS THE SAME SCHEMA AS #RESULT AND #TEMP
use Northwind
go
declare @sum int
declare @curr int
set @sum = 0
declare @id int
IF OBJECT_ID('tempdb..#temp','u') IS NOT NULL
DROP TABLE #temp
IF OBJECT_ID('tempdb..#result','u') IS NOT NULL
DROP TABLE #result
create table #result(
id int not null,
[name] varchar(255) not null,
weight int not null,
turn int not null
)
create table #temp(
id int not null,
[name] varchar(255) not null,
weight int not null,
turn int not null
)
INSERT into #temp SELECT * FROM line order by turn
WHILE EXISTS (SELECT 1 FROM #temp)
BEGIN
-- Get the top record
SELECT TOP 1 @curr = r.weight FROM #temp r order by turn
SELECT TOP 1 @id = r.id FROM #temp r order by turn
--print @curr
print @sum
IF(@sum + @curr <= 1000)
BEGIN
print 'entering........ again'
--print @curr
set @sum = @sum + @curr
--print @sum
INSERT INTO #result SELECT * FROM #temp where [id] = @id --id, [name], turn
DELETE FROM #temp WHERE id = @id
END
ELSE
BEGIN
print 'breaaaking.-----'
BREAK
END
END
SELECT TOP 1 [name] FROM #result r order by r.turn desc
Voici le script de création pour la table que j'ai utilisée Northwind pour les tests:
USE [Northwind]
GO
/****** Object: Table [dbo].[line] Script Date: 28.05.2018 21:56:18 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[line](
[id] [int] NOT NULL,
[name] [varchar](255) NOT NULL,
[weight] [int] NOT NULL,
[turn] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
UNIQUE NONCLUSTERED
(
[turn] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[line] WITH CHECK ADD CHECK (([weight]>(0)))
GO
INSERT INTO [dbo].[line]
([id], [name], [weight], [turn])
VALUES
(5, 'gary', 800, 1),
(3, 'jo', 350, 2),
(6, 'thomas', 400, 3),
(2, 'will', 200, 4),
(4, 'mark', 175, 5),
(1, 'james', 100, 6)
;
Vous devriez essayer d'éviter les boucles en général. Ils sont normalement moins efficaces que les solutions basées sur des ensembles ainsi que moins lisibles.
Ce qui suit devrait être assez efficace.
Encore plus si les colonnes de nom et de poids peuvent être INCLUDE-
D dans l'index pour éviter les recherches de clés.
Il peut analyser l'index unique par ordre de turn
et calculer le total cumulé de la colonne Weight
- puis utiliser LEAD
avec les mêmes critères de classement pour voir le total cumulé la ligne suivante sera.
Dès qu'il trouve la première ligne où cela dépasse 1000 ou est NULL
(indiquant qu'il n'y a pas de ligne suivante), il peut arrêter l'analyse.
WITH T1
AS (SELECT *,
SUM(Weight) OVER (ORDER BY turn ROWS UNBOUNDED PRECEDING) AS cume_weight
FROM [dbo].[line]),
T2
AS (SELECT LEAD(cume_weight) OVER (ORDER BY turn) AS next_cume_weight,
*
FROM T1)
SELECT TOP 1 name
FROM T2
WHERE next_cume_weight > 1000
OR next_cume_weight IS NULL
ORDER BY turn
En pratique, il semble lire quelques lignes avant où cela est strictement nécessaire - il semble que chaque paire d'agrégats de spoule/flux de fenêtre entraîne la lecture de deux lignes supplémentaires.
Pour les exemples de données dans la question, idéalement, il ne faudrait lire que deux lignes de l'analyse d'index, mais en réalité, il lit 6, mais ce n'est pas un problème d'efficacité significatif et il ne se dégrade pas lorsque davantage de lignes sont ajoutées au tableau cette démo )
Pour ceux qui sont intéressés par ce problème, une image avec les lignes sorties par chaque opérateur (comme le montre l'événement étendu query_trace_column_values
) Est ci-dessous, les lignes sont sorties dans l'ordre row_id
(À partir de 47
Pour la première ligne lue par le balayage d'index et se terminant à 113
Pour le TOP
)
Cliquez sur l'image ci-dessous pour l'agrandir ou voir également la version animée pour rendre le flux plus facile à suivre .
Pause de l'animation au point où l'agrégat de flux de droite a émis sa première ligne (pour gary - turn = 1). Il semble évident qu'il attendait de recevoir sa première ligne avec un WindowCount différent (pour Jo - turn = 2). Et la bobine de fenêtre ne libère pas la première ligne "Jo" jusqu'à ce qu'elle ait lu la ligne suivante avec un turn
différent (pour thomas - turn = 3)
Ainsi, le spouleur de fenêtre et l'agrégat de flux entraînent tous deux la lecture d'une ligne supplémentaire et il y en a quatre dans le plan - d'où 4 lignes supplémentaires.
Une explication des colonnes montrées ci-dessus suit (basée sur info ici )
Partition By
Dans le SUM
seule la première ligne obtient 1row_number()
dans le groupe indiqué par l'indicateur Segment1010. Comme toutes les lignes sont dans le même groupe, ce sont des entiers ascendants de 1 à 6. Seraient utilisés pour filtrer les lignes de trame de droite dans des cas comme rows between 5 preceding and 2 following
. (ou comme pour LEAD
plus tard)Partition By
Dans le SUM
seule la première ligne obtient 1 (Identique à Segment1010)UNBOUNDED PRECEDING
. Où il émet deux lignes par ligne source. Un avec les valeurs cumulées et un avec les valeurs de détail. Bien qu'il n'y ait aucune différence visible dans les lignes exposées par query_trace_column_values
Je suppose que les colonnes cumulatives sont là en réalité.Count(*)
groupé par WindowCount1012 selon le plan mais en fait un nombre courantSUM(weight)
groupé par WindowCount1012 selon le plan mais en fait la somme courante du poids (c'est-à-dire cume_weight
)CASE WHEN [Expr1004]=(0) THEN NULL ELSE [Expr1005] END
- Je ne vois pas comment COUNT(*)
peut être 0 donc sera toujours en cours d'exécution somme (cume_weight
)partition by
Sur le LEAD
donc la première ligne obtient 1. Tous les autres sont nulsrow_number()
dans le groupe indiqué par l'indicateur Segment1013. Comme toutes les lignes sont dans le même groupe, il s'agit d'entiers croissants de 1 à 4LEAD
requiert la seule ligne suivanteLEAD
requiert la seule ligne suivantepartition by
Sur le LEAD
donc la première ligne obtient 1. Tous les autres sont nulsLEAD
a au maximum 2 lignes (la suivante et la suivante)LAST_VALUE([Expr1002])
pour LEAD(cume_weight)
Tout comme une curiosité (puisque la question indique T-SQL), il est également possible de résoudre ce problème efficacement en utilisant SQLCLR.
L'idée est de lire les lignes une par une dans l'ordre turn
jusqu'à ce que weight
dépasse 1000 (ou nous manquons de lignes), puis de retourner la dernière name
lecture.
Le code source est:
using Microsoft.SqlServer.Server;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
public partial class UserDefinedFunctions
{
[SqlFunction(DataAccess = DataAccessKind.Read,
SystemDataAccess = SystemDataAccessKind.None,
IsDeterministic = true, IsPrecise = true)]
[return: SqlFacet(IsFixedLength = false, IsNullable = true, MaxSize = 255)]
public static SqlString Elevator()
{
const string query =
@"SELECT L.[name], L.[weight]
FROM dbo.line AS L
ORDER BY L.turn;";
using (var con = new SqlConnection("context connection = true"))
{
con.Open();
using (var cmd = new SqlCommand(query, con))
{
var rdr = cmd.ExecuteReader(CommandBehavior.SingleResult);
var name = SqlString.Null;
var total = 0;
while (rdr.Read() && (total += rdr.GetInt32(1)) <= 1000)
{
name = rdr.GetSqlString(0);
}
return name;
}
}
}
}
La fonction Assembly et T-SQL compilée:
CREATE Assembly Elevator AUTHORIZATION [dbo]
FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C010300606C0E5B0000000000000000E00022200B013000000C000000060000000000004A2A0000002000000040000000000010002000000002000004000000000000000600000000000000008000000002000000000000030060850000100000100000000010000010000000000000100000000000000000000000F82900004F00000000400000A802000000000000000000000000000000000000006000000C000000C02800001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000500A000000200000000C000000020000000000000000000000000000200000602E72737263000000A80200000040000000040000000E0000000000000000000000000000400000402E72656C6F6300000C00000000600000000200000012000000000000000000000000000040000042000000000000000000000000000000002C2A0000000000004800000002000500F0200000D007000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B30030070000000010000117201000070730600000A0A066F0700000A723500007006730800000A0B07176F0900000A0C7E0A00000A0D1613042B0808166F0B00000A0D086F0C00000A2C14110408176F0D00000A5825130420E803000031DC091305DE14072C06076F0E00000ADC062C06066F0E00000ADC11052A011C000002001D003C59000A0000000002000B005863000A000000001E02280F00000A2A42534A4201000100000000000C00000076342E302E33303331390000000005006C000000AC010000237E0000180200002002000023537472696E677300000000380400008801000023555300C0050000100000002347554944000000D00500000002000023426C6F620000000000000002000001471502000900000000FA01330016000001000000110000000200000002000000010000000F000000050000000200000001000000020000000000E70001000000000006008A00A9010600BC00A9010600610096010F00C901000006000202F9000A0075005C010A003E005C010A0038005C010A00AA005C010A00DD00D8010A00120109020A002D0009020A00400109020A00050120010A00770113000A003301200106004D00F900000000000A00000000000100010001001000ED0100001500010001005020000000009600870150000100E820000000008618900106000200000000000000090090010100110090010600190090010A00310090010600490090010600590090011E0071000001060061009001230061004E012A005100F40031006900DA003500810028003B00810001003F0089005900060029009001060020002300860024002B00CE012E000B0055002E0013005E002E001B007D00100044000480000000000000000000000000000000008701000004000000000000000000000047001F000000000004000000000000000000000047001300000000000000000000476574496E743332003C4D6F64756C653E0053797374656D2E44617461006D73636F726C696200526561640053716C436F6D6D616E640053797374656D446174614163636573734B696E640049446973706F7361626C6500446973706F73650044656275676761626C654174747269627574650053716C46756E6374696F6E41747472696275746500436F6D70696C6174696F6E52656C61786174696F6E734174747269627574650053716C46616365744174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650047657453716C537472696E6700456C657661746F722E646C6C004E756C6C0053797374656D004F70656E004462436F6E6E656374696F6E0053716C436F6E6E656374696F6E0053797374656D2E446174612E436F6D6D6F6E004462446174615265616465720053716C446174615265616465720045786563757465526561646572004D6963726F736F66742E53716C5365727665722E53657276657200436F6D6D616E644265686176696F7200456C657661746F72002E63746F720053797374656D2E446961676E6F73746963730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F6465730053797374656D2E446174612E53716C54797065730055736572446566696E656446756E6374696F6E73004F626A6563740053797374656D2E446174612E53716C436C69656E740000003363006F006E007400650078007400200063006F006E006E0065006300740069006F006E0020003D00200074007200750065000081510D000A00200020002000200020002000200020002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000200020002000200020002000200020004C002E005B006E0061006D0065005D002C000D000A0020002000200020002000200020002000200020002000200020002000200020004C002E005B007700650069006700680074005D000D000A00200020002000200020002000200020002000200020002000460052004F004D002000640062006F002E006C0069006E00650020004100530020004C000D000A002000200020002000200020002000200020002000200020004F0052004400450052002000420059000D000A0020002000200020002000200020002000200020002000200020002000200020004C002E007400750072006E003B000D000A00200020002000200020002000200020002000200020002000006C68AC85076C6945BCECB2CE73B9787F000420010108032000010520010111110D0706122D123112351129081129042001010E062002010E122D0620011235113D0306112905200111290803200002042001080802060E08B77A5C561934E08904000011290801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F7773010801000200000000008146010004005455794D6963726F736F66742E53716C5365727665722E5365727665722E446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D342E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038390A446174614163636573730100000054557F4D6963726F736F66742E53716C5365727665722E5365727665722E53797374656D446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D342E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038391053797374656D446174614163636573730000000054020F497344657465726D696E69737469630154020949735072656369736501310100030054020D497346697865644C656E6774680054020A49734E756C6C61626C65015408074D617853697A65FF00000000000000606C0E5B00000000020000001C010000DC280000DC0A000052534453F8D0A90B6069F74DB5FBFABECC9D213901000000633A5C75736572735C7061756C775C736F757263655C7265706F735C4461746162617365325C4461746162617365325C6F626A5C52656C656173655C456C657661746F722E7064620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202A000000000000000000003A2A00000020000000000000000000000000000000000000000000002C2A0000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF2500200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001001000000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000584000004C02000000000000000000004C0234000000560053005F00560045005200530049004F004E005F0049004E0046004F0000000000BD04EFFE00000100000000000000000000000000000000003F000000000000000400000002000000000000000000000000000000440000000100560061007200460069006C00650049006E0066006F00000000002400040000005400720061006E0073006C006100740069006F006E00000000000000B004AC010000010053007400720069006E006700460069006C00650049006E0066006F0000008801000001003000300030003000300034006200300000002C0002000100460069006C0065004400650073006300720069007000740069006F006E000000000020000000300008000100460069006C006500560065007200730069006F006E000000000030002E0030002E0030002E00300000003A000D00010049006E007400650072006E0061006C004E0061006D006500000045006C0065007600610074006F0072002E0064006C006C00000000002800020001004C006500670061006C0043006F00700079007200690067006800740000002000000042000D0001004F0072006900670069006E0061006C00460069006C0065006E0061006D006500000045006C0065007600610074006F0072002E0064006C006C0000000000340008000100500072006F006400750063007400560065007200730069006F006E00000030002E0030002E0030002E003000000038000800010041007300730065006D0062006C0079002000560065007200730069006F006E00000030002E0030002E0030002E0030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000C0000004C3A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
WITH PERMISSION_SET = SAFE;
GO
CREATE FUNCTION dbo.Elevator ()
RETURNS nvarchar(255)
AS EXTERNAL NAME Elevator.UserDefinedFunctions.Elevator;
Obtenir le résultat:
SELECT dbo.Elevator();
Légère variation par rapport à solution de Martin Smith
SELECT top 1 name
FROM (
SELECT id, name, weight, turn
, SUM(weight) OVER (ORDER BY turn) AS cumulative_weight
FROM line
) as T
WHERE cumulative_weight <= 1000
ORDER BY turn DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
est le cadre de fenêtre par défaut, donc je ne l'ai pas déclaré.
Un prédicat pour le poids cumulé actuel est utilisé à la place du poids cumulé suivant.
Je n'ai vérifié aucun plan, donc je ne peux pas dire s'il y a une différence à cet égard.
Vous pouvez faire une jointure contre elle-même:
select
a.id, a.turn, a.game,
coalesce(sum(b.weight), 0) as cumulative_weight
from
table a
left join
table b
on
a.turn > b.turn
group by
a.id, a.turn, a.game ;
Ce genre de chose n'est pas très efficace car il provoque une sélection par ligne. Mais au moins, il est exprimé en une seule déclaration.
Si vous n'avez pas à le faire entièrement en SQL, vous pouvez simplement sélectionner toutes les lignes et les parcourir, en les additionnant au fur et à mesure.
Vous pouvez également faire de même dans une procédure stockée sans la table temporaire. Maintenez simplement la somme et le nom de la dernière ligne dans une variable.