Excel VBA Performance - 1 million de lignes - Supprimer les lignes contenant une valeur, en moins de 1 min
Je suis en train d'essayer de trouver un moyen de filtrer les données et supprimer des lignes dans une feuille de calcul, en moins d'une minute
L'objectif:
- Trouver tous les enregistrements contenant un texte spécifique dans la colonne 1, et de supprimer la ligne entière
- Garder toutes les cellules de mise en forme (couleurs, la police des frontières, les largeurs de colonne) et les formules qu'ils sont
.
Données De Test:
:
.
Comment fonctionne le code:
- Il commence par tourner toutes les fonctionnalités d'Excel Off
-
Si le classeur n'est pas vide et la valeur texte supprimé existe dans la colonne 1
- Des Copies de la plage utilisée de la colonne 1 pour un tableau
- Itère sur chaque valeur dans le tableau en arrière
-
Lorsqu'il trouve une correspondance:
- Ajoute l'adresse de la cellule à un tmp chaîne de caractères dans le format
"A11,A275,A3900,..."
- Si la variable tmp longueur est proche de 255 caractères
- Supprime des lignes à l'aide de
.Range("A11,A275,A3900,...").EntireRow.Delete Shift:=xlUp
- Réinitialise tmp à vide et passer à la prochaine série de lignes
- Ajoute l'adresse de la cellule à un tmp chaîne de caractères dans le format
- À la fin, il tourne toutes les fonctionnalités d'Excel de retour Sur
.
Le principal problème est l'opération de Suppression, et la durée totale doit être de moins d'une minute. Le code à base de solution est acceptable tant qu'il effectue en moins de 1 minute.
Cela réduit la portée de très peu de réponses acceptables. Les réponses déjà fournies sont également très court et facile à mettre en œuvre. Un effectue l'opération en environ 30 secondes, donc il y a au moins une réponse qui fournit une solution acceptable, et d'autres peuvent le trouver utile ainsi
.
Ma principale fonction initiale:
Sub DeleteRowsWithValuesStrings()
Const MAX_SZ As Byte = 240
Dim i As Long, j As Long, t As Double, ws As Worksheet
Dim memArr As Variant, max As Long, tmp As String
Set ws = Worksheets(1)
max = GetMaxCell(ws.UsedRange).Row
FastWB True: t = Timer
With ws
If max > 1 Then
If IndexOfValInRowOrCol("Test String", , ws.UsedRange) > 0 Then
memArr = .Range(.Cells(1, 1), .Cells(max, 1)).Value2
For i = max To 1 Step -1
If memArr(i, 1) = "Test String" Then
tmp = tmp & "A" & i & ","
If Len(tmp) > MAX_SZ Then
.Range(Left(tmp, Len(tmp) - 1)).EntireRow.Delete Shift:=xlUp
tmp = vbNullString
End If
End If
Next
If Len(tmp) > 0 Then
.Range(Left(tmp, Len(tmp) - 1)).EntireRow.Delete Shift:=xlUp
End If
.Calculate
End If
End If
End With
FastWB False: InputBox "Duration: ", "Duration", Timer - t
End Sub
Des fonctions d'assistance (activer les fonctionnalités d'Excel et de décollage):
Public Sub FastWB(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationManual, xlCalculationAutomatic)
.DisplayAlerts = Not opt
.DisplayStatusBar = Not opt
.EnableAnimations = Not opt
.EnableEvents = Not opt
.ScreenUpdating = Not opt
End With
FastWS , opt
End Sub
Public Sub FastWS(Optional ByVal ws As Worksheet = Nothing, _
Optional ByVal opt As Boolean = True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
EnableWS ws, opt
Next
Else
EnableWS ws, opt
End If
End Sub
Private Sub EnableWS(ByVal ws As Worksheet, ByVal opt As Boolean)
With ws
.DisplayPageBreaks = False
.EnableCalculation = Not opt
.EnableFormatConditionsCalculation = Not opt
.EnablePivotTable = Not opt
End With
End Sub
Trouve la dernière cellule avec des données (merci @ZygD - maintenant, je l'ai testé dans plusieurs scénarios):
Public Function GetMaxCell(Optional ByRef rng As Range = Nothing) As Range
'Returns the last cell containing a value, or A1 if Worksheet is empty
Const NONEMPTY As String = "*"
Dim lRow As Range, lCol As Range
If rng Is Nothing Then Set rng = Application.ActiveWorkbook.ActiveSheet.UsedRange
If WorksheetFunction.CountA(rng) = 0 Then
Set GetMaxCell = rng.Parent.Cells(1, 1)
Else
With rng
Set lRow = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByRows)
If Not lRow Is Nothing Then
Set lCol = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByColumns)
Set GetMaxCell = .Parent.Cells(lRow.Row, lCol.Column)
End If
End With
End If
End Function
Renvoie l'index d'un match dans le tableau, ou 0 si aucune correspondance n'est trouvée:
Public Function IndexOfValInRowOrCol( _
ByVal searchVal As String, _
Optional ByRef ws As Worksheet = Nothing, _
Optional ByRef rng As Range = Nothing, _
Optional ByRef vertical As Boolean = True, _
Optional ByRef rowOrColNum As Long = 1 _
) As Long
'Returns position in Row or Column, or 0 if no matches found
Dim usedRng As Range, result As Variant, searchRow As Long, searchCol As Long
result = CVErr(9999) '- generate custom error
Set usedRng = GetUsedRng(ws, rng)
If Not usedRng Is Nothing Then
If rowOrColNum < 1 Then rowOrColNum = 1
With Application
If vertical Then
result = .Match(searchVal, rng.Columns(rowOrColNum), 0)
Else
result = .Match(searchVal, rng.Rows(rowOrColNum), 0)
End If
End With
End If
If IsError(result) Then IndexOfValInRowOrCol = 0 Else IndexOfValInRowOrCol = result
End Function
.
Mise à jour:
Testé 6 solutions (3 tests de chaque): Excel Héros solution est la plus rapide jusqu'à présent (supprime les formules)
.
Voici les résultats, plus rapide au plus lent:
.
Test 1. Total de 100 000 enregistrements de, 10 000 à être supprimé:
1. ExcelHero() - 1.5 seconds
2. DeleteRowsWithValuesNewSheet() - 2.4 seconds
3. DeleteRowsWithValuesStrings() - 2.45 minutes
4. DeleteRowsWithValuesArray() - 2.45 minutes
5. QuickAndEasy() - 3.25 minutes
6. DeleteRowsWithValuesUnion() - Stopped after 5 minutes
.
Test 2. Total de 1 million d'enregistrements, de 100 000 à être supprimé:
1. ExcelHero() - 16 seconds (average)
2. DeleteRowsWithValuesNewSheet() - 33 seconds (average)
3. DeleteRowsWithValuesStrings() - 4 hrs 38 min (16701.375 sec)
4. DeleteRowsWithValuesArray() - 4 hrs 37 min (16626.3051757813 sec)
5. QuickAndEasy() - 5 hrs 40 min (20434.2104492188 sec)
6. DeleteRowsWithValuesUnion() - N/A
.
Notes:
- ExcelHero méthode: facile à mettre en œuvre, fiable, extrêmement rapide, mais supprime les formules
- NewSheet méthode: facile à mettre en œuvre, fiable et respecte l'objectif
- Chaînes de méthode: plus d'effort à mettre en œuvre, fiable, mais ne répond pas à l'exigence
- Méthode de tableau: similaire à Cordes, mais ReDims un tableau (version plus rapide de l'Union)
- QuickAndEasy: facile à mettre en œuvre (court, fiable et élégant), mais ne répond pas à l'exigence
- Gamme de l'Union: la mise en œuvre de complexité similaire aux 2 et 3, mais trop lent
J'ai aussi fait le test des données plus réalistes, par l'introduction d'inhabituel valeurs:
- des cellules vides, des plages, des lignes et des colonnes
- caractères spéciaux, comme =[`~!@#$%^&*()_-+{}[]\|;:'",.<>/?, séparer et de multiples combinaisons
- des espaces, des tabulations, des vides, des formules, des frontières, de la police et d'autres cellules de mise en forme
- grands et petits nombres avec décimales (=12.9999999999999 + 0.00000000000000001)
- liens hypertexte, la mise en forme conditionnelle règles
- vide mise en forme à l'intérieur et à l'extérieur des plages de données de
- autre chose qui pourrait causer des problèmes de données
- J'ai commencé à regarder votre fil à partir de la fin -
GetMaxCell
fonction. Une chose à mentionner - vous devez supprimer la dot:Set GetMaxCell = .Cells(lRow.row, lCol.Column)
devrait devenirSet GetMaxCell = Cells(lRow.row, lCol.Column)
, parce que vous auriez résultat inexact si par exemple, l'ensemble de la colonneA:A
était vide. Cependant, maintenant je vois, que l'ensemble de laGetMaxCell
fonction est inutile ici? C'est ne sont pas liées pourdeleteRowsWithValuesStrings
! - Merci. J'ai supprimé l'appel à GetMaxCell pendant le test et j'ai oublié de le remettre, mais je vais le mettre à jour. Aussi, j'ai analysé spécifiquement pour la fiabilité - le point de référence est liée à l'ensemble de la région, pas seulement la première colonne. Il commence à partir de la première cellule et utilise xlPrevious, ce qui en fait habiller à la dernière cellule de la zone, puis continue de se déplacer vers l'arrière à la première cellule avec des données
- Je ne suis pas sûr que vous avez compris ce que je voulais dire. Dans votre situation actuelle, à partir de vos données, je vois qu'il n'y a pas de différence entre l'option avec dot et sans. Toutefois, si jamais vous décidez d'utiliser
GetMaxCell
dans un autre projet, vous devez avoir la version sans dot. Dans mon test de la feuille que j'avais données de l'ordre de B2 E4 (A:A est vide, 1:1 est vide). Le résultat de la fonction avec la dot était la cellule F5, ce qui est manifestement inexact. Après la suppression de la dot, le résultat était correct - E4. - Aussi, vous manquez
Set
avantws =
etrng =
. - un simple changement qui fait une différence raisonnable est de faire une gamme de l'union de toutes les lignes que vous souhaitez supprimer puis de les supprimer tous à la fois (donc au lieu de votre ligne téléphonique .supprimer maintenant, faire quelque chose comme
if deleteRng is nothing then Set deleteRng = rng Else Set deleteRng = Union(deleteRng, rng.EntireRow)
puis, après votre boucle ne le supprimer (deleteRng.supprimer). - Je pense que cette question appartient vraiment sur la Revue de Code car le code fourni fonctionne correctement et c'est seulement à la performance qui doit être amélioré
- Je vais voter pour fermer cette question hors-sujet, car il appartient à l'Examen du Code StackExchange site
- J'ai tendance à être en désaccord avec le hors-sujet suggestion: AINSI a une "performance" de la balise, et aussi: ce n'est pas lié à l'examen du code, mais de trouver de meilleures solutions à un problème commun: la suppression des données spécifiques de gros fichiers, et de surmonter les problèmes de performance, indépendamment de la façon dont il est accompli (le code). L'activité et de la rétroaction sur la question montrent un certain intérêt dans le sujet, je vous suggérons consensus de la communauté avant de prendre une décision
- Le plus proche de la Méta question que j'ai pu trouver sur le sujet a été celui-ci. Il est juste de dire qu'il n'y a pas d'accord unanime sur la question de ce qui appartient à DONC contre ce qui est sur la CR
- C'est une zone grise, en effet. Alors pour clarifier mon propos: je ne cherche pas à améliorer mon code, mais de trouver un générique meilleure approche (le code), qui traite de la nécessité de filtrer les données de grande taille, indépendamment de la mise en œuvre - il peut être une nouvelle idée créative, ou tout simplement à la logique de base, tant qu'il permet de résoudre le problème ", la réponse arrive trop tard pour être utile", comme mentionné dans votre lien. Il est donc plus lié à la découverte de la solution ("comment faire") que "je veux faire de mon algorithme en mieux" - de toute autre algorithme qui fournit la meilleure solution est acceptable
- Pouvez-vous m'envoyer le classeur photo ci-dessus? J'aimerais prendre un coup d'oeil. Mon adresse email est : [email protected]
- Je viens de l'envoyer. Laissez-moi savoir si vous avez besoin de détails
- En utilisant PowerQuery être utile dans cette situation? Je suis en train de réfléchir à la recherche de la performance sur des données de grande taille quand il est dans la mémoire et non pas dans la feuille de calcul.
- il peut être; je n'ai pas l'utiliser, mais de ce que j'ai lu qu'il peut gérer des données de grande taille très bien
Vous devez vous connecter pour publier un commentaire.
Je suis en fournissant la première réponse comme une référence
D'autres peuvent trouver utile, si il n'y a pas d'autres options disponibles
.
.
À un niveau élevé:
.AutoFilter Field:=1, Criteria1:="<>Test String"
Il utilise les mêmes fonctions d'assistance posté dans la question
Les 99% de la durée est utilisée par le filtre automatique
.
Il y a quelques limitations que j'ai trouvé jusqu'à présent, le premier peut être adressée:
S'il y a des lignes masquées sur la première feuille, il permet de faire apparaître leur
VBA connexes:
.
Quelques notes sur l'utilisation de gros fichiers comme ceci:
Non géré la mise en forme Conditionnelle règles peut entraîner exponentielle des problèmes de performances
La lecture de fichier ou de données à partir du réseau est beaucoup plus lent que de travailler avec un locall fichier
Une augmentation considérable de la vitesse peut être atteint que si la source de données ne contiennent pas de formules, ou si le scénario permettrait (ou ne veulent) les formules pour être convertis en valeurs lors de la conditionnelle suppressions de lignes.
Ci-dessus, comme une mise en garde, ma solution utilise la AdvancedFilter de l'objet range. C'est deux fois plus rapide que DeleteRowsWithValuesNewSheet().
Sur mes vieux Dell Inspiron 1564 (Win 7 Office 2007) ce:
a pris environ 10 secondes pour s'exécuter. Je suis en supposant que la colonne AA est disponible.
EDIT#1:
Veuillez noter que ce code ne pas ensemble Calcul Manuel. La Performance sera meilleure si le mode de Calcul est défini sur Manuel après l ' "aide" de la colonne est autorisé à calculer.
Je sais je suis très en retard avec ma réponse ici cependant les futurs visiteurs peuvent trouver très utile.
Veuillez Noter: Mon approche nécessite une colonne d'index pour les lignes à l'extrémité de la commande d'origine, cependant, si vous n'avez pas l'esprit les lignes dans un ordre différent, puis une colonne d'index n'est pas nécessaire, et la ligne de code supplémentaire peut être retiré.
Mon approche: Mon approche a été de simplement sélectionner toutes les lignes de la plage sélectionnée (colonne), de les trier dans l'ordre croissant à l'aide de
Range.Sort
, puis à recueillir la première et la dernière de l'indice de"Test String"
dans la plage sélectionnée (colonne). J'ai ensuite créer une gamme à partir de la première et de la dernière indices et utiliserRange.EntrieRow.Delete
pour supprimer toutes les lignes qui contiennent"Test String"
.Pour:
- Il est très rapide.
- Il ne pas supprimer la mise en forme, formules, des graphiques, des images ou quoi que ce soit comme la méthode de copie pour une nouvelle feuille.
Contre:
- D'une taille décente de code pour implémenter cependant, il est tout simple.
Plage De Test De Génération De Sous:
Filtrer Et Supprimer Des Lignes Sous:
CE CODE UTILISE
FastWB
,FastWS
ETEnableWS
PAR Paul Bica!Fois à 100 entrées (10k à être supprimé, FastWB Vrai):
1. 0,2 secondes.
2. 0,2 secondes.
3. 0.21 secondes.
Avg. 0,2 secondes.
Fois à 1 million d'entrées (100k à être supprimé, FastWB Vrai):
1. 2.3 secondes.
2. 2.32 secondes.
3. 2.3 secondes.
Avg. 2.31 secondes.
En cours d'exécution sur: Windows 10, iMac i3 11,2 (à Partir de 2010)
MODIFIER
Ce code a été conçu à l'origine avec le but de filtrer les valeurs numériques à l'extérieur d'une plage de nombres et a été adapté pour filtrer
"Test String"
donc une partie du code peut être redondante.Votre utilisation de tableaux dans le calcul de la plage utilisée et le nombre de lignes peut nuire à la performance. Voici une autre approche que dans le test s'avère efficace à travers 1m+ lignes de données - entre 25 et 30 secondes. Il n'utilise pas de filtres il en sera de supprimer des lignes, même si ce n'est caché. La suppression d'un ensemble de lignes ne seront pas effectuer la mise en forme ou les largeurs de colonne de l'autre les lignes restantes.
Tout d'abord, vérifiez si le ActiveSheet a "Chaîne de Test". Puisque vous êtes seulement intéressés dans la Colonne 1, j'ai utilisé ceci:
Au lieu d'utiliser votre GetMaxCell() fonction que j'ai tout simplement utilisé
Cells.SpecialCells(xlCellTypeLastCell).Row
pour obtenir la dernière ligne:Puis boucle sur les lignes de données:
Pour tester si la cellule dans la Colonne 1 est égal à "la Chaîne de Test":
Supprimer la ligne:
Mettre tous ensemble complet de code ci-dessous. J'ai mis ActiveSheet à une variable Sht et a ajouté tourné de ScreenUpdating pour en améliorer l'efficacité. Depuis, il a beaucoup de données que j'ai assurez-vous de désactiver les variables à la fin.