Monthly Archives: September 2016

Gegevens ophalen met ADO

ADO betekent “ActiveX Data Objects“. Kun je verder vergeten. Wat kun je er mee? Gegevens ophalen uit een ander (Excel)bestand. Dat andere bestand fungeert dan als een soort database. Wat mij betreft kun je net zo goed het bestand openen en de gegevens die je nodig hebt filteren en vervolgens kopiëren om er verder mee te werken.

Excel bestand is tabellen.xlsx en de data staat op het blad shippers. Zie onderstaand screenshot. Dat bestand kun je zelf aanmaken en dan openen in Excel om het te kunnen lezen.

Wie persé die voorziening wil gebruiken hier twee voorbeelden.
Onderstaande code kopieren en plakken in blanco werkmap. Zorg dat de blanco werkmap op de voorgrond staat en dan de code laten uitvoeren.

VOORBEELD 1

Option Explicit

'***********************************************
'Set reference to:
'Microsoft ActiveX Data Objects Library
'Via: Alt+F11 | Extra | References
'***********************************************

Sub Voorbeeld_ADO_Verbinding_Voorbeeld_1()
    Dim objVerbinding As New ADODB.Connection
    Dim objGegevensSet As New ADODB.Recordset
    Dim strPad As String, strVerbind As String, strSQL As String
    
    'Path to file
    strPad = "C:\temp\tabellen.xlsx"
    
    strVerbind = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & strPad & ";HDR=Yes';"
    
    objVerbinding.Open strVerbind
    
    'Tablename is de sheetname in this example [shippers$]
    'Dollarsign is mandatory behind shippers.
    strSQL = "SELECT * From [shippers$]"
        
    objGegevensSet.Open strSQL, objVerbinding
    
    'Paste data in activesheet
    ActiveSheet.Range("A2").CopyFromRecordset objGegevensSet
    
    'Close dataset
    objGegevensSet.Close
    
    'Close connection
    objVerbinding.Close
End Sub

VOORBEELD 2

Option Explicit

'***********************************************
'Set reference to:
'Microsoft ActiveX Data Objects Library
'Via: Alt+F11 | Extra | References
'***********************************************

Sub Voorbeeld_ADO_Verbinding_Voorbeeld_2()
    Dim arrMatrix As Variant
    Dim objVerbinding As New ADODB.Connection
    Dim objGegevensSet As New ADODB.Recordset
    Dim strPad As String, strVerbind As String, strSQL As String
    Dim i As Long, j As Long
    
    'Path to file
    strPad = "C:\temp\tabellen.xlsx"
    
    strVerbind = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & strPad & ";HDR=Yes';"
    
    objVerbinding.Open strVerbind
    
    'Tablename is de sheetname in this example [shippers$]
    'Dollarsign is mandatory behind shippers.
    strSQL = "SELECT * From [shippers$]"
        
    objGegevensSet.Open strSQL, objVerbinding
    
    'Load data in Array
    arrMatrix = objGegevensSet.GetRows
    
    'Paste data from array to activesheet
    For i = 0 To UBound(arrMatrix, 2) 'Rows
        For j = 0 To UBound(arrMatrix, 1) 'Columns
            ActiveSheet.Cells(i + 2, j + 1).Value = arrMatrix(j, i)
        Next j
    Next i
    
    'Close dataset
    objGegevensSet.Close
    
    'Close connection
    objVerbinding.Close
End Sub

Unieke waarden tonen, 1 criterium, totaal

Verkopers hebben goed hun best gedaan en van alles verkocht en geld verdiend. Nu wil je de totalen hebben van die verkopers maar slechts als ze voldoen aan een bepaald criterium. Bijvoorbeeld: Periode = 3.

In de kolom Verkopers, Kolom B, staan de namen. Er staan echter dubbele waarden in en je wilt de naam van de verkoper slechts één keer weergeven en daarachter het totaal van periode = 3. Kijk naar de opzet van het werkblad. Resultaten weergegeven vanaf E5.

FORMULES:

In [F2] voer je handmatig de periode in.

Invoeren met Ctrl+Shift+Enter, NIET met Enter.
[F3] =SUM(IF(FREQUENCY(IF(Periode=$F$2;IF(Verkoper<>””;MATCH(Verkoper;Verkoper;0)));intRij);1))

