Une solution T-SQL pour les lacunes et les îles peut-elle s'exécuter plus rapidement qu'une solution C # exécutée sur le client?
Pour être précis, fournissons quelques données de test:
CREATE TABLE dbo.Numbers
(
n INT NOT NULL
PRIMARY KEY
) ;
GO
INSERT INTO dbo.Numbers
( n )
VALUES ( 1 ) ;
GO
DECLARE @i INT ;
SET @i = 0 ;
WHILE @i < 21
BEGIN
INSERT INTO dbo.Numbers
( n
)
SELECT n + POWER(2, @i)
FROM dbo.Numbers ;
SET @i = @i + 1 ;
END ;
GO
CREATE TABLE dbo.Tasks
(
StartedAt SMALLDATETIME NOT NULL ,
FinishedAt SMALLDATETIME NOT NULL ,
CONSTRAINT PK_Tasks PRIMARY KEY ( StartedAt, FinishedAt ) ,
CONSTRAINT UNQ_Tasks UNIQUE ( FinishedAt, StartedAt )
) ;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, n, '20100101') AS StartedAt ,
DATEADD(MINUTE, n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Ce premier ensemble de données de test présente exactement une lacune:
SELECT StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE StartedAt BETWEEN DATEADD(MINUTE, 499999, '20100101')
AND DATEADD(MINUTE, 500006, '20100101')
Le deuxième ensemble de données de test a 2M -1 espaces, un espace entre chacun des deux intervalles adjacents:
TRUNCATE TABLE dbo.Tasks;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, 3*n, '20100101') AS StartedAt ,
DATEADD(MINUTE, 3*n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Actuellement, je lance 2008 R2, mais les solutions 2012 sont les bienvenues. J'ai publié ma solution C # comme réponse.
Le code C # suivant résout le problème:
Ce code appelle cette procédure stockée:
Il trouve et imprime un intervalle à 2 M d'intervalle dans les moments suivants, cache chaud:
Il trouve et imprime 2M-1 intervalles à 2M dans les moments suivants, cache chaud:
C'est une solution très simple - il m'a fallu 10 minutes pour développer. Un récent diplômé d'université peut le proposer. Du côté de la base de données, le plan d'exécution est une jointure de fusion triviale qui utilise très peu de CPU et de mémoire.
Edit: pour être réaliste, j'exécute le client et le serveur sur des boîtes séparées.
la source
Je pense avoir épuisé les limites de mes connaissances en serveur SQL sur celui-ci ....
Pour trouver un écart dans SQL Server (ce que fait le code C #), et vous ne vous souciez pas du début ou de la fin des écarts (ceux avant le premier démarrage ou après la dernière fin), la requête (ou variantes) suivante est la le plus vite que j'ai pu trouver:
Ce qui fonctionne bien à la main que pour chaque ensemble de départ-arrivée, vous pouvez traiter le début et la fin comme des séquences distinctes, décaler la fin d'une unité et des espaces sont affichés.
par exemple prendre (S1, F1), (S2, F2), (S3, F3), et ordonner comme: {S1, S2, S3, null} et {null, F1, F2, F3} Ensuite, comparer la ligne n à la ligne n dans chaque ensemble, et les écarts sont là où la valeur de l'ensemble F est inférieure à la valeur de l'ensemble S ... le problème, je pense, est que dans SQL Server, il n'y a aucun moyen de joindre ou de comparer deux ensembles distincts uniquement sur l'ordre des valeurs dans l'ensemble ... d'où l'utilisation de la fonction row_number pour nous permettre de fusionner uniquement sur la base du numéro de ligne ... mais il n'y a aucun moyen de dire au serveur SQL que ces valeurs sont uniques (sans les insérer dans une table var avec un index dessus - qui prend plus de temps - je l'ai essayé), donc je pense que la jointure de fusion est moins qu'optimale? (bien que difficile à prouver quand c'est plus rapide que tout ce que je pourrais faire)
J'ai pu obtenir des solutions en utilisant les fonctions LAG / LEAD:
(ce qui, soit dit en passant, je ne garantis pas les résultats - cela semble fonctionner, mais je pense que je compte sur StartedAt pour être en ordre dans le tableau des tâches ... et c'était plus lent)
En utilisant le changement de somme:
(pas de surprise, aussi plus lent)
J'ai même essayé une fonction d'agrégation CLR (pour remplacer la somme - elle était plus lente que la somme et je comptais sur row_number () pour conserver l'ordre des données), et CLR une fonction de valeur de table (pour ouvrir deux jeux de résultats et comparer des valeurs basées uniquement sur sur séquence) ... et c'était aussi plus lent. Je me suis cogné la tête tellement de fois sur les limitations SQL et CLR, en essayant de nombreuses autres méthodes ...
Et pour quoi?
En cours d'exécution sur la même machine et en répartissant à la fois les données C # et les données filtrées SQL dans un fichier (selon le code C # d'origine), les temps sont pratiquement les mêmes .... environ 2 secondes pour les 1 données d'écart (C # généralement plus rapide ), 8 à 10 secondes pour l'ensemble de données à intervalles multiples (SQL généralement plus rapide).
REMARQUE : n'utilisez pas l'environnement de développement SQL Server pour la comparaison de synchronisation, car son affichage sur la grille prend du temps. Testé avec SQL 2012, VS2010, .net 4.0 Profil client
Je soulignerai que les deux solutions effectuent à peu près le même tri des données sur le serveur SQL, de sorte que la charge du serveur pour le fetch-sort sera similaire, quelle que soit la solution que vous utilisez, la seule différence étant le traitement sur le client (plutôt que sur le serveur) et le transfert sur le réseau.
Je ne sais pas quelle pourrait être la différence lors du partitionnement par différents membres du personnel, ou quand vous pourriez avoir besoin de données supplémentaires avec les informations sur l'écart (bien que je ne puisse pas penser à autre chose qu'un identifiant du personnel), ou bien sûr si il y a une connexion de données lente entre le serveur SQL et la machine cliente (ou un client lent ) ... Je n'ai pas non plus comparé les temps de verrouillage, ou les problèmes de contention, ou les problèmes de CPU / RESEAU pour plusieurs utilisateurs ... Donc, je Je ne sais pas lequel est le plus susceptible d'être un goulot d'étranglement dans ce cas.
Ce que je sais, c'est oui, le serveur SQL n'est pas bon dans ce genre de comparaisons d'ensemble, et si vous n'écrivez pas correctement la requête, vous en paierez le prix fort.
Est-ce plus facile ou plus difficile que d'écrire la version C #? Je ne suis pas tout à fait sûr, le changement +/- 1, la solution totale en cours d'exécution n'est pas entièrement intuitif non plus, et moi, mais ce n'est pas la première solution à laquelle un diplômé moyen viendrait ... une fois terminé, il est assez facile de copier, mais il faut un aperçu pour écrire en premier lieu ... il en va de même pour la version SQL. Quel est le plus difficile? Qu'est-ce qui est plus robuste pour les données frauduleuses? Lequel a le plus de potentiel pour les opérations parallèles? Est-ce vraiment important lorsque la différence est si faible par rapport à l'effort de programmation?
Une dernière note; il y a une contrainte non déclarée sur les données - le StartedAt doit être inférieur au FinishedAt, sinon vous obtiendrez de mauvais résultats.
la source
Voici une solution qui s'exécute en 4 secondes.
la source