Forum | | | | - Seite 1 - |
| Christof Neuß | Hallo,
ich hexe mal wieder mit großen CSV-Dateien herum. Will diese möglichst schnell und einfach in eine SQLite-Datenbank überführen. Das funktioniert auch grundsätzlich, aber es ist mir an einigen Stellen noch zu langsam.
2 Alternativen verfolge ich gerade a) Einlesen der CSV Satz für Satz, Anpassung an Insert-Befehl und einlesen b) Aufbau einer passenden CSV-Datei für den Import-Befehl der SQLite-Konsole und dann über Batchdatei starten.
Variante a) ist hier (erstmal) nicht maßgeblich, da Satz-Input eh eher zu langsam ist.
Bei Variante b) bekomme ich es einfach nicht hin, die SQLite-Konsole mit einem Parameter zu versehen, der TAB als Feldtrenner setzt (\t funktioniert nicht), daher möchte ich die Datei (bis 1 GB groß) vorher vom TAB befreien und z.B. ";" als Feldtrenner setzen. Ich lese dazu über Blockread immer 1.000.000 Bytes ein, bearbeite diese und schreibe sie in eine neue Datei. Experimentiert habe ich mit Translate$ und Translate als nproc. Beide brauchen bei mir ca. 30 Sekunden, bis sie 1 MB geändert haben. Ich habe dann die nproc etwas abgeändert, da ich nur ein Zeichen tauschen will
Braucht aber fast genauso lange.
Sieht einer eine Möglichkeit, dass noch - entscheidend - zu beschleunigen?
Es ist (auch) deshalb nicht ganz einfach, die CSV-Dateien in eine SQLite-DB zu übertragen, weil ich nicht immer sicher vorher weiß, mit welchem Zeichen die Felder getrennt sind und ob es sich um eine Datei mit einfachen oder WideChar handelt. Beide kriege ich mittlerweile aber relativ schnell und sicher raus.
Hier der komplette Code des Moduls:
nProc translateONE(string src,fnd,rpl)
{
long p=1,cc=len(src), akt=0
case cc<1 : return src
case src=="" : return ""
case fnd=="" : return src
long fndCH=fnd[1]
string sret=""', akt=""
while 1
{
case p > cc : break
if src[p]<>fndCH
{
akt=src[p]
sret=sret+chr$(akt)
} else
{
sret=sret+rpl
}
add p,1
continue
}
return sret
}
Proc CSV_in_SQLite_2'hier wird mit einem Batchfile gearbeitet
'Dateiname, der eingelesen werden soll
'Bezeichnung der Tabelle in SQLite
'0=keine Meldung, 1=MessageBox am Schluss
Parameters CSVDateiname$, EinleseTabelle$, MsgBox&
Var TempErgebnis$="Temp_CSV_f_SQLite.CSV"
Var IstWideChar&=0
Var EinleseDaten$=""
Declare EinleseDaten#
Var EinleseBytes&=1000000'Hier kann eingestellt werden, wieviele Bytes beim Blockread jedes Mal eingelesen werden
Var Eingelesen&=0
Var EinleseSatz$=""
Var EinleseFelder$=""
Var AnzEinleseFelder&=0
Var FeldTrenner$=""
Var SQLiteDLL&=db("slUseDLL","SQLite3.DLL")
Var dbODVAss&=db("slInit","ODVAss.DB")
Var SQLBefehl$=""
Var x&=0
Var XX&=Fortschritt()
Var Zaehl&=0
Erase TempErgebnis$
Assign #1, CSVDateiname$ : OpenRW #1
Assign #2, TempErgebnis$ : OpenRW #2
'Umwandlung in Datei ohne WideChar...
Dim EinleseDaten#, EinleseBytes&
Eingelesen&=Blockread(#1,EinleseDaten#,0,EinleseBytes&)
if Char$(EinleseDaten#,0,1)=Chr$(255)'Datei mit WideChar
IstWideChar&=1
endif
if IstWideChar&=1
EinleseDaten$=EinleseDaten$+WideChar(EinleseDaten#,0,Eingelesen&)
else
'normaler Zeichensatz
EinleseDaten$=EinleseDaten$+Char$(EinleseDaten#,0,Eingelesen&)
endif
EinleseFelder$=Left$(EinleseDaten$,InStr(Chr$(10),EinleseDaten$)-1)'Damit haben wir die Überschriften
EinleseFelder$=Translate$(EinleseFelder$,Chr$(9),"¦")
'Einlesedaten$ bleibt einfach so. Dann sind die Überschriften in der ersten Zeile
'EinleseDaten$ =Mid$(EinleseDaten$,InStr(Chr$(10),EinleseDaten$)+1,InStr(Chr$(0),EinleseDaten$)-Len(EinleseFelder$)-2) 'Damit sind die Überschriften raus
if Instr(Chr$(0),EinleseDaten$)<>0
EinleseDaten$=Left$(EinleseDaten$,Instr(Chr$(0),EinleseDaten$)-1)
endif
Zaehl&=Zaehl&+Len(EinleseDaten$,Chr$(10))
Print "Anfang"
EinleseDaten$=TranslateONE(EinleseDaten$,Chr$(9),"¦")
Print "Ende erster Satz"
Blockwrite #2, EinleseDaten$
WhileLoop 10000'Der Wert wird wohl nie erreicht werden
SetText XX&, format$("###,###,###",&Loop)
Dispose EinleseDaten#
EinleseDaten$=""
Dim EinleseDaten#, EinleseBytes&
Eingelesen&=Blockread(#1,EinleseDaten#,0,EinleseBytes&)
if IstWideChar&=1
EinleseDaten$=WideChar(EinleseDaten#,0,Eingelesen&)
else
'normaler Zeichensatz
EinleseDaten$=Char$(EinleseDaten#,0,Eingelesen&)
endif
if Instr(Chr$(0),EinleseDaten$)<>0
EinleseDaten$=Left$(EinleseDaten$,Instr(Chr$(0),EinleseDaten$)-1)
endif
Print "Anfang "+Str$(&Loop)
EinleseDaten$=TranslateONE(EinleseDaten$,Chr$(9),"¦")
Print "Ende "+Str$(&Loop)
Zaehl&=Zaehl&+Len(EinleseDaten$,Chr$(10))
Blockwrite #2, EinleseDaten$
if eof(#1)
Break
endif
EndWhile
Close #1
Close #2
'Erstellen Table in dbODVAss...
'Datenbanktabelle erstellen/bereinigen
if db("slTableExists",dbODVAss&,EinleseTabelle$)<>0'Tabelle existiert, dann löschen
SQLBefehl$="Drop Table "+EinleseTabelle$
db("slSQLExec",dbODVAss&, SQLBefehl$,1)
endif
'Felder in Tabelle Tab_Felder eintragen bzw. vorher vorhandene löschen
if db("slTableExists",dbODVAss&,"Tab_Felder")<>0'Tabelle existiert
SQLBefehl$="Delete from Tab_Felder where TabName='"+EinleseTabelle$+"'"
db("slSQLExec",dbODVAss&, SQLBefehl$,1)
else
SQLBefehl$="Create Table Tab_Felder (TabName, FeldName, FeldBezei, FeldArt)"
db("slSQLExec",dbODVAss&, SQLBefehl$,1)
endif
'Feldtrenner in Originaldatei bestimmen
if Len(EinleseFelder$,"¦")>Len(EinleseFelder$,";")
FeldTrenner$="¦"
else
FeldTrenner$=";"
endif
if Len(EinleseFelder$,",")>Len(EinleseFelder$,FeldTrenner$)
FeldTrenner$=","
endif
'Felder aus der aktuellen Tabelle eintragen ==> Feldtrenner ist dann ein Komma
EinleseFelder$=TranslateONE(EinleseFelder$,Chr$(9),";")
EinleseFelder$=TranslateONE(EinleseFelder$,",",".")
EinleseFelder$=TranslateONE(EinleseFelder$,";",",")
EinleseFelder$=Translate$(EinleseFelder$,",,,",",0,0,")
EinleseFelder$=Translate$(EinleseFelder$,",,",",0,")
EinleseFelder$=Translate$(EinleseFelder$,", ,",",0,")
EinleseFelder$=TranslateONE(EinleseFelder$,":","")
AnzEinleseFelder&=Len(EinleseFelder$,",")
WhileLoop AnzEinleseFelder&
SQLBefehl$="Insert into Tab_Felder (TabName, FeldName, FeldBezei) Values ('"+EinleseTabelle$+"','F"+Format$("0000",&Loop)+"','"+Substr$(EinleseFelder$,&Loop,",")+"')"
db("slSQLExec",dbODVAss&, SQLBefehl$,1)
EndWhile
EinleseFelder$="("
WhileLoop AnzEinleseFelder&'Danach sind in EinleseFelder$ die (neuen) F...-Bezeichnungen
EinleseFelder$=EinleseFelder$+"F"+Format$("0000",&Loop)+","
EndWhile
EinleseFelder$=left$(EinleseFelder$,Len(EinleseFelder$)-1)+")"
SQLBefehl$="Create Table "+EinleseTabelle$+" "+EinleseFelder$
db("slSQLExec",dbODVAss&, SQLBefehl$,1)
db("slDone",dbODVAss&)
FreeDll SQLiteDLL&
'Einlesen Datensätze in dbODVAss...
'SL-Datei erstellen
Erase "SQLite_Import.sl"
Assign #1, "SQLite_Import.sl" : Rewrite #1
Print #1, "Delete from "+EinleseTabelle$+";"
'Print #1, ".separator "+FeldTrenner$
'Print #1, ".mode csv"
Print #1, ".import Temp_CSV_f_SQLite.CSV "+EinleseTabelle$
Close #1
'Batchdatei erstellen
Erase "SQLite_Import.bat"
if FeldTrenner$=Chr$(9)
FeldTrenner$=Chr$(34)+"\\t"+Chr$(34)
endif
Assign #1, "SQLite_Import.bat" : Rewrite #1
Print #1, "@echo off"
Print #1, "SQLite3.exe -separator "+FeldTrenner$+" ODVAss.DB < SQLite_Import.SL"
'Print #1, "Pause"
Close #1
ChDir $ProgDir
SQLBefehl$="SQLite_Import.bat"
WinExecWait(SQLBefehl$,1)
DestroyWindow(WegwerfFenster&)
Erase TempErgebnis$
if MsgBox&=1
MessageBox("Daten wurden in die Datenbank geladen!","Meldung",0)
endif
Endproc
Merci und viele Grüße
Christof |
| | | | |
| | | | | - Seite 1 - |
| H.Brill | 3 Mio. Zeilen ist ne Menge Ich glaube, so 260.000 ist das höchste, was die Listboxliste aufnehmen kann. Man könnte sich aber behelfen, indem man den index (2.Parameter) überprüft. Wenn dieser z.B. 250.000 erreicht, einfach die Liste löschen. Um das etappenweise zu bewerkstelligen, muß man halt mit index MOD 250.000 überprüfen. Müßte man mal probieren. |
| | | Benutze XPROFAN X3 + FREEPROFAN Wir sind die XProfaner. Sie werden von uns assimiliert. Widerstand ist zwecklos! Wir werden alle ihre Funktionen und Algorithmen den unseren hinzufügen.
Was die Borg können, können wir schon lange. | 10.09.2018 ▲ |
| |
| | Jörg Sellmeyer | Musst du die Datei denn unbedingt zeilenweise bearbeiten? Wenn du die Daten als CSV vorliegen hast, kannst du die doch einfach in einen String laden und den einmal mit Translate verwursten und schon sollten alle Trenner ersetzt sein. Wenn ich es richtig in Erinnerung habe, ist die maximale Stringlänge durch den Hauptspeicher begrenzt - sollte also dann kein ernsthaftes Problem darstellen. |
| | | | |
| | Christof Neuß | Tja... genau damit laboriere ich ja schon ein paar Tage rum...
- Bei ca. 260.000 Zeilen ist Schluss. Wäre ja auch zu schön gewesen um wahr zu sein. Das mit dem zweiten Parameter habe ich nicht verstanden.
- Erstens kann man 1 GB nicht mal so einfach in einen String einlesen (da kommt eher ein Speicherfehler, als man glaubt) und zweitens habe ich sowas schon versucht... Nach über 2 Stunden Laufzeit habe ich das Programm abgebrochen.
Werde ich wohl noch etwas tüfteln müssen... |
| | | | |
| | H.Brill | Der zweite Parameter ist halt der index. Bei Move("ArrToList", a[]) ist es z.B. der Index des Arrays a[index]. Bei FileToList müßte es dann die jeweilige Zeile der Datei sein.
Das ergibt die Möglichkeit z.B. nur Teile eines Arrays in die Listboxliste zu schieben, indem man den index mit > oder < abfragt.
Die MoveListProc scheint da auch nicht allzu schnell zu sein :
Hier sieht man schön, wie schnell ein MAT geht gegenüber der Proc. Schade, daß bei MAT nur feste Ausdrücke gehen. Übrigens : du brauchst ja bei der MoveListProc die Listboxliste gar nicht zu benutzen. Es liegt ja in deinem Gusto, was du mit dem String s und dem Int i anstellst.
Grob gesagt, stellt Roland den index und den dazu gehörenden Wert (als String) der zu movenden Liste/ Array/File zur Verfügung. Die MoveListProc ist dann, wie ein Schleifenkörper : WhileLoop .... EndWhile |
| | | Benutze XPROFAN X3 + FREEPROFAN Wir sind die XProfaner. Sie werden von uns assimiliert. Widerstand ist zwecklos! Wir werden alle ihre Funktionen und Algorithmen den unseren hinzufügen.
Was die Borg können, können wir schon lange. | 10.09.2018 ▲ |
| |
| | Christof Neuß | Hi,
danke. Wieder was gelernt.
Mal sehen, ob/wie ich das gebrauchen kann. |
| | | | |
| | H.Brill | Vielleicht kann man ja auch mit dem Inline-ASM etwas nachhelfen. Schau mal in Paules Forum. Zumindest, was Schleifen betrifft. Auch das Translate$() ließe sich damit ersetzen bzw. wäre als ASM-Proc schneller.
Evtl. kann dir diesbezüglich jemand was basteln. Volkmar ist da ein guter Ansprechpartner.
Schneller als ASM geht nicht mehr. |
| | | Benutze XPROFAN X3 + FREEPROFAN Wir sind die XProfaner. Sie werden von uns assimiliert. Widerstand ist zwecklos! Wir werden alle ihre Funktionen und Algorithmen den unseren hinzufügen.
Was die Borg können, können wir schon lange. | 11.09.2018 ▲ |
| |
| | Christof Neuß | BTW: Gibt es eine Begrenzung bei Blockread und/oder Blockwrite? Wenn ich mehr als 1 MB einlesen will, bekomme ich eine Fehlermeldung.
Exception EAccessViolation in Modul... Zugriffsverletzung bei Adresse...
Hab' die Grenze nicht genau ausgetestet, aber mit 1.000.000 Bytes hat's noch funktioniert, bei 1.200.000 nicht mehr. |
| | | | |
| | E.T. | .... grad mal probiert und via Blockread 23,5 MB in einen Bereich eingelesen, ohne Probleme....
(Ausschnitt aus einem Progg, wo ich das so ohne Probleme verwende) :
und dann arbeite ich einfach mit dem Bereich weiter, ist ja dann die ganze Datei drinne und ich kann damit machen, was ich will...
Wenn nat. der RAM dann ausgeht, muss man es "zerhackstückeln" |
| | | Grüße aus Sachsen... Mario WinXP, Win7 (64 Bit),Win8(.1),Win10, Win 11, Profan 6 - X4, XPSE, und 'nen schwarzes, blinkendes Dingens, wo ich das alles reinschütte... | 13.09.2018 ▲ |
| |
| | H.Brill | Was man auch nehmen kann, ist Frank Abbings Listview.dll.
Declare Memory bereich, Long hdll, anz
hdll = UseDLL("Listview.dll")
ImportFunc(hdll, "ExchangeSeparator", "TranslateX")
CLS
Dim bereich, FileSize("E:\Liste.txt")
Assign #1, "E:\Liste.txt"
OpenRW #1
anz = BlockRead(#1, bereich)
Close #1
Print "Fertig..."
Print "Taste zum Austausch"
WaitKey
TranslateX(bereich, FileSize("E:\Liste.txt"), Ord(","),Ord("|"),1)
Print "Fertig..."
Waitkey
Dispose bereich
FreeDLL hdll
End
Da ist keine Zeit messbar, so schnell geht das.
PS: Die ProSpeed.dll von Frank hat da noch mehr in Sachen Bereiche zu bieten. |
| | | Benutze XPROFAN X3 + FREEPROFAN Wir sind die XProfaner. Sie werden von uns assimiliert. Widerstand ist zwecklos! Wir werden alle ihre Funktionen und Algorithmen den unseren hinzufügen.
Was die Borg können, können wir schon lange. | 15.09.2018 ▲ |
| |
| | | | - Seite 2 - |
| | H.Brill | Mit ASM für ein einzelnes Zeichen zu ersetzen hat mir Volkmar eine Proc gemacht :
Declare Memory bereich
CLS
Dim bereich, 1000
String bereich, 0 = "1,", "2,", "3,", "4,", "5,", "6,", "7,", "8,", "9,", "10"
//TranslateX(Bereich#, Size&, OrgChar%, NewChar%)
ASM "TranslateX", 4
PUSH EBX
PUSH ECX
PUSH EDX
PUSH ESI
MOV EDI, Par1// Adresse Quellstring
MOV ECX, Par2// Länge Quellstring
MOV EBX, Par3// Code Suchzeichen
MOV EDX, Par4// Code Ersatzzeichen
MOV AL, BL
MOV AH, DL
XOR EDX, EDX// EDX löschen
Suchen:
OR ECX, ECX// Quelllänge 0?
JZ IsLen// Länge 0 erreicht, String durch
DEC ECX// Länge runter zählen
SCASB
JNZ Suchen// Byte nicht gefunden
MOV [EDI - 1], AH// Ersetzen
INC EDX// Ersetzen Zählen (kann entfallen)
JMP Suchen
IsLen:
MOV EAX, EDX
POP ESI
POP EDX
POP ECX
POP EBX
EndASM
Print Char$(bereich, 0, 40)
Print "Stellen ersetzt : ", Str$(TranslateX(bereich, SizeOf(bereich), Ord(","), Ord("|")))
Print Char$(bereich, 0, 40)
WaitInput
Dispose bereich
End
Vielleicht kannst du es ja brauchen. |
| | | Benutze XPROFAN X3 + FREEPROFAN Wir sind die XProfaner. Sie werden von uns assimiliert. Widerstand ist zwecklos! Wir werden alle ihre Funktionen und Algorithmen den unseren hinzufügen.
Was die Borg können, können wir schon lange. | 15.09.2018 ▲ |
| |
| | Christof Neuß | Hallo H.
vielen Dank für Deine Bemühungen. Ja, das hilft tatsächlich schon wieder ein wenig weiter. Geht auch 'ne Ecke schneller.
Genial wäre jetzt folgende ASM-Schleife:
Aufruf mit TranslateX(Addr(Text$),Len(Text$)-1,a,b,c,d,e,f,g,h,k,l,m,n,o,p,q,r) Wobei a-h Buchstaben(codes) sind, nach denen gesucht wird und k-r die Buchstaben(codes) sind, die dafür eingesetzt werden. So könnte ich in einem Aufwasch gleich mehrere Ersetzungen machen (z.B. Tab gegen "|" und "/" gegen " " und ... Dabei wird die Länge des Strings nicht verändert. Ein Zeichen wird gegen genau ein anderes Zeichen ausgetauscht.
Außerdem habe ich (leider) auch noch so etwas zu ersetzen: "|||" gegen "|0|0|". Aber das würde die Länge des Strings verändern. Muss nicht unbedingt sein. Das geht letztlich auch über Translate$ ziemlich fix.
Gruß
Christof |
| | | | |
| | H.Brill | Vielleicht helfen dir da auch die regulären Ausdrücke weiter. Mit Set("RegEx", 1) eingeschaltet, wirken die auch beim xprofan-eigenen Translate$. Gerade das letzte, "|||" gegen "|0|0|" auszutauschen, wäre nicht schwer. Das Suchmuster hierfür wäre : neuerstring$ = Translate$(alterstring$, "[|||]{3,3}", "|0|0|")
Auch für das Suchen von Gruppen [a-h] o.ä. sind ja die reg. Ausdrücke sehr geeignet.
Lies dir mal die Hilfe dazu durch. Bei Fragen kannst du dich ja melden. |
| | | Benutze XPROFAN X3 + FREEPROFAN Wir sind die XProfaner. Sie werden von uns assimiliert. Widerstand ist zwecklos! Wir werden alle ihre Funktionen und Algorithmen den unseren hinzufügen.
Was die Borg können, können wir schon lange. | 17.09.2018 ▲ |
| |
|
AntwortenThemenoptionen | 9.803 Betrachtungen |
ThemeninformationenDieses Thema hat 4 Teilnehmer: |