klinger85
Goto Top

Zwei CSV Dateien verknüpfen

Hallo zusammen,

ich habe das Problem das ich Informationen, sprich Spalten, aus zwei CSV Dateien miteinander verknüpfen möchte.

Zum Beispiel habe ich eine CSV mit den Namen (
ID,Name
1,Huber
2,Meier
3,Schmid
4,Mueller
) und eine zweite mit dem dazugehörigem Ort (
ID,Ortsname
3,Berlin
1,Hamburg
4,Stuttgart
2,Muenchen
). Nun möchte ich anhand der „Spalte“ „ID“ automatisiert folgende Ausgabe zustande bringen:
ID,Name,Ortsname
1,Huber,Hamburg
2,Meier,Muenchen
3,Schmid,Berlin
4,Mueller,Stuttgart

Ich habe es bereits mit der PowerShell, VBSkript und mit dem MS Log Parser versucht, bin jedoch nicht zu einem passenden Ergebnis gekommen.
Das Ergebnis sollte dann wiederrum eine CSV Datei sein.

Schon mal vorab vielen Dank für eure Hilfe...
Kommentar vom Moderator Biber am Apr 27, 2010 um 12:13:48 Uhr
Hier stehen bis heute, 27.4..2010 schon so viele kompetente Antworten...
Deshalb setze ich den Beitrag jetzt auf "Beantwortet", egal ob sich Klinger85 jemals wieder meldet oder nicht.

Content-Key: 141054

Url: https://administrator.de/contentid/141054

Printed on: April 18, 2024 at 22:04 o'clock

Mitglied: 77559
77559 Apr 20, 2010 at 13:08:48 (UTC)
Goto Top
Zitat von @Klinger85:
Ich habe es bereits mit der PowerShell, VBSkript und mit dem MS Log Parser versucht, bin jedoch nicht zu einem passenden Ergebnis
gekommen.

Dann zeig doch mal deine Versuche, es wird sich schon jemand finden der den Finger an die richtige(falsche) Stelle legt face-wink

Gruß
LotPings
Member: Klinger85
Klinger85 Apr 20, 2010 at 13:19:12 (UTC)
Goto Top
Mein Problem liegt darin die korrekten Zeilen zuzuordnen.
Bspw. bin ich im Netz auf der Seite http://poshcode.org/1654 auf ein PowerShellSkript gestossen,
das sich genau richtig anhört.

Jedoch überschreibt das Skript scheinbar immer den Wert, denn als Ergebnis erhalte ich foglendes:
ID,Name,Ortsname
1,Huber,Muenchen
2,Meier,Muenchen
3,Schmid,Muenchen
4,Mueller,Muenchen

Da "Muenchen" der letzte Wert in der zweiten Datei ist...
Member: bastla
bastla Apr 20, 2010 at 14:23:13 (UTC)
Goto Top
Hallo Klinger85 und willkommen im Forum, hallo LotPings!

Um das etwas abzukürzen face-wink: Ich bin in meinem Editor auf folgenden Batch gestoßen:
@echo off & setlocal
set "Namen=D:\Namen.txt"  
set "Orte=D:\Ortsnamen.txt"  
set "Gesamt=D:\Gesamt.txt"  

>"%Gesamt%" echo ID,Name,Ortsname  
for /f "usebackq skip=1 tokens=1,2 delims=," %%i in ("%Namen%") do for /f "tokens=2 delims=," %%a in ('findstr /b /c:"%%i," "%Orte%"') do >>"%Gesamt%" echo %%i,%%j,%%a  
Grüße
bastla
Mitglied: 77559
77559 Apr 20, 2010 at 14:53:10 (UTC)
Goto Top
Hallo bastla,

ja, die Aufgabenstellung kam mir schon bekannt vor face-wink

@Klinger85,
ich hab mir das PowerShell Script mal angesehen, läuft bei mir nicht wegen PosH 2.1
Mein Win7-64 hat nur PoSh 2.0. Wenn ich das ignoriere sieht bei mir das Ergebnis genauso aus.
Das ist aber zu komplex um es mal eben zu untersuchen, vielleicht später einmal.

Gruß
LotPings
Member: bastla
bastla Apr 20, 2010 at 15:05:45 (UTC)
Goto Top
@77559
ja, die Aufgabenstellung kam mir schon bekannt vor face-wink
Na gut, dann eben mal etwas Abwechslung face-wink:
Namen = "D:\Namen.txt"  
Orte = "D:\Ortsnamen.txt"  
Gesamt = "D:\Gesamt.txt"  
Delim = "," 'Trennzeichen  

