I would like each column of an Excel sheet to become a CSV file having just that column as its only line.
Example, from the attached Excel screenshot my output should be two csv files (because of the 2 columns) with one row each.
That is the first csv variables will be from 1-9, while the second will be from 11-19.
Export Column To Row
Exports each column of a range to one-row .csv-file.
Adjust the values in the constants section.
Option Explicit
Sub exportColumnsToCSV()
Const FolderPath As String = "F:\Test\2021\66730122\"
Const FileLeft As String = "File "
Const csvDelimiter As String = "," ' or maybe ";"
Dim rg As Range: Set rg = Range("A1").CurrentRegion
Dim Data As Variant: Data = rg.Value
Dim rCount As Long: rCount = UBound(Data, 1)
Dim Result As Variant: ReDim Result(1 To rCount)
Dim cNum As Long: cNum = FreeFile
Dim cFile As String
Dim c As Long, r As Long
For c = 1 To UBound(Data, 2)
For r = 1 To rCount
Result(r) = Data(r, c)
Next r
cFile = FolderPath & FileLeft & c & ".csv"
Open cFile For Output As #cNum
Print #cNum, Join(Result, csvDelimiter)
Close #cNum
Next c
End Sub
You're lucky, I just spent my week on a similar problem. I adapted it to your problem.
Enjoy!
Private Sub SplitColumnsInCSV()
Dim wb As Workbook
Dim ws As Worksheet
Dim TableToSplit As ListObject
Dim WorkbookPath As String
Dim CSVLocation As String
Dim FilePath As String
Dim Folder As String
Dim i, j, TableColumnCount As Integer
Dim TempRangeToSplit As Range
Set wb = ActiveWorkbook
'Real name of your worksheet where your table is located
Set ws = wb.Worksheets("Feuil1")
'Real name of your table in your worksheet
Set TableToSplit = ws.ListObjects("TableToSplit")
For i = 1 To TableToSplit.ListColumns.Count
'adjust temp range for each columns
Set TempRangeToSplit = TableToSplit.ListColumns(i).Range
'Create à folder with your CSV where your workbook is located
CSVLocation = wb.Path & "\CSV\"
Folder = Dir(CSVLocation, vbDirectory)
If Folder = "" Then MkDir CSVLocation
'name the csv file
FilePath = wb.Path & "\CSV\" & ws.Name & TempRangeToSplit.Cells(1, 1).Value & ".csv"
'this piece of code have 2 loops in case of 2 dimentionnal range
Open FilePath For Output As #1
For j = 1 To TempRangeToSplit.Rows.Count
For k = 1 To TempRangeToSplit.Columns.Count
cellValue = TempRangeToSplit.Cells(j, k).Value
If k = TempRangeToSplit.Columns.Count Then
Write #1, cellValue
Else
Write #1, cellValue,
End If
Next k
Next j
Close #1
Next i
End Sub
In detail:
I fill the temp range with the column I want to extract in CSV
I create a folder in case it doesn't already exist
I create the csv file and give it a name
I write in the csv file all the lines of a column
Be careful, if you reuse the macro, the new files with the same name will overwrite the old ones.
Related
I have a fairly large excel file (Think 65,000+ rows).
Within the excel file, only two columns matter for this exercise: CCNumber and FileFound (Col BC/BD).
I am trying to use a for loop to loop through the 65,000 + rows and compare the CCNumber (ID) against a folder of files (30,000 files), and then if an id matches/isnt found print "Available" or "Not Found" in the FileFound column - As below:
Sub LoopFiles
Dim fileName As Variant, csheet As Variant
fileName = Dir("Some\Directory\Here\*pdf")
Dim CCNums As Range
Set CCNums = Range("BC4:BC68512")
Application.ScreenUpdating = False
While fileName <> ""
ID = Left(fileName,6) 'id is a 6 digit numeric number, strip away everything else
For Each CCNum in CCNums
csheet = Left(CCNum, 6)
if(ID = csheet) Then
CCNum.Offset(0,1).Value = "Available"
Else
CCNum.Offset(0,1).Value = "Not Found"
End If
Next CCNum
fileName = Dir
Wend
Application.ScreenUpdating = True
End Sub
The above is hilariously inefficient and it takes forever. Is there a way I can speed this up, or am I just going to have to sit here and wait for the spinning wheel of doom to stop.
Instead of looping through a file list you can directly check with Dir and wildcards if a file exists.
eg. you can use Dir("C:\Temp\myNumber*.pdf") to find a file that is named myNumberAndUnusefulText.pdf. So if you use fileName = Dir("Some\Directory\Here\" & CSheet & "*.pdf") it will return the file name of a file that starts with the number in CSheet.
Further reading all the values into an array first and then processing the array makes your code much faster. Reading and writing actions to cells use a lot of overhead and therefore are slow. By reading the values into an array you reduce it to just one cell reading and one cell writing action.
Option Explicit
Public Sub LoopFilesImproved()
Dim CCNums As Range
Set CCNums = ThisWorkbook.Worksheets("Sheet1").Range("BC4:BC68512") ' always specify in which sheet a range is!
' define output range
Dim Output As Range
Set Output = CCNums.Offset(ColumnOffset:=1)
' read output range into array for faster processing
Dim OutputValues() As Variant
OutputValues = Output.Value2
' read all values into an array for faster processing
Dim CCNumsValues() As Variant
CCNumsValues = CCNums.Value2
' loop through numbers and check if a file exists
Dim iCCNum As Long
For iCCNum = LBound(CCNumsValues, 1) To UBound(CCNumsValues, 1)
Dim CSheet As String
CSheet = Left$(CCNumsValues(iCCNum, 1), 6)
Dim fileName As String
fileName = Dir("Some\Directory\Here\" & CSheet & "*.pdf")
If fileName <> vbNullString Then
OutputValues(iCCNum, 1) = "Available"
Else
OutputValues(iCCNum, 1) = "Not Found"
End If
Next iCCNum
' write array values back to cell
Output.Value2 = OutputValues
End Sub
You can try first collecting all of the file names into a dictionary - from that point the check will be fast...
Sub LoopFiles()
Dim dictFiles As Object, arrCC, arrAv, rngCC As Range, r As Long
Set dictFiles = FileIds("Some\Directory\Here\*.pdf") 'collect all the file Id's
Set rngCC = ActiveSheet.Range("BC4:BC68512")
arrCC = rngCC.Value
ReDim arrAv(1 To UBound(arrCC, 1), 1 To 1) 'size the "available?" array
For r = 1 To UBound(arrCC, 1) 'loop data from BC
id = Left(arrCC(r, 1), 6) 'extract the id
arrAv(r, 1) = IIf(dict.exists(id), "Available", "Not found")
Next r
rngCC.Offset(0, 1).Value = arrAv 'populate availability in BD
End Sub
'scan all files matching the `folderPath` pattern, and return a Dictionary object
' with keys equal to the first 6 characters of the file names
Function FileIds(folderPath As String)
Dim dict As Object, f, id
Set dict = CreateObject("scripting.dictionary")
f = Dir(folderPath)
Do While Len(f) > 0
If Len(f) >= 10 Then dict(Left(f, 6)) = True 'need at least 10 chars with the extension
f = Dir()
Loop
Set FileIds = dict
End Function
In a quick test on a local drive with 30k files, calling FileIds took about 0.08 seconds. Calling Dir() 65k times on the same folder took 12-13secs.
Update File Availability Using a List
Option Explicit
Sub UpdateFilesAvailability()
Const FolderPath As String = "C:\Test"
Const RightFilePart As String = "*.pdf"
Const idLen As Long = 6
Const sRangeAddress As String = "BC4:BC68512"
Const dCol As String = "BD"
Const dYes As String = "Available"
Const dNo As String = "Not found"
Const Msg As String = "Files availability updated."
' Validate the folder path.
Dim fPath As String: fPath = FolderPath
If Right(fPath, 1) <> "\" Then fPath = fPath & "\"
If Len(Dir(fPath, vbDirectory)) = 0 Then
MsgBox "The folder '" & fPath & "' doesn't exist.", vbCritical
Exit Sub
End If
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
' Reference the source range ('srg').
Dim srg As Range: Set srg = ws.Range(sRangeAddress)
' Write the values from the source range
' to a 2D one-based one-column array ('Data').
Dim Data As Variant: Data = srg.Value
Dim cString As String ' Current String
Dim fName As String ' Current File Name
Dim r As Long ' Current Array Row
Dim FileFound As Boolean
' Loop through the rows of the destination array and replace its values
' with the results.
For r = 1 To UBound(Data, 1)
cString = CStr(Data(r, 1))
If Len(cString) >= idLen Then
fName = Dir(fPath & Left(cString, idLen) & RightFilePart)
If Len(fName) > 0 Then FileFound = True
End If
If FileFound Then
Data(r, 1) = dYes
FileFound = False
Else
Data(r, 1) = dNo
End If
Next r
' Reference the destination range.
Dim drg As Range: Set drg = srg.EntireRow.Columns(dCol)
' Write the values from the array to the destination range.
drg.Value = Data
'drg.EntireColumn.AutoFit
'ws.Parent.Save ' save the workbook
MsgBox Msg, vbInformation
End Sub
I am copying data under columns with matching headers between the source sheet and the destination sheet. Both the sheets are in the same excel file but they need to have a clarification number.
For example, one of the columns in the destination sheet has the the clarification number QM6754 and the row of data of QM6754. The source sheet also has the clarification number column but I dont want to copy it, I want to copy the other data in the row of this specific clarification number to the destination sheet that in one of its columns. this way the data isn't copied randomly and the entire row from each sheet relate to each other.
The code I used shows results(I modified it) but when I run it, the excel file shows (not responding) for about 3-4 minutes and then shutsdown or leaves a blank Excel sheet and VBA window. I close the excel file and reopen it and the data has been copied. The file is quite large and I have three pushbuttons that run this code for each sheet I want to copy data from. Three sheets with average of 3k-6k rows. But I cannot eliminate the rows.
The code runs but I would like to optimize of the way it runs because it isn't practical to run, close file and then open file again. Could the issue be with the For loop?
Sub CopyColumnData()
Dim wb As Workbook
Dim myworksheet As Variant
Dim workbookname As String
' DECLARE VARIABLES
Dim i As Integer ' Counter
Dim j As Integer ' Counter
Dim colsSrc As Integer ' PR Report: Source worksheet columns
Dim colsDest As Integer ' Open PR Data: Destination worksheet columns
Dim rowsSrc As Long ' Source worksheet rows
Dim WsSrc As Worksheet ' Source worksheet
Dim WsDest As Worksheet ' Destination worksheet
Dim ws1PRRow As Long, ws1EndRow As Long, ws2PRRow As Long, ws2EndRow As Long
Dim searchKey As String, foundKey As String
workbookname = ActiveWorkbook.Name
Set wb = ThisWorkbook
myworksheet = "Sheet 1 copied Data"
wb.Worksheets(myworksheet).Activate
' SET VARIABLES
' Source worksheet: Previous Report
Set WsSrc = wb.Worksheets(myworksheet)
Workbooks(workbookname).Sheets("Main Sheet").Activate
' Destination worksheet: Master Sheet
Set WsDest = Workbooks(workbookname).Sheets("Main Sheet")
'Adjust incase of change in column in both sheets
ws1ORNum = "K" 'Clarification Number
ws2ORNum = "K" 'Clarification Number
' Setting first and last row for the columns in both sheets
ws1PRRow = 3 'The row we want to start processing first
ws1EndRow = WsSrc.UsedRange.Rows(WsSrc.UsedRange.Rows.Count).Row
ws2PRRow = 3 'The row we want to start search first
ws2EndRow = WsDest.UsedRange.Rows(WsDest.UsedRange.Rows.Count).Row
For i = ws1PRRow To ws1EndRow ' first and last row
searchKey = WsSrc.Range(ws1ORNum & i)
'if we have a non blank search term then iterate through possible matches
If (searchKey <> "") Then
For j = ws2PRRow To ws2EndRow ' first and last row
foundKey = WsDest.Range(ws2ORNum & j)
' Copy result if there is a match between PR number and line in both sheets
If (searchKey = foundKey) Then
' Copying data where the rows match
WsDest.Range("AI" & j).Value = WsSrc.Range("A" & i).Value
WsDest.Range("AJ" & j).Value = WsSrc.Range("B" & i).Value
WsDest.Range("AK" & j).Value = WsSrc.Range("C" & i).Value
WsDest.Range("AL" & j).Value = WsSrc.Range("D" & i).Value
WsDest.Range("AM" & j).Value = WsSrc.Range("E" & i).Value
WsDest.Range("AN" & j).Value = WsSrc.Range("F" & i).Value
WsDest.Range("AO" & j).Value = WsSrc.Range("G" & i).Value
WsDest.Range("AP" & j).Value = WsSrc.Range("H" & i).Value
Exit For
End If
Next
End If
Next
'Close Initial PR Report file
wb.Save
wb.Close
'Pushbuttons are placed in Summary sheet
'position to Instruction worksheet
ActiveWorkbook.Worksheets("Summary").Select
ActiveWindow.ScrollColumn = 1
Range("A1").Select
ActiveWorkbook.Worksheets("Summary").Select
ActiveWindow.ScrollColumn = 1
Range("A1").Select
End Sub
To increase the speed and reliability, you will want to handle the copy/paste via array transfer instead of the Range.Copy method. Given your existing code, here's how a solution that should work for you:
Sub CopyColumnData()
'Source data info
Const sSrcSheet As String = "Sheet 1 copied Data"
Const sSrcClarCol As String = "K"
Const lSrcPRRow As Long = 3
'Destination data info
Const sDstSheet As String = "Main Sheet"
Const sDstClarCol As String = "K"
Const lDstPRRow As Long = 3
'Set variables based on source and destination
On Error Resume Next
Dim wbSrc As Workbook: Set wbSrc = ThisWorkbook
Dim wsSrc As Worksheet: Set wsSrc = wbSrc.Worksheets(sSrcSheet)
Dim wbDst As Workbook: Set wbDst = ActiveWorkbook
Dim wsDst As Worksheet: Set wsDst = wbDst.Worksheets(sDstSheet)
On Error GoTo 0
'Verify source and destination were found
If wsSrc Is Nothing Then
MsgBox "Worksheet """ & sSrcSheet & """ not found in " & wbSrc.Name
Exit Sub
End If
If wsDst Is Nothing Then
MsgBox "Worksheet """ & sDstSheet & """ not found in " & wbDst.Name
Exit Sub
End If
'Setup variables to handle Clarification Number matching and data transfer via array
Dim hDstClarNums As Object: Set hDstClarNums = CreateObject("Scripting.Dictionary") 'Clarification Number Matching
'Load Source data into array
Dim rSrcData As Range: Set rSrcData = wsSrc.Range(sSrcClarCol & lSrcPRRow, wsSrc.Cells(wsSrc.Rows.Count, sSrcClarCol).End(xlUp))
Dim aSrcClarNums() As Variant: aSrcClarNums = rSrcData.Value
Dim aSrcData() As Variant: aSrcData = Intersect(rSrcData.EntireRow, wsSrc.Columns("A:H")).Value 'Transfer data from columns A:H
'Prepare dest data array
Dim rDstData As Range: Set rDstData = wsDst.Range(sDstClarCol & lDstPRRow, wsDst.Cells(wsDst.Rows.Count, sDstClarCol).End(xlUp))
Dim aDstClarNums() As Variant: aDstClarNums = rDstData.Value
Dim aDstData() As Variant: aDstData = Intersect(rDstData.EntireRow, wsDst.Columns("AI:AP")).Value 'Destination will be into columns AI:AP
'Use dictionary to perform Clarification Number matching
Dim vClarNum As Variant
For Each vClarNum In aDstClarNums
If Not hDstClarNums.Exists(vClarNum) Then hDstClarNums.Add vClarNum, hDstClarNums.Count + 1
Next vClarNum
'Transfer data from source to destination using arrays
Dim i As Long, j As Long
For i = 1 To UBound(aSrcClarNums, 1)
For j = 1 To UBound(aSrcData, 2)
If hDstClarNums.Exists(aSrcClarNums(i, 1)) Then aDstData(hDstClarNums(aSrcClarNums(i, 1)), j) = aSrcData(i, j)
Next j
Next i
'Output to destination
Intersect(rDstData.EntireRow, wsDst.Columns("AI:AP")).Value = aDstData
'Save and close source workbook (uncomment next line if this is necessary)
'wbSrc.Close SaveChanges:=True
'Activate summary sheet, cell A1 in destination workbook (uncomment these lines if this is necessary)
'wbDst.Worksheets("Summary").Activate
'wbDst.Worksheets("Summary").Range("A1").Select
End Sub
I'd like to output text file from some sample excel files.
So that I created following samples.
after opening text file , each rows are printed.
But when I try to loop over columns , these values are appended in one columns
Are there any good way to achieve row and column based loop ?
This text file uses comma separator.
Thanks.
Sub Test_Open()
Dim strFilePath As String
Dim ws As Worksheet
strFilePath = "C:\Users\test\text.txt"
Workbooks.Open "C:\Users\test.xlsx"
Set ws = ActiveWorkbook.Worksheets("test")
Open strFilePath For Output As #1
Dim row As Integer
Dim column As Integer
row = 7
Do Until ws.Cells(row, 2).Value = ""
For column = 1 To 86
Print #1, ws.Cells(row, column)
Next
row = row + 1
Loop
Close #1
End Sub
You can add some variable to hold all your column information.
Change your code
For column = 1 To 86
Print #1, ws.Cells(row, column)
Next
To this code.
Dim cols As String
' Add all column separated by comma(,)
For column = 1 To 86
cols = cols & "," & ws.Cells(row, column)
Next
' Trim first comma(,)
cols = Mid(cols, 2)
' Write column to one line at last
Print #1, cols
You can save it with Workbook.SaveAs as *.csv with requered options
Sub Test_Open()
Dim strFilePath As String
strFilePath = "c:\test\text.txt"
With Workbooks.Open("c:\test\test.xlsx").Worksheets("test")
.SaveAs strFilePath, xlCSV
.Parent.Close False
End With
End Sub
Please, try the next code:
Sub Test_Open()
Dim strFilePath As String, wb As Workbook, ws As Worksheet
Dim i As Long, j As Long, txtArr, colArr, nrCol As Long, arrFin
strFilePath = "C:\Users\test\text.txt"
Set wb = Workbooks.Open("C:\Users\test.xlsx")
Set ws = wb.Worksheets("test")
txtArr = Split(CreateObject("Scripting.FileSystemObject").OpenTextFile(strFilePath, 1).ReadAll, vbCrLf)
nrCol = UBound(Split(txtArr(0), ","))
ReDim arrFin(1 To UBound(txtArr) - 6, 1 To nrCol)
For i = 6 To UBound(txtArr)
colArr = Split(txtArr(i), ",")
For j = 0 To nrCol
arrFin(i + 1, j + 1) = colArr(j)
Next j
Next
ws.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
End Sub
The code is not tested. If you would share the file you use, I will test it and eventually optimize something, if the case...
If something unclear, do not hesitate to ask for clarifications. I can comment the lines which look more difficult to be understood.
Dim num As Double 'Variable forstoring
Dim row As Integer, col As Integer ' loop
Sheets("asses").Select
Open "Ass.dat" For Output As #1 'Open file
For col = 1 To 10
For row = 1 To 100
Print #1, Cells(row, col)
Next row
Print #1, vbCrLf
Next col
Close #1 ' close assigned file #1
MsgBox "Finished"
This sub i wrote reads my values but every 100 rows puts a space I need it to write a new column into the sheet so it is 10 columns by 100 rows not 100 rows with a space then another 100 in one columns
You could improve on something like this:
Option Explicit
Sub exportAsText()
Const FilePath As String = "F:\Test\2020\Test.dat"
Const wsName As String = "Sheet1"
Const rngAddr As String = "A1:J100"
Dim wb As Workbook
Set wb = ThisWorkbook
Dim rng As Range
Set rng = wb.Worksheets(wsName).Range(rngAddr)
Dim Source As Variant
Source = rng.Value
Dim Result As Variant
ReDim Result(1 To 100)
Dim i As Long
Dim j As Long
Dim strR As String
For i = 1 To 100
strR = Source(i, 1)
For j = 2 To 10
strR = strR & vbTab & Source(i, j)
Next j
Result(i) = strR
Next i
strR = Join(Result, vbLf)
Open FilePath For Output As #1
Print #1, strR
Close #1
MsgBox "Finished"
End Sub
Currently I have a template which is in range called rngP1.
And this contains a text below:
"This is to confirm that strTitle has been enacted on strDate for strCompany."
Basically, I have a data in another sheet that will be used to replace these 3 strings from my template:
So what I would like to happen is that in every row data it will search strings strTitle, strDate, and strCompany and replace them according to the data of each row.
I have a code already, however, it doesn't work as I expected:
Sub example()
Dim wsMain As Worksheet
Set wsMain = Sheets("Main")
Dim wsTemplate As Worksheet
Set wsTemplate = Sheets("Template")
Dim textToReplace As Variant
Dim array_example()
Dim Find_Text As Variant
Dim str As String
last_row = wsMain.Range("A1").End(xlDown).Row 'Last row of the data set
ReDim array_example(last_row - 1, 2)
Find_Text = Array("strTitle", "strDate", "strCompany")
str = wsTemplate.Range("rngP1").Value
'Storing values in the array
For i = 0 To last_row - 1
array_example(i, 0) = wsMain.Range("A" & i + 2)
array_example(i, 1) = wsMain.Range("C" & i + 2)
array_example(i, 2) = wsMain.Range("D" & i + 2)
Next
For i = LBound(array_example, 1) To UBound(array_example, 1)
For j = LBound(array_example, 2) To UBound(array_example, 2)
For a = 0 To UBound(Find_Text)
str = Replace(str, Find_Text(a), array_example(i, j))
Next a
Next j
MsgBox str
Next i
End Sub
Wrong Output:
It should be:
This is to confirm that Title1 has been enacted on 13-October-18 for Company X.
And next one would be the next row which is title 2. So on and so fort.
If you have an alternative way to do it, I appreciate it.
Here is a working example:
You can push the data range from a worksheet into an array with one line without looping
DataArr = wsMain.Range("A2:D" & LastRow).Value
You need only 2 loops for the replacing:
one to loop through the data rows
one to loop through the variables to replace
Your template str was not initialized within the loop, but you need a fresh template for every data row.
Note that the array loaded from the range starts counting from 1 but the variables array starts counting from 0.
Option Explicit
Sub Example()
Dim Template As String
Template = "This is to confirm that strTitle has been enacted on strDate for strCompany."
'load your template string from worksheet here!
Dim Variables As Variant 'variables to be replaced
Variables = Array("strTitle", "strDate", "strCompany")
Dim wsMain As Worksheet
Set wsMain = ThisWorkbook.Worksheets("Main")
Dim LastRow As Long 'this method is more reliable to find the last used row
LastRow = wsMain.Cells(wsMain.Rows.Count, "A").End(xlUp).Row
Dim DataArr As Variant 'load the complete data range into an array
DataArr = wsMain.Range("A2:D" & LastRow).Value
Dim Output As String
Dim iRow As Long, iVar As Long
For iRow = LBound(DataArr, 1) To UBound(DataArr, 1) '1 to LastRow
Output = Template 'initialize with the template!
For iVar = LBound(Variables) To UBound(Variables) ' 0 to 2
Output = Replace(Output, Variables(iVar), DataArr(iRow, iVar + 1))
Next iVar
Debug.Print Output
Next iRow
End Sub