Invoeren met Ctrl+Shift+Enter, NIET met Enter
[E5] =IF(ROWS($E$5:E5<=$F$3;INDEX(Verkoper;SMALL(IF(FREQUENCY(IF(Periode=$F$2;IF(Verkoper<>””;
MATCH(Verkoper;Verkoper;0)));intRij);intRij);ROWS($E$5:E5)));””)

Invoeren met Enter
[F5]=IF(E5<>””;COUNTIFS(Periode;$F$2;Verkoper;E5);””)

Invoeren met Enter
[G5]=IF($E5=””;””;SUMIFS(Bedrag;Verkoper;$E5;Periode;$F$2))

Namen geven: Ga naar Formules | in de groep Gedefinieerde namen |  Namen beheren

Periode
=Sheet1!$A$2:$A$40

Verkoper
=Sheet1!$B$2:$B$40

Bedrag
=Sheet1!$C$2:$C$40

intRij
=ROW(Verkoper)-ROW(INDEX(Verkoper;1;1))+1

Twee lijsten vergelijken

Je hebt een controlelijst met Id, Naam en Email. Nu krijg je een nieuwe lijst met BIJNA dezelfde gegevens echter het Id ontbreekt. Als Naam en Email hetzelfde zijn kun je het Id zoeken in de controle lijst. De truc is om de Naam en het Email samen te voegen met het & teken en vervolgens te vergelijken met de Naam en het Email in de andere lijst (die je natuurlijk ook samenvoegt).

Formule invoeren met Ctrl+Shift+Enter. Niet alleen Enter.

[E2]=IFERROR(INDEX($A$2:$A$22;MATCH(F2&G2;$B$2:$B$22&$C$2:$C$22;0));””)

Kopiëren naar beneden.

SOMPRODUCT met criterium

Je hebt een aantal artikelen die besteld worden door verschillende winkeliers. Je wil weten hoeveel de verkoop van 1 bepaald produkt heeft opgebracht. In dit voorbeeld: Tofu.

Formule in E4;
=SOMPRODUCT(–(A2:A23=E2);B2:B23:C2:C23)

Deze formule vermenigvuldigt kolom A met kolom B met kolom C en het resultaat optelt.

In feite krijg je dus:

Rij 5: 1 * € 18,60 * 9 = € 167,40
Rij 8: 1 * € 18,60 * 35 = € 651,00
Rij 14:1 * € 18,60 * 25 = € 465,00
Rij 21: 1 * € 18,60 * 21 = € 390,60
Totaal:                           € 1674,00

Maar hoe krijg je het voor elkaar dat alleen de rijen met “Tofu” worden berekend en wat doet die 1 eigenlijk? Daarvoor zorgt dit gedeelte:
– –(A2:A23=E2)
In kolom A wordt gekeken welk produkt voldoet aan het criterium in cel E2 (Tofu). Normaliter krijg je dan een reeks van FALSE en/of TRUE. De twee minnen (– –) aan het begin zorgen er echter voor dat als Tofu gevonden wordt er een 1 (i. p. v. TRUE) wordt gegenereerd. Zoniet dan wordt een 0 (i.p.v. FALSE) gegenereerd.

Optellen alle aankopen van één klant

Klant BERGS heeft diverse producten gekocht. We willen het totaal berekenen door van al zijn gekochte producten het subtotaal (Kolom E) op te tellen.

Formule in G5.
=SUMPRODUCT(($A$2:$A$10=$G$2)*($E$2:$E$10))

In G2 kun je een validatielijst maken met alle namen van de klanten.

Gegevens | Gegevensvalidatie | Gegevensvalidatie | Toestaan > Lijst | Bron > (type in het vak ->) ALFKI;BERGS;FAMIA
Let op de puntkomma tussen de klantnamen.

Unieke lijst maken en één product uitsluiten

Je hebt een lijst waarin dubbele waarden voorkomen. Je wilt een lijst maken met unieke waarden maar één waarde wil je uitsluiten/negeren.

Formule C7
=IFERROR(INDEX($A$7:$A$28;SMALL(IF(FREQUENCY(IF($A$7:$A$28<>””;IF(1-ISNUMBER(SEARCH($B$4;$A$7:$A$28));MATCH($A$7:$A$28;$A$7:$A$28;0)));ROW($A$7:$A$28)-ROW($A$7)+1);ROW($A$7:$A$28)-ROW($A$7)+1);ROWS(C$7:C7)));””)

Let op: Invoeren met: Ctrl+Shift+Enter

Formule B3
=SUM(IF(FREQUENCY(IF($A$7:$A$28<>””;IF(1-ISNUMBER(SEARCH($B$4;$A$7:$A$28));MATCH($A$7:$A$28;$A$7:$A$28;0)));ROW($A$7:$A$28)-ROW($A$7)+1);1))

Let op: Invoeren met: Ctrl+Shift+Enter

2 lijsten vergelijken

Je kent dat wel. Je hebt twee lijsten die gegevens bevatten. Nu wil je checken of de items in Lijst_2 voorkomen in Lijst_1. Als het lange lijsten zijn is dat een hels karwei. Bijvoorbeeld, komt “Drachenblut Delikatessen” voor in Lijst_1? Ja (TRUE). Komt “QUICK-Stop” voor in Lijst_1? Nee (FALSE).

Formule die je daarvoor kan gebruiken is simpel:
D1 =ISNUMBER(MATCH($B2;$A$2:$A$11;0))
Doorvoeren naar beneden.

Wil je weten of een item NIET in Lijst_1 voorkomt dan gebruik je de formule:
E1 =ISNA(MATCH($B2;$A$2:$A$11;0))
Doorvoeren naar beneden.

Scrape complete webpagina

Nog een voorbeeld van ‘webscraping’ om de online voorpagina van The Guardian van het web te halen. Het resultaat wordt geschreven naar het bestand:
C:\temp\textfile.html
Zorg dat je de map C:\temp maakt. Het bestand textfile.html wordt automatisch gemaakt als het nog niet aanwezig/gemaakt is.
Webscraping is het geautomatiseerd gegevens verzamelen van webpagina’s

Option Explicit

'Tools->References Microsoft HTML Object Library

'MSDN - URLDownloadToFile function - https://msdn.microsoft.com/en-us/library/ms775123(v=vs.85).aspx
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
        (ByVal pCaller As Long, ByVal szURL As String, ByVal szFileName As String, _
        ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long

Sub scrape_complete_webpage()

    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")

    Dim sLocalFilename As String
    sLocalFilename = Environ$("TMP") & "\urlmon.html"
    
    Dim sURL As String
    
    sURL = "https://www.theguardian.com/europe"
    'sURL = "https://www.theguardian.com/artanddesign/gallery/2015/sep/25/inside-un-headquarters-in-pictures"
    Dim bOk As Boolean
    bOk = (URLDownloadToFile(0, sURL, sLocalFilename, 0, 0) = 0)
    If bOk Then
        If fso.FileExists(sLocalFilename) Then
        
            'Tools->References Microsoft HTML Object Library
            Dim oHtml4 As MSHTML.IHTMLDocument4
            Set oHtml4 = New MSHTML.HTMLDocument
            
            Dim oHtml As MSHTML.HTMLDocument
            Set oHtml = Nothing
            
            'IHTMLDocument4.createDocumentFromUrl
            'MSDN - IHTMLDocument4 createDocumentFromUrl method - https://msdn.microsoft.com/en-us/library/aa752523(v=vs.85).aspx
            Set oHtml = oHtml4.createDocumentFromUrl(sLocalFilename, "")
            
            'need to wait a little whilst the document parses
            'because it is multithreaded
            While oHtml.readyState <> "complete"
                DoEvents  'do not comment this out it is required to break into the code if in infinite loop
            Wend
            Debug.Assert oHtml.readyState = "complete"
            

            Dim sTest As String
            sTest = Left$(oHtml.body.outerHTML, 100)
            Debug.Assert Len(Trim(sTest)) > 50  'just testing we got a substantial block of text, feel free to delete
            
            ' Get elements by class name
            Dim elems, i
            Set elems = oHtml.getElementsByClassName("top-fronts-banner-ad-container dcr-12mgsnl")
            
            ' Loop through elements and remove them
            For i = elems.Length - 1 To 0 Step -1
                elems.Item(i).ParentNode.RemoveChild elems.Item(i)
            Next i
            
            LogInformation (oHtml.body.outerHTML)
        End If
    End If
End Sub

Sub LogInformation(LogMessage As String)
Dim fileNum As Integer, x1 As String, x2 As String
Const LogFileName As String = "C:\temp\textfile.html"
    
    Open "C:\temp\textfile.html" For Output As #1: Close #1
    'MsgBox "Clear complete"

    fileNum = FreeFile ' next file number
    Open LogFileName For Append As #fileNum ' creates the file if it doesn't exist
    
    'remove everything between <header data-component="header"> and </header> including these two tags
    x1 = Split(LogMessage, "<div id=""bannerandheader"" data-print-layout=""hide"">")(0) ' the text before <div id="bannerandheader" data-print-layout="hide">
    x2 = Split(LogMessage, "</header>")(1) ' the text after </header>
    LogMessage = x1 & x2

    LogMessage = Replace(LogMessage, Range("A5"), Range("A6"))
    Print #fileNum, LogMessage ' write information at the end of the text file
    Close #fileNum ' close the file
End Sub

Scrape game data van website

Er zijn veel mogelijkheden om gegevens van een website te halen (ook wel webscraping genoemd). Dit is een van de voorbeelden.

Sub ScrapeGameData()
'set reference to the Microsoft HTML Object Library
    Dim ie As Object ' Internet Explorer instance
    Dim doc As Object ' HTML Document
    Dim platformLinks As Object, titles As Object, prices As Object
    Dim i As Integer
    
    ' Create a new Internet Explorer instance
    Set ie = CreateObject("InternetExplorer.Application")
    
    ' Navigate to the webpage
    ie.Visible = False
    ie.navigate "https://www.gameshop.nl/webshop/index.php" ' Change to the correct URL
    
    ' Wait for the page to load
    Do While ie.Busy Or ie.readyState <> 4
        DoEvents
    Loop
    
    ' Get the document
    Set doc = ie.document
    
    ' Get elements by class name
    Set platformLinks = doc.getElementsByClassName("platform-link")
    Set titles = doc.getElementsByClassName("titel")
    Set prices = doc.getElementsByClassName("prijs")
    
    ' Output results to Immediate Window (Ctrl+G in VBA editor to view)
    For i = 0 To platformLinks.Length - 1
        Debug.Print "Platform: " & platformLinks.Item(i).innerText
        Debug.Print "Title: " & titles.Item(i).innerText
        Debug.Print "Price: " & prices.Item(i).innerText
        Debug.Print "----------------------------"
    Next i

    ' Clean up
    ie.Quit
    Set ie = Nothing
    Set doc = Nothing
End Sub