2009-01-09 9 views
11

J'ai un fichier csv de 1,2 millions de dossiers de texte. Les champs alphanumériques sont entourés de guillemets, les champs date/heure ou numériques ne le sont pas.Vous voulez VBA dans Excel pour lire très gros CSV et créer un fichier de sortie d'un petit sous-ensemble de la CSV

Par exemple "Fred", "Smith" 01/07/19672, "7, The High Street", "Anytown", "Anycounty", "LS1 7AA"

Ce que je veux faire est d'écrire VBA dans Excel (plus ou moins le seul outil à ma disposition que je maîtrise raisonnablement bien) qui lit l'enregistrement CSV par enregistrement, effectue une vérification (comme cela arrive sur le dernier champ, le code postal) puis sort un petit sous-ensemble des enregistrements de 1,2 m vers un nouveau fichier de sortie.

Je comprends comment ouvrir les deux fichiers, lisez le dossier, faire ce que je dois faire avec les données et l'écrire (je vais juste sortie l'enregistrement d'entrée avec un préfixe indiquant un type d'exception)

Ce que je ne sais pas, c'est comment analyser le CSV dans VBA correctement. Je ne peux pas faire un simple balayage de texte et rechercher des virgules car le texte a parfois des virgules (d'où les champs de texte sont délimités par du texte)

Y at-il une commande fantastique qui me permettrait d'obtenir rapidement les données du nième champ dans mon dossier?

Ce que je veux est s_work = champ (s_input_record, 5) où 5 est le numéro du champ dans mon CSV ....

Un grand merci, C

Répondre

6

Que diriez-vous VBScript, bien que cela travaillent aussi dans Excel:

Set cn = CreateObject("ADODB.Connection") 

'Note HDR=Yes, that is, first row contains field names ' 
'and FMT delimted, ie CSV ' 

strCon="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Docs\;" _ 
& "Extended Properties=""text;HDR=Yes;FMT=Delimited"";" 

cn.open strcon 

'You would not need delimiters ('') if last field is numeric: '  
strSQL="SELECT FieldName1, FieldName2 INTO New.csv FROM Old.csv " _ 
& " WHERE LastFieldName='SomeTextValue'" 

'Creates new csv file 
cn.Execute strSQL 
+0

J'ai utilisé une technique similaire pour accomplir la même chose dans le passé –

+0

+ 1 c'est la meilleure solution pour un si grand ensemble de données, je l'ai fait avec des données agrégées dans le passé –

+0

@Fionnuala, je suis en utilisant votre réponse pour lire un fichier CSV dans la mémoire. Cependant, je rencontre une limite de 280 car comme je l'ai souligné dans une question: http://stackoverflow.com/questions/34230062/excel-vba-reading-csv-file-with-large-multiline-values-using-adodb -avec-jet-ou-a Avez-vous connu un tel comportement et aucune idée de comment surmonter cette limitation? – IzCe

4

