2016-07-02 9 views
1

Ich schreibe eine Funktion, die einen Spaltenbereich und findet die erste und letzte Zelle in dieser Spalte, die einen bestimmten Wert haben. Dies ergibt eine erste Zeilennummer und eine letzte Zeilennummer, die dann verwendet werden, um den entsprechenden Unterbereich in einer anderen Spalte zurückzugeben.
Die Idee ist, dass ich mit dieser Funktion Excel-Funktionen auf einen (kontinuierlichen) Teilbereich eines Bereichs anwenden kann. Z.B. Angenommen, ich habe einen Tisch mit verschiedenen Preisen für Äpfel und Bananen, so gruppiert, dass alle Preise von Äpfeln zuerst kommen, dann Bananen. Ich möchte den Mindestpreis von Äpfeln und das Minimum von Bananen finden, aber den gesamten Bereich auswählen und ohne den Bereich zu ändern, über den minimiert werden soll. Ich würde meine gewünschte Funktion verwenden, um einen Bereich an Excel MIN-Funktion zu füttern, die nur Äpfel oder nur Bananen enthielt, ohne diese Teilbereiche manuell auswählen zu müssen. Ein MINIF, wenn Sie wollen - wie eine schwache Version von SUMMIF, aber für MIN (und möglicherweise viele andere Funktionen).
Ich habe einen Weg gefunden, es zu tun, aber es läuft wirklich ziemlich langsam. Ich denke, dass es mit der for-Schleife zu tun hat, aber ich verstehe nicht genug über die Effizienz in Excel/VBA, um zu wissen, wie man es verbessert. Ich verwende diesen Code in einer Excel-Tabelle, daher sind die Spalten, die ich übergebe, Spalten eines Tabellenobjekts. Ich verwende Excel 2010 unter Windows 7 Enterprise.Excel UDF zum ersten und letzten Zelle in Bereich mit einem bestimmten Wert finden - läuft langsam

Dankbar für jede Hilfe. Auch Lösungen zur bedingten Anwendung von Funktionen auf Bereiche, die davon stark abweichen, werden gut aufgenommen.

Code:

' ParentRange and CriterionRange are columns of the same table. 
'I want to extract a reference to the part of ParentRange which corresponds 
'by rows to the part of CriterionRange that contains cells with a certain value. 
Function CorrespondingSubrange(CriterionRange As Range, Criterion As _ 
String, ParentRange As Range) As Range 

Application.ScreenUpdating = False 

Dim RowCounter As Integer 
Dim SubRangeFirstRow As Integer 
Dim SubRangeFirstCell As Range 
Dim SubRangeLastRow As Integer 
Dim SubRangeLastCell As Range 
Dim RangeCountStarted As Boolean 

RangeCountStarted = False 

Set SubRangeFirstCell = CriterionRange.Find(What:=Criterion, LookIn:=xlValues, _ 
     LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _ 
     MatchCase:=False, SearchFormat:=False) 

If Not (SubRangeFirstCell Is Nothing) Then 
    RangeCountStarted = True 
    SubRangeFirstRow = SubRangeFirstCell.Row - CriterionRange.Range("A1").Row + 1 

    For RowCounter = SubRangeFirstRow To CriterionRange.Cells.Count 

     If Not (CriterionRange.Cells(RowCounter, 1).Value = Criterion) Then 
      SubRangeLastRow = RowCounter - 1 
      Exit For 
     End If 

    Next 

End If 

If RangeCountStarted = True And SubRangeLastRow = 0 Then SubRangeLastRow = RowCounter 

Set CorrespondingSubrange = ParentRange.Range("A" & SubRangeFirstRow & ":A" & SubRangeLastRow) 

Application.ScreenUpdating = True 
End Function 
+0