Set fso = CreateObject("Scripting.FileSystemObject")  
Set d = CreateObject("Scripting.Dictionary") 'Dictionary für die Orte erstellen  

O = Split(fso.OpenTextFile(Orte).ReadAll, vbCrLf) 'gesamte Orte-Datei in Zeilen-Array einlesen ...  
For i = 0 To UBound(O) '... und alle Zeilen durchgehen  
    Felder = Split(O(i), Delim) 'Satz anhand des Trennzeichens aufteilen  
    If Not d.Exists(Felder(0)) Then 'Feld 0 enthält den Schlüssel - noch nicht vorhanden?  
        d.Add Felder(0), Felder(1) '... dann hinzufügen (mit Wert aus Feld 1)  
    Else 'ansonsten Fehlermeldung und Abbruch  
        WScript.Echo "Fehler in """ & Orte & """ / Zeile " & i + 1 & ": Nummer """ & Felder(0) & """ bereits für """ & d.Item(Felder(0)) & """ vorhanden!"  
        WScript.Quit
    End If
Next

N = Split(fso.OpenTextFile(Namen).ReadAll, vbCrLf) 'gesamte Namen-Datei in Zeilen-Array einlesen ...  
For i = 0 To UBound(N) '... und alle Zeilen durchgehen  
    Felder = Split(N(i), Delim) ''Satz anhand des Trennzeichens aufteilen  
    If d.Exists(Felder(0)) Then 'Ort mit dem Schlüssel aus Feld 0 vorhanden?  
        G = G & vbCrLf & N(i) & Delim & d.Item(Felder(0))' ... dann dem Ergebnisstring eine Zeilenschaltung sowie den Namens-Satz und den Ort hinzufügen  
    Else 'ansonsten Fehlermeldung und Abbruch  
        WScript.Echo "Fehler in """ & Namen & """ / Zeile " & i + 1 & ": Kein Ort für Nummer """ & Felder(0) & """ gefunden!"  
        WScript.Quit
    End If
Next

fso.CreateTextFile(Gesamt).Write Mid(G, 3) 'Ergebnisstring ab 3. Position (die ersten beiden Stellen enthalten eine unnötige Zeilenschaltung) in Gesamt-Datei schreiben  
WScript.Echo "Fertig."  
[Edit] Trennzeichen in Variable verfrachtet [/Edit]

Grüße
bastla
Member: Klinger85
Klinger85 Apr 20, 2010 at 16:54:32 (UTC)
Goto Top
Hallo bastla,

dein Batch Ansatz funktioniert einwandfrei... echt super danke!!!

Aus interesse hab ich natürlich auch noch dein vbs getestet,
jedoch kommt bei mir immer ein Laufzeitfehler in Zeile 12 (Index ausserhalb des Bereichs...)
Ich hab auch mal dein "IF" weg gelassen jedoch meckert er dann an Zeile 13 (d.Add Felder(0), Felder(1)) mit dem gleichen Fehler.

Grüße und besten Dank
Stefan
Member: bastla
bastla Apr 20, 2010 at 17:16:13 (UTC)
Goto Top
Hallo Klinger85!

Ich war eigentlich nicht davon ausgegangen, dass es Leerzeilen (oder zumindest Zeilen ohne ein Komma) in der Ortsnamen-Datei gibt ...

... aber dagegen sollte sich ja noch was machen lassen:
Namen = "D:\Namen.txt"  
Orte = "D:\Ortsnamen.txt"  
Gesamt = "D:\Gesamt.txt"  
Delim = "," 'Trennzeichen  

Set fso = CreateObject("Scripting.FileSystemObject")  
Set d = CreateObject("Scripting.Dictionary") 'Dictionary für die Orte erstellen  

o = Split(fso.OpenTextFile(Orte).ReadAll, vbCrLf) 'gesamte Orte-Datei in Zeilen-Array einlesen ...  
For i = 0 To UBound(o) '... und alle Zeilen durchgehen  
    Felder = Split(o(i), Delim) 'Satz anhand des Trennzeichens aufteilen  
    If UBound(Felder) > 0 Then 'nur, wenn der Satz mindestens 2 Felder enthält, verarbeiten ...  
        If Not d.Exists(Felder(0)) Then 'Feld 0 enthält den Schlüssel - noch nicht vorhanden?  
            d.Add Felder(0), Felder(1) '... dann hinzufügen (mit Wert aus Feld 1)  
        Else '... ansonsten Fehlermeldung und Abbruch  
            WScript.Echo "Fehler in """ & Orte & """ / Zeile " & i + 1 & ": Nummer """ & Felder(0) & """ bereits für """ & d.Item(Felder(0)) & """ vorhanden!"  
            WScript.Quit
        End If
    Else
        WScript.Echo "Fehler in """ & Orte & """ / Zeile " & i + 1 & ": Unvollständiger Datensatz!"  
        WScript.Quit
    End If
Next

N = Split(fso.OpenTextFile(Namen).ReadAll, vbCrLf) 'gesamte Namen-Datei in Zeilen-Array einlesen ...  
For i = 0 To UBound(N) '... und alle Zeilen durchgehen  
    Felder = Split(N(i), Delim) 'Satz anhand des Trennzeichens aufteilen  
    If UBound(Felder) > 0 Then 'nur, wenn der Satz mindestens 2 Felder enthält, verarbeiten ...  
        If d.Exists(Felder(0)) Then 'Ort mit dem Schlüssel aus Feld 0 vorhanden?  
            G = G & vbCrLf & N(i) & Delim & d.Item(Felder(0))' ... dann dem Ergebnisstring eine Zeilenschaltung sowie den Namens-Satz und den Ort hinzufügen  
        Else '... ansonsten Fehlermeldung und Abbruch  
            WScript.Echo "Fehler in """ & Namen & """ / Zeile " & i + 1 & ": Kein Ort für Nummer """ & Felder(0) & """ gefunden!"  
            WScript.Quit
        End If
    Else
        WScript.Echo "Fehler in """ & Orte & """ / Zeile " & i + 1 & ": Unvollständiger Datensatz!"  
        WScript.Quit
    End If
Next

fso.CreateTextFile(Gesamt).Write Mid(G, 3) 'Ergebnisstring ab 3. Position (die ersten beiden Stellen enthalten eine unnötige Zeilenschaltung) in Gesamt-Datei schreiben  
WScript.Echo "Fertig."  
[Edit] Bezeichnung des "Orte"-Arrays "O" auf Schreibweise mit Kleinbuchstaben "o" geändert, um Verwechslung mit der Ziffer 0 vorzubeugen [/Edit]

Grüße
bastla
Member: Biber
Biber Apr 20, 2010 at 18:03:12 (UTC)
Goto Top
Moin bastla,

ich habe deine Korrektur gedanklich und schnipselig nachvollzogen.
Ich würde bei einem Einsatz in einem realitätsnäheren Umfeld (=mehr als 5 Zeilen in jeder der Dateien) allerdings eine kleine nervensparende Anpassung nahelegen.

Bei einer Leerzeile (oder kommalosen Zeile) in einer der CSV-Dateien grätscht das Programm professionell und elegant ab mit
>e:\schnipsel\bastlaCsvJoin.vbs
Fehler in "D:\temp\Orte.csv" / Zeile 6: Unvollständiger Datensatz!

Blöd nur, wenn dann jemand die Leerzeile in Zeile 6 wegeditiert und wieder startet.

Dann nämlich
>e:\schnipsel\bastlaCsvJoin.vbs
Fehler in "D:\temp\Orte.csv" / Zeile 6: Unvollständiger Datensatz!

{User editiert, zählt bis drei und startet durch..}
>e:\schnipsel\bastlaCsvJoin.vbs
Fehler in "D:\temp\Orte.csv" / Zeile 445: Unvollständiger Datensatz!

{User editiert, zählt bis dreimaldrei und startet durch..}
>e:\schnipsel\bastlaCsvJoin.vbs
Fehler in "D:\temp\Orte.csv" / Zeile 20045: Unvollständiger Datensatz!

{User editiert, zählt bis dreiundzwanzig und startet durch..}
...

--> Man/frau muss jede Mistzeile einzeln "reparieren", den Schnipsel neu starten und schauen, wie weit er jetzt kommt.

--> deshalb würde ich die Zeilen 21 und 33 ("Wscript.Quit") auskommentieren, alle bisher auch geschriebenen Fehlersätze genauso anmeckern wie bisher und WEITERMACHEN.

Dann habe ich ZWEI Vorteile.
a) eine erzeugte Gesamt.txt mit allen "funktionierenden" Namen x Ort-Zeilen
b) eine unveränderte, nicht manipulierte Nur-Gelesen-CSV-Datei, die ich mitsamt dem Fehlerprotokoll zurück an den Datenlieferanten schicken kann.

Grüße
Biber
Member: bastla
bastla Apr 20, 2010 at 19:36:11 (UTC)
Goto Top
@Biber
Du hast natürlich recht, und es ist keine Frage, dass im Ernstfall die Fehlermeldungen in einer Datei landen würden und es auch nicht zum Abbruch käme ...

Da als Batch eigentlich fast ein Einzeiler genügt, sollte die (von vorneherein umfangreichere) VBS-Variante zumindest andeutungsweise einen Mehrwert bekommen, daher habe ich noch die Kontrollen (eigentlich eher zu Demozwecken) mit eingebaut.

Abgesehen davon wäre ja mit der Ursachenforschung / -behebung ohnehin, wie Du schon angemerkt hast, bei der Entstehung der "csv"-Datei anzusetzen ...

Grüße
bastla
Member: 5t8d1e
5t8d1e Apr 23, 2010 at 08:45:07 (UTC)
Goto Top
Hallo,

kurz und knapp würde ich es in PS so machen
$user1 = import-csv users1.csv
$user2 = import-csv users2.csv
$header = "ID,Name,Ortsname"  

Add-Content -Value $header Ausgabe.csv

foreach ($ID1 in $user1)
{ foreach ($ID2 in  $user2)
    { IF ($ID1.ID -eq $ID2.ID) 
        { Add-Content -Value ($ID1.ID.ToString() + "," + $ID1.Name.ToString() + "," + $ID2.Ortsname.ToString()) Ausgabe.csv  
        }
    }
}

grüße
Torsten
Member: Biber
Biber Apr 23, 2010 at 11:52:53 (UTC)
Goto Top
...wäre natürlich schön, wenn der Fragesteller Klinger85 noch vor dem wohlverdienten Wochenende antwortet:

"Vielen Dank für die ganzen Batch-, VBS- und Powershell-Lösungen.
Aus Zeitmangel konnte ich nicht alles austesten und habe das Problem inzwischen mit dem NotePad gelöst."


Dann könnten wir diesen Beitrag endlich schließen.

Grüße
Biber
Member: 5t8d1e
5t8d1e Apr 23, 2010 at 12:45:13 (UTC)
Goto Top
ahoi

rofl und auf wiedersehen. Happy WE

ach egal.
Biber schöner Kommentar.

bye bye
Mitglied: 77559
77559 Apr 23, 2010 at 13:47:39 (UTC)
Goto Top
Auch allen ein schönes WE,

@Torsten, auch bei PoSh führen viele Wege nach Rom, die geschachtelten Schleifen sind ein in diesem kurzen Beispiel tolerierbares "brute force".
Effizienter wäre ein Hash ArrayTable:
$namen = import-csv .\namen.csv
$orte = @{} 
Import-Csv .\orte.csv | %{$orte.item($_.ID.ToString()) = $_.Ortsname.ToString()}
$header = "ID,Name,Ortsname"

Add-Content -Value $header Ausgabe.csv

foreach ($name in $namen)
{ Add-Content -Value ($name.ID.ToString() + "," + $name.Name.ToString() + "," + $orte[$name.ID].ToString()) Ausgabe.csv}

Gruß
Lotpings
Member: 5t8d1e
5t8d1e Apr 27, 2010 at 09:44:54 (UTC)
Goto Top
Hallo LotPings,

danke für das Script (ja es läuft um einiges schneller), ganze Script war auch nur ein Gedankenspiel. Habe das Script so aufgebaut wie ich es mit ausgedruckten Listen machen würde.

Mit HashTables (nicht arrays! face-wink) habe ich noch so meine Verständnissprobleme. Eine Bitte hätte ich noch, wenn man Scripte postet, liest sich ein ForEach-Object besser als ein %. Ja ich kann es lesen, aber andere fragen sich evtl. was das sein soll.


Grüße Torsten.
Mitglied: 77559
77559 Apr 27, 2010 at 11:19:58 (UTC)
Goto Top
Zitat von @5t8d1e:
Eine Bitte hätte ich noch, wenn man Scripte postet, liest sich ein ForEach-Object besser als ein %.
Ja ich kann es lesen, aber andere fragen sich evtl. was das sein soll.
Hallo Torsten,
strenggenommen ist auch foreach nur ein Alias für foreach-object, auch wenn sich der Sinn eher erschließt
alias | where {$_.Definition -eq "foreach-object"} | ft --auto
CommandType Name    Definition
----------- ----    ----------
Alias       %       ForEach-Object
Alias       foreach ForEach-Object

Ansonsten gehört es IMHO zu Lernkurve valide Syntax Elemente zu verstehen face-wink

Gruß
LotPings

PS: Zur Klärung ggfs weiterer auftretender Fragen einfach dies eingeben
"%", "where", "ft" | %{alias $_}
oder gleich dies:
"%", "where", "ft" | %{alias $_} | %{get-help $_.Definition} | more