cela ne répond pas directement à votre question, mais grep (ou l'un des équivalents de Windows) serait vraiment briller pour cela, par exemple,

grep -e <regex_filter> foo.csv > bar.csv 
+0

Pourquoi unix pour une question Windows? – Fionnuala

+1

Windows variantes de grep: WinGrep (http://www.wingrep.com/), PowerGrep (http://www.powergrep.com/), et GNU Grep pour Windows (http://gnuwin32.sourceforge.net/ packages/grep.htm). –

+1

Étant donné que grep est disponible sous une forme quelconque, et que son but entier est d'imprimer des lignes correspondant à une regex, ce qui semblait suffisant pour le filtrage mentionné dans la question, cela semblait naturel. –

8

Le code suivant devrait faire l'affaire. Je n'ai pas Excel devant moi, donc je ne l'ai pas testé, mais le concept est solide.

Si cela finit par être trop lent, nous pouvons chercher des moyens d'améliorer l'efficacité.

Sub SelectSomeRecords() 
    Dim testLine As String 

    Open inputFileName For Input As #1 
    Open outputFileName For Output As #2 

    While Not EOF(1) 
     Line Input #1, testLine 
     If RecordIsInteresting(testLine) Then 
      Print #2, testLine 
     End If 
    Wend 

    Close #1 
    Close #2 
End Sub 

Function RecordIsInteresting(recordLine As String) As Boolean 
    Dim lineItems(1 to 8) As String 

    GetRecordItems(lineItems(), recordLine) 

    ''// do your custom checking here: 
    RecordIsInteresting = lineItems(8) = "LS1 7AA" 
End Function 

Sub GetRecordItems(items() As String, recordLine as String) 
    Dim finishString as Boolean 
    Dim itemString as String 
    Dim itemIndex as Integer 
    Dim charIndex as Long 
    Dim inQuote as Boolean 
    Dim testChar as String 

    inQuote = False 
    charIndex = 1 
    itemIndex = 1 
    itemString = "" 
    finishString = False 

    While charIndex <= Len(recordLine) 
     testChar = Mid$(recordLine, charIndex, 1) 

     finishString = False 

     If inQuote Then 
      If testChar = Chr$(34) Then 
       inQuote = False 
       finishString = True 
       charIndex = charIndex + 1 ''// ignore the next comma 
      Else 
       itemString = itemString + testChar 
      End If 
     Else 
      If testChar = Chr$(34) Then 
       inQuote = True 
      ElseIf testChar = "," Then 
       finishString = True 
      Else 
       itemString = itemString + testChar 
      End If 
     End If 

     If finishString Then 
      items(itemIndex) = itemString 
      itemString = "" 
      itemIndex = itemIndex + 1 
     End If 

     charIndex = charIndex + 1 
    Wend 
End Sub 
1

Je vous suggère de jeter un oeil à la bibliothèque d'expression régulière (vous devriez le voir dans « Outils ... Références » comme

« expressions régulières Microsoft VBScript 5.5 » ou quelque chose de très similaire.

Il sont des exemples de Reg Exp et un caractère-caractère assez complet à cet endroit: http://www.xbeat.net/vbspeed/c_ParseCSV.php. Notez que la version Regexp est waaaay plus courte!

Amusez-vous ...

2

Regardez la déclaration Input # dans l'aide d'Excel

Exemple d'utilisation serait:

Input #fnInput, s_Forename, s_Surname, dt_DOB, i_Something, s_Street, s_Town, s_County, s_Postcode 

puis utilisez l'instruction Write # pour écrire des enregistrements correspondant à nouveau

La seule question est peut-être que le format de date dans la sortie finira par # 1967-07-01 # mais ce format est sans ambiguïté contrairement au 01/07/1967 qui représenterait le 1er juillet au Royaume-Uni et le 7 janvier aux Etats-Unis. Si vous avez besoin de conserver la mise en forme de la date puis l'écrire comme une chaîne:

s_DOB = Format(dt_DOB, "dd/mm/yyyy") 
2

Tout ce que vous pouvez faire une ligne-à-un-temps avec vba dans Excel, vous pouvez le faire en matière d'accès à vba ; plus encore parce que c'est une base de données plutôt qu'une feuille de calcul. L'accès est-il indisponible pour vous?

Il est beaucoup plus facile de gérer les tables, les enregistrements et les champs logiques que les feuilles de calcul logiques, les lignes et les colonnes. Pourquoi le paramètre "/ Données/Importation de données externes/Texte/csv" ne fonctionne-t-il pas? L'entrée n'est-elle pas vraiment portable csv?

+0

1,2 million de lignes ne fonctionnera pas dans Excel, cependant, les données peuvent être manipulées avec ADO en utilisant le moteur Jet, c'est-à-dire le moteur sur lequel Access est basé. D'où ma réponse. – Fionnuala

+1

ok - donc pour confirmer - L'accès n'est pas disponible? Quoi qu'il en soit, si vous pouvez accéder à Jet avec ADO, vous pouvez également ouvrir une base de données Access mdb à partir d'Excel avec Excel VBA et y stocker les données - vous n'avez même pas besoin d'Access installé pour le faire. – dkretz

3

J'ai utilisé la dérivée suivante du code ci-dessus pour ouvrir avec succès un fichier csv arbitraire à partir de VBA dans Excel.

Option Explicit
cn public CONNECTION
Public Sub DoIt()
Dim strCon As String
Dim strsql As String
Dim rs As Recordset

Set cn = CreateObject ("ADODB .Connection ")

strcon =" Fournisseur = Microsoft.Jet.OLEDB.4.0; Source de données = C: \ bin \ HomePlanet \; " _
& "Propriétés étendues =" "text; HDR = Oui; FMT = délimité" ";"

cn.Open strCon

strsql = "SELECT * FROM astuname.csv"
Set rs = new ADODB.Recordset
rs.Open strsql, cn
DoEvents de la pause ici pour inspecter des objets et des propriétés rs.close
End Sub

Le rs (recordset) a une collection de champs, avec une propriété Count. Chaque champ en tant que propriété Type.

Vous pouvez référencer les champs par numéro de séquence ...

Debug.Print rs.Fields (rs.Fields.Count - 1) .Type

Est-ce suffisant?

Si ce n'est pas le cas, postez les premières lignes du fichier d'entrée et je prendrai le reste du chemin.

Questions connexes