Welche Version von Excel? Office 365/xl2016 hat eine native [MINIFS-Funktion] (https://support.office.com/de-de/article/MINIFS-function-6ca1ddaa-079b-4e74-80cc-72eef32e6599). [AGGREGATE-Funktion] (https://support.office.com/de-de/article/AGGREGATE-function-43B9278E-6AA7-4F17-92B6-E19993FA26DF) (xl2010 +) kann auch für einen einfachen Pseudo-MINIF verwendet werden. Es ist unwahrscheinlich, dass Sie diese nativen Funktionen sogar in strukturierten [ListObject] (https://msdn.microsoft.com/en-us/library/office/aa174247.aspx) -Tabellen übertreffen, aber Sie könnten Ihren Code mit dem * nach verbessern : = * und * SearchDirection: = * Parameter (keine Schleife). – Jeeped

+0

Wenn dieser Code bereits funktioniert, könnte er ein guter Kandidat für http://codereview.stackexchange.com sein. Sie helfen dabei, den Arbeitscode zu verbessern. – TheEngineer

+0

@Jeeped Leider Excel 2010. Gut zu wissen, dass, wenn mein Arbeitgeber auf Excel 2016 in 20 Jahren aufrüstet, wir es native haben werden ... – alfymbohm

Antwort

3

Ich mag es nicht mit VBA, wenn eine Excel-Formel effizient genutzt werden kann.

Zunächst einmal können Sie ein Minimum oder Maximum erhalten Bedingungen nach einer einfachen IF in einer Matrixformel mit (geben Sie die Formel Strg + Umschalt + ein. Dadurch wird die umgebenden {}, die ein angeben hinzufügen Matrixformel):

=MIN(IF($A$1:$A$10=D1,$B$1:$B$10)) 

diese Formel prüft in A für den Zustand in D1 und gibt den entsprechenden Wert von B. Beachten Sie, dass Ihre Daten brauchen nicht einmal bestellt werden für diese Formel zu arbeiten:

Zweitens, wenn Sie die ersten und letzten Zeilennummern erhalten möchten, können Sie diese Formel mit einem kleinen Zusatz verwenden. Allerdings, ich vermute, dass man die INDIRECT oder OFFSET Funktionen mit diesen Werten verwenden würde, was unnötig und ineffizient ist, da diese Funktionen flüchtig sind. Unabhängig davon ist die Addition zur Formel die ROW Funktion. (Diese Formel benötigt natürlich die Daten, die bestellt werden müssen). Array Formel für Zeilennummern:

=MAX(IF($A$1:$A$10=D1,ROW($A$1:$A$10))) 

Dies wird die letzte Zeilennummer für Bananen zurück.

+0

Großer Zeiger und Illustration der Kraft von Arrays, danke. Gewinnt den Tag, obwohl ich nicht verstehe, was du andeutest, wenn du sagst, dass du denkst, ich benutze 'INDIREKT' oder 'OFFSET'. – alfymbohm

+0

Vielleicht war meine Formulierung aus. Ich hatte Angst, dass die Verwendung einer Formel, die Zeilenindizes zurückgibt, eine volatile Funktion hat, die nicht empfohlen wird. – dePatinkin

1

Indem Sie SearchDirection auf xlPrevious einstellen, können Sie ganz einfach das letzte Vorkommen in einem Bereich finden.
Das Umschalten von Application.ScreenUpdating hat wenig Auswirkungen, wenn Sie nur Werte lesen. Ich bevorzuge kürzere Variablennamen. Längere Namen neigen dazu, den Bildschirm zu überladen und es schwieriger zu sehen, was vor sich geht. Aber das ist nur meine Meinung.

Function CorrespondingSubrange(rCriterion As Range, Criterion As _ 
                 String, rParent As Range) As Range 
    Dim FirstCell As Range 
    Dim LastCell As Range 

    Set FirstCell = rCriterion.Find(What:=Criterion, LookIn:=xlValues, _ 
               LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _ 
               MatchCase:=False, SearchFormat:=False) 
    Set LastCell = rCriterion.Find(What:=Criterion, LookIn:=xlValues, _ 
               LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, _ 
               MatchCase:=False, SearchFormat:=False) 


    If Not (FirstCell Is Nothing) Then 
     Set CorrespondingSubrange = rParent.Range("A" & FirstCell.Row & ":A" & LastCell.Row) 
    End If 
End Function 
+0

Sie sollten 'After: =' auf die letzte Zelle für xlNext und auf die erste Zelle für xlPrevious setzen. – Jeeped

+0

Ist das nicht rückwärts? Oder meinen Sie, dass Sie 'xlNext' verwenden können, um das letzte Vorkommen zu finden, indem Sie' After: = 'auf die letzte Zelle setzen? Hat das einen Leistungsvorteil? –

+0

Thomas - siehe die Erklärung, die ich in meiner eigenen Lösung gegeben habe. – Jeeped

1

Meine Antwort ist ähnlich der VBA UDF Lösung zuvor von Thomas Inzina mit ein paar Unterschiede geschrieben.

Der Parameter After:= wird verwendet, um sicherzustellen, dass das erste gefundene Match das erste Match in diesem Bereich ist. Die Range.Find method verwendet eine "Dose-Dose" Ansatz, wo es durch die Zellen der Reichweite Schleifen und startet am Anfang neu, wenn es das Ende erreicht. Wenn Sie After:=.Cells(.Cells.Count) starten und sich vorwärts bewegen, finden Sie die erste Zelle in dem entsprechenden Bereich. Ähnlich, indem Sie bei After:=.Cells(1) beginnen und SearchDirection:=xlPrevious verschieben, finden Sie schnell das Letzte ohne Schleife.

Ich habe auch die Intersect method zu a) verwendet, um vollständige Spaltenreferenzen auf die Worksheet.UsedRange property zu reduzieren und b) um den Arbeitsbereich schnell aus dem festgelegten Kriterienbereich zurückzukehren.

