J'essaie de trouver un moyen de filtrer des données volumineuses et de supprimer des lignes dans une feuille de calcul en moins d'une minute
Le but:
.
Données de test:
:
.
Comment fonctionne le code:
Si le classeur n'est pas vide et que la valeur de texte à supprimer existe dans la colonne 1
Quand il trouve une correspondance:
"A11,A275,A3900,..."
.Range("A11,A275,A3900,...").EntireRow.Delete Shift:=xlUp
.
Le problème principal est l'opération de suppression}, et la durée totale doit être inférieure à une minute. Toute solution basée sur un code est acceptable tant qu'elle fonctionne moins d'une minute.
Cela réduit la portée à très peu de réponses acceptables. Les réponses déjà fournies sont également très courtes et faciles à mettre en œuvre. One effectue l'opération en environ 30 secondes. Il y a donc au moins une réponse qui offre une solution acceptable, et d'autres peuvent également la trouver utile.
.
Ma fonction initiale principale:
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
Fonctions d'assistance (activer et désactiver les fonctions Excel):
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ée 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
Retourne l'index d'une correspondance 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
.
Mettre à jour:
Testé 6 solutions (3 tests chacune): La solution Excel Hero est la plus rapide jusqu'à présent (supprime les formules)
.
Voici les résultats, du plus rapide au plus lent:
.
Test 1. Total de 100 000 enregistrements, dont 10 000 à supprimer:
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, dont 100 000 à supprimer:
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
.
Remarques:
J'ai également rendu les données de test plus réalistes en introduisant des valeurs inhabituelles:
Je fournis la première réponse comme référence
D'autres peuvent le trouver utile, s'il n'y a pas d'autres options disponibles
.
Sub DeleteRowsWithValuesNewSheet() '100K records 10K to delete
'Test 1: 2.40234375 sec
'Test 2: 2.41796875 sec
'Test 3: 2.40234375 sec
'1M records 100K to delete
'Test 1: 32.9140625 sec
'Test 2: 33.1484375 sec
'Test 3: 32.90625 sec
Dim oldWs As Worksheet, newWs As Worksheet, rowHeights() As Long
Dim wsName As String, t As Double, oldUsedRng As Range
FastWB True: t = Timer
Set oldWs = Worksheets(1)
wsName = oldWs.Name
Set oldUsedRng = oldWs.Range("A1", GetMaxCell(oldWs.UsedRange))
If oldUsedRng.Rows.Count > 1 Then 'If sheet is not empty
Set newWs = Sheets.Add(After:=oldWs) 'Add new sheet
With oldUsedRng
.AutoFilter Field:=1, Criteria1:="<>Test String"
.Copy 'Copy visible data
End With
With newWs.Cells
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteAll 'Paste data on new sheet
.Cells(1, 1).Select 'Deselect paste area
.Cells(1, 1).Copy 'Clear Clipboard
End With
oldWs.Delete 'Delete old sheet
newWs.Name = wsName
End If
FastWB False: InputBox "Duration: ", "Duration", Timer - t
End Sub
.
Au plus haut niveau:
.AutoFilter Field:=1, Criteria1:="<>Test String"
Il utilise les mêmes fonctions d'assistance affichées dans la question
Le 99% de la durée est utilisée par le filtre automatique
.
Il y a quelques limitations que j'ai trouvées jusqu'à présent, la première peut être adressée:
S'il y a des lignes cachées sur la feuille initiale, elle les masque.
VBA liés:
.
Quelques notes sur l’utilisation de gros fichiers comme ceci:
Les règles de mise en forme conditionnelle non gérées peuvent entraîner des problèmes de performances exponentielles
Lire un fichier ou des données sur le réseau est beaucoup plus lent que de travailler avec un fichier local
Un gain de vitesse important peut être obtenu si les données source ne contiennent pas de formules ou si le scénario permet (ou souhaite) que les formules soient converties en valeurs absolues lors des suppressions de lignes conditionnelles.
Avec ce qui précède comme avertissement, ma solution utilise le filtre avancé de l’objet plage. Il est environ deux fois plus rapide que DeleteRowsWithValuesNewSheet ().
Public Sub ExcelHero()
Dim t#, crit As Range, data As Range, ws As Worksheet
Dim r&, fc As Range, lc As Range, fr1 As Range, fr2 As Range
FastWB True
t = Timer
Set fc = ActiveSheet.UsedRange.Item(1)
Set lc = GetMaxCell
Set data = ActiveSheet.Range(fc, lc)
Set ws = Sheets.Add
With data
Set fr1 = data.Worksheet.Range(fc, fc.Offset(, lc.Column))
Set fr2 = ws.Range(ws.Cells(fc.Row, fc.Column), ws.Cells(fc.Row, lc.Column))
With fr2
fr1.Copy
.PasteSpecial xlPasteColumnWidths: .PasteSpecial xlPasteAll
.Item(1).Select
End With
Set crit = .Resize(2, 1).Offset(, lc.Column + 1)
crit = [{"Column 1";"<>Test String"}]
.AdvancedFilter xlFilterCopy, crit, fr2
.Worksheet.Delete
End With
FastWB False
r = ws.UsedRange.Rows.Count
Debug.Print "Rows: " & r & ", Duration: " & Timer - t & " seconds"
End Sub
Sur mon Dell Inspiron 1564 âgé (Win 7 Office 2007), ceci:
Sub QuickAndEasy()
Dim rng As Range
Set rng = Range("AA2:AA1000001")
Range("AB1") = Now
Application.ScreenUpdating = False
With rng
.Formula = "=If(A2=""Test String"",0/0,A2)"
.Cells.SpecialCells(xlCellTypeFormulas, xlErrors).EntireRow.Delete
.Clear
End With
Application.ScreenUpdating = True
Range("AC1") = Now
End Sub
a pris environ 10 secondes pour courir. Je suppose que la colonneAA AAest disponible.
EDIT # 1:
Veuillez noter que ce code ne pas définit Calcul sur Manuel. Les performances s'amélioreront si le mode de calcul est défini sur Manuel après la colonne "aide" est autorisée à calculer.
Je sais que je suis incroyablement en retard avec ma réponse ici, mais les futurs visiteurs pourraient la trouver très utile.
Remarque: Mon approche nécessite une colonne d'index pour que les lignes se retrouvent dans l'ordre d'origine. Toutefois, si vous ne craignez pas que les lignes soient dans un ordre différent, une colonne d'index n'est pas nécessaire et la ligne supplémentaire de le code peut être supprimé.
Mon approche: Mon approche consistait simplement à sélectionner toutes les lignes de la plage sélectionnée (colonne), à les trier par ordre croissant en utilisant Range.Sort
, puis à collecter le premier et le dernier index de "Test String"
dans la plage sélectionnée (colonne). Je crée ensuite une plage à partir du premier et du dernier index et utilise Range.EntrieRow.Delete
pour supprimer toutes les lignes contenant "Test String"
.
Avantages:
- Il flambe vite.
- Il ne supprime pas le formatage, les formules, les graphiques, les images ou quoi que ce soit d'autre que la méthode qui copie sur une nouvelle feuille.
Les inconvénients:
- Une taille de code décente à mettre en œuvre, mais tout est simple.
Sous plage de test:
Sub DevelopTest()
Dim index As Long
FastWB True
ActiveSheet.UsedRange.Clear
For index = 1 To 1000000 '1 million test
ActiveSheet.Cells(index, 1).Value = index
If (index Mod 10) = 0 Then
ActiveSheet.Cells(index, 2).Value = "Test String"
Else
ActiveSheet.Cells(index, 2).Value = "Blah Blah Blah"
End If
Next index
Application.StatusBar = ""
FastWB False
End Sub
Filtrer et supprimer des lignes Sous:
Sub DeleteRowFast()
Dim curWorksheet As Worksheet 'Current worksheet vairable
Dim rangeSelection As Range 'Selected range
Dim startBadVals As Long 'Start of the unwanted values
Dim endBadVals As Long 'End of the unwanted values
Dim strtTime As Double 'Timer variable
Dim lastRow As Long 'Last Row variable
Dim lastColumn As Long 'Last column variable
Dim indexCell As Range 'Index range start
Dim sortRange As Range 'The range which the sort is applied to
Dim currRow As Range 'Current Row index for the for loop
Dim cell As Range 'Current cell for use in the for loop
On Error GoTo Err
Set rangeSelection = Application.InputBox("Select the (N=) range to be checked", "Get Range", Type:=8) 'Get the desired range from the user
Err.Clear
M1 = MsgBox("This is recommended for large files (50,000 or more entries)", vbYesNo, "Enable Fast Workbook?") 'Prompt the user with an option to enable Fast Workbook, roughly 150% performace gains... Recommended for incredibly large files
Select Case M1
Case vbYes
FastWB True 'Enable fast workbook
Case vbNo
FastWB False 'Disable fast workbook
End Select
strtTime = Timer 'Begin the timer
Set curWorksheet = ActiveSheet
lastRow = CLng(rangeSelection.SpecialCells(xlCellTypeLastCell).Row)
lastColumn = curWorksheet.Cells(1, 16384).End(xlToLeft).Column
Set indexCell = curWorksheet.Cells(1, 1)
On Error Resume Next
If rangeSelection.Rows.Count > 1 Then 'Check if there is anything to do
lastVisRow = rangeSelection.Rows.Count
Set sortRange = curWorksheet.Range(indexCell, curWorksheet.Cells(curWorksheet.Rows(lastRow).Row, 16384).End(xlToLeft)) 'Set the sort range
sortRange.Sort Key1:=rangeSelection.Cells(1, 1), Order1:=xlAscending, Header:=xlNo 'Sort by values, lowest to highest
startBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, MatchCase:=False).Row
endBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, SearchDirection:=xlPrevious, MatchCase:=False).Row
curWorksheet.Range(curWorksheet.Rows(startBadVals), curWorksheet.Rows(endBadVals)).EntireRow.Delete 'Delete uneeded rows, deleteing in continuous range blocks is quick than seperated or individual deletions.
sortRange.Sort Key1:=indexCell, Order1:=xlAscending, Header:=xlNo 'Sort by index instead of values, lowest to highest
End If
Application.StatusBar = "" 'Reset the status bar
FastWB False 'Disable fast workbook
MsgBox CStr(Round(Timer - strtTime, 2)) & "s" 'Display duration of task
Err:
Exit Sub
End Sub
CE CODE UTILISE FastWB
, FastWS
ET EnableWS
PAR Paul Bica!
Temps à 100K entrées (10k à supprimer, FastWB True):
1. 0,2 seconde.
2. 0,2 seconde.
3. 0,21 seconde.
Moy. 0,2 seconde.
Fois à 1 million d'entrées (100k à supprimer, FastWB True):
1. 2,3 secondes.
2. 2,32 secondes.
3. 2,3 secondes.
Moy. 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 pour filtrer les valeurs numériques en dehors d'une plage numérique et a été adapté pour filtrer "Test String"
afin qu'une partie du code soit redondante.
Votre utilisation des tableaux pour calculer la plage utilisée et le nombre de lignes peut affecter les performances. Voici une autre approche qui, lors des tests, s’avère efficace sur plusieurs lignes de données, entre 25 et 30 secondes. Il n'utilise pas de filtres, donc supprime les lignes même si elles sont masquées. La suppression d'une ligne entière n'aura pas d'effet sur le formatage ni sur la largeur des colonnes des autres lignes.
Premièrement, vérifiez si ActiveSheet a "Test String". Puisque vous êtes seulement intéressé par la colonne 1, j'ai utilisé ceci:
TCount = Application.WorksheetFunction.CountIf(sht.Columns(1), "Test String")
If TCount > 0 Then
Au lieu d'utiliser votre fonction GetMaxCell (), j'ai simplement utilisé Cells.SpecialCells(xlCellTypeLastCell).Row
pour obtenir la dernière ligne:
EndRow = sht.Cells.SpecialCells(xlCellTypeLastCell).Row
Puis parcourez les lignes de données:
While r <= EndRow
Pour vérifier si la cellule de la colonne 1 est égale à "Chaîne de test":
If sht.Cells(r, 1).Text) = "Test String" Then
Pour supprimer la ligne:
Rows(r).Delete Shift:=xlUp
Rassembler tout le code complet ci-dessous. J'ai défini ActiveSheet sur une variable Sht et ajouté l'activation de ScreenUpdating pour améliorer l'efficacité. Puisqu'il y a beaucoup de données, je m'assure de supprimer les variables à la fin.
Sub RowDeleter()
Dim sht As Worksheet
Dim r As Long
Dim EndRow As Long
Dim TCount As Long
Dim s As Date
Dim e As Date
Application.ScreenUpdating = True
r = 2 'Initialise row number
s = Now 'Start Time
Set sht = ActiveSheet
EndRow = sht.Cells.SpecialCells(xlCellTypeLastCell).Row
'Check if "Test String" is found in Column 1
TCount = Application.WorksheetFunction.CountIf(sht.Columns(1), "Test String")
If TCount > 0 Then
'loop through to the End row
While r <= EndRow
If InStr(sht.Cells(r, 1).Text, "Test String") > 0 Then
sht.Rows(r).Delete Shift:=xlUp
r = r - 1
End If
r = r + 1
Wend
End If
e = Now 'End Time
D = (Hour(e) * 360 + Minute(e) * 60 + Second(e)) - (Hour(s) * 360 + Minute(s) * 60 + Second(s))
Application.ScreenUpdating = True
DurationTime = TimeSerial(0, 0, D)
MsgBox Format(DurationTime, "hh:mm:ss")
End Sub