web-dev-qa-db-fra.com

T-SQL - Quel est le moyen le plus efficace de parcourir une table jusqu'à ce qu'une condition soit remplie

En a obtenu une tâche de programmation dans le domaine de T-SQL.

Tâche:

  1. Les gens veulent entrer dans un ascenseur, chaque personne a un certain poids.
  2. L'ordre des personnes faisant la queue est déterminé par le tour de colonne.
  3. L'ascenseur a une capacité maximale de <= 1000 lb.
  4. Renvoyez le nom de la dernière personne qui peut entrer dans l'ascenseur avant qu'il ne devienne trop lourd!
  5. Le type de retour doit être une table

enter image description here

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)
;
10
Legends

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 

Plan d'exécution

enter image description here

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.

enter image description here

Une explication des colonnes montrées ci-dessus suit (basée sur info ici )

  • NodeName: Index Scan, NodeId: 15, ColumnName: id colonne de la table de base couverte par l'index
  • NodeName: Index Scan, NodeId: 15, ColumnName: tournez la colonne de la table de base couverte par l'index
  • NodeName: recherche d'index cluster, NodeId: 17, ColumnName: poids colonne de la table de base récupérée à partir de la recherche
  • NodeName: recherche d'index cluster, NodeId: 17, ColumnName: nom colonne de la table de base récupérée de la recherche
  • NodeName: Segment, NodeId: 13, ColumnName: Segment1010 Renvoie 1 au début du nouveau groupe ou null sinon. Comme aucun Partition By Dans le SUM seule la première ligne obtient 1
  • NodeName: Projet de séquence, NodeId: 12, ColumnName: RowNumber1009 row_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)
  • NodeName: Segment, NodeId: 11, ColumnName: Segment1011 Renvoie 1 au début du nouveau groupe ou null sinon. Comme aucun Partition By Dans le SUM seule la première ligne obtient 1 (Identique à Segment1010)
  • NodeName: Spool de fenêtre, NodeId: 10, ColumnName: WindowCount1012 Attribut qui regroupe les lignes appartenant à un cadre de fenêtre. Cette bobine de fenêtre utilise le cas "fast track" pour 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é.
  • NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1004 Count(*) groupé par WindowCount1012 selon le plan mais en fait un nombre courant
  • NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1005 SUM(weight) groupé par WindowCount1012 selon le plan mais en fait la somme courante du poids (c'est-à-dire cume_weight)
  • NodeName: Segment, NodeId: 7, ColumnName: Expr1002 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)
  • NodeName: Segment, NodeId: 7, ColumnName: Segment1013 Non partition by Sur le LEAD donc la première ligne obtient 1. Tous les autres sont nuls
  • NodeName: Projet de séquence, NodeId: 6, ColumnName: RowNumber1006 row_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 à 4
  • NodeName: Segment, NodeId: 4, ColumnName: BottomRowNumber1008 RowNumber1006 + 1 car LEAD requiert la seule ligne suivante
  • NodeName: Segment, NodeId: 4, ColumnName: TopRowNumber1007 RowNumber1006 + 1 car le LEAD requiert la seule ligne suivante
  • NodeName: Segment, NodeId: 4, ColumnName: Segment1014 Non partition by Sur le LEAD donc la première ligne obtient 1. Tous les autres sont nuls
  • NodeName: Fenêtre Spool, NodeId: 3, ColumnName: WindowCount1015 Attribut qui regroupe les lignes appartenant à un cadre de fenêtre en utilisant les numéros de ligne précédents. Le cadre de la fenêtre pour LEAD a au maximum 2 lignes (la suivante et la suivante)
  • NodeName: Stream Aggregate, NodeId: 2, ColumnName: Expr1003 LAST_VALUE([Expr1002]) pour LEAD(cume_weight)
16
Martin Smith

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();
6
Paul White 9

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.

1
Lennart

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.

0
Ewan