Function CorrespondingSubrange(rngCriterion As Range, Criterion As String, _ 
           rngWorking As Range) As Variant 

    Dim SubRangeFirstCell As Range 
    Dim SubRangeLastCell As Range 

    'set the return value to an #N/A error (success will overwrite this) 
    CorrespondingSubrange = CVErr(xlErrNA) 

    'chop any full column references down to manageable ranges 
    Set rngCriterion = Intersect(rngCriterion, rngCriterion.Parent.UsedRange) 

    With rngCriterion 

     'look forwards for the first occurance 
     Set SubRangeFirstCell = .Find(What:=Criterion, After:=.Cells(.Cells.Count), _ 
       LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByColumns, _ 
       SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False) 

     If Not SubRangeFirstCell Is Nothing Then 

      'there is at least one of the criteria - now look backwards 
      Set SubRangeLastCell = .Find(What:=Criterion, After:=.Cells(1), _ 
        LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByColumns, _ 
        SearchDirection:=xlPrevious, MatchCase:=False, SearchFormat:=False) 

      Set CorrespondingSubrange = Intersect(rngWorking, Range(SubRangeFirstCell, SubRangeLastCell).EntireRow) 

      Debug.Print CorrespondingSubrange.Address(0, 0, external:=True) 

     End If 
    End With 

End Function 

CorrespondingSubrange

+0

Das ist ziemlich cool und überhaupt nicht offensichtlich. Die Verwendung von CVErr und insbesondere der Schnittmenge zur Reduzierung der Bereichsgröße ist sehr cleaver. Ich denke, deshalb bist du ein Microsoft MVP. –

+0

Danke, ich benutze gerne Intersect auf dem UsedRange für die meisten meiner UDFs, so dass vollständige Spaltenreferenzen ohne Nachteil verwendet werden können. Die Schleife im ursprünglichen Code des OP wäre bei vollen Spaltenreferenzen stark abgebremst worden, aber in jedem Fall ist es am besten, eine Schleifenbildung vollständig zu vermeiden. – Jeeped

Verwandte Themen