In classic ASP I need to extract data out of a MSSQL database, passing the results to a two dimensional array (rows, columns) and display the data in various formats.
For each such format I need to build functions to display data. So, in order to be as modular as possible I need to separate (i) extraction and passing data to the array from (ii) displaying the results.
My code does currently the extraction of data using a class, but also displays (within the same class) the data in a primitive way (just to test that the data is extracted and correct).
How can I pass such array to a function? You can imagine how flexible would be to have an array as data input into a function and then manipulate it (creating many functions) when trying to display it in a table (example: function 1 will be based on template no. 1 of a table that is red with background black and no borderline, function 2 is built on the template 2, the table is green, with borderline and yellow background, etc etc).
Here is my code and at the end of the main function (within the class) you will see a portion that displays results, i.e. the one that I need to do it separately from / outside the class (i.e. in the functions to be created).
<!--#include file ="../functions/fctGetnrofrecords.asp"-->
<%
Dim db : Set db = New GetRowsFromAdatabase
db.strTable="Customers"
strDisplay=db.FindOutRecordsAndPassThemToAnArray()
Response.Write strDisplay
Class GetRowsFromAdatabase
Public strTable
Public numberOfRecords
Public numberOfFields
Public Function FindOutRecordsAndPassThemToAnArray()
Set conn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
'Find out connecting credentials
strSERVERspecific=Coli(0)
strDATABASENAMEspecific=Coli(1)
strUIDspecific=Coli(2)
strPWDspecific=Coli(3)
conn.Open "Provider=SQLOLEDB;server=" & strSERVERspecific & ";database=" & strDATABASENAMEspecific & ";uid=" & strUIDspecific & ";pwd=" & strPWDspecific & ";"
rs.Open strTable, conn
if rs.EOF and rs.BOF then
strError = "There is no record in the table " & strTable
else
'Assign the Number Of Fields to the variable “counter”
counter = rs.fields.count
numberOfFields=counter
Dim matrix(25, 10) ' these exceed by far the values of numberOfRecords and numberOfFields
for j=0 to counter-1
matrix(0,j)= rs.Fields(j).Name ' The first dimension of the array, when is zero,
' is populated with the names of fields
next
rs.movefirst
i=1
do until rs.EOF
for j=0 to counter-1
matrix(i,j)=rs(j)
next
i=i+1
rs.movenext
loop
end if
' Now, I need this class not to include the displaying section that follows
' (i.e. see the portion until the end of this function), although this section works fine
numberOfRecords=fctGetNumberOfRowsOfaTable(strTable)
'see the include directive at the beginning of this code (there is a function there that does this)
'====begin section that displays the arrays values
for m = 0 to numberOfRecords
for n=0 to counter-1
strDisplay = strDisplay & m & "," & n & "=" & matrix(m,n) & "<br>"
next
next
'====end section that displays the array values
FindOutRecordsAndPassThemToAnArray = strDisplay
End Function
Public Function Coli(x)
'This function read a line of a txt file located on the server side (hidden from public / httpdocs)
' where x = the relevant line out of the following
' 1 means the 1st line = name / IP server
' 2 means the 2nd line = database name
' 3 means the 3rd line = user name available in the database
' 4 means the 4th line = user’s password
if x<0 or x> 3 then
Coli="Erorr"
Exit Function
else
serv=Server.MapPath("../../../")
path=serv & "\somehiddenfolder\"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile(path & "configuration.txt")
J=0
Dim Datele(3)
Do Until objTextFile.AtEndOfStream
strNextLine = objTextFile.Readline
if x=J then
Coli=strNextLine
exit function
else
J=J+1
end if
Loop
end if
End Function
End Class
%>
Any hints will be highly appreciated.
Use .GetRows to get an array of a table/resultset. To pass such an array to a function (why function? what should be its return value?) you write its name in the argument list of the function call.
Update wrt comment:
Sample of calling a function that expects an array:
>> Function sum(a)
>> sum = Join(a, "+")
>> End Function
>> a = Split("1 2 3")
>> WScript.Echo sum(a)
>>
1+2+3
Instead of Split() - which returns a one dimensional array - you'd use .GetRows() on a valid recordset (and keep in mind that .GetRows() returns a two dimensional array).
Related
I'm trying to create a single PDF file containing a sheet for each tab which I have listed from cell J2 in my Control sheet but I keep getting a Subscript Out Of Range error.
When I record the action I see that it creates an array of sheet names which it then selects to export, so I have a For loop which goes through the list and creates an array which adds to itself until it reaches the end of the list - the aim being to create one long string which I then select as an array.
All appears to be good (the variable PDFArray displays a string of the tab names in what appears to be the correct format) but when I get to the line 'Worksheets(Array(PDFarray)).Select' then I get the error. I've made sure the sheet names contain no undesirable characters or spaces but still no joy. Any help would be very much appreciated. Thank you
Sub B_PDFs()
Dim PDFarray As String, PDFName as String, sht As String
Sheets("Control").Select
PLFile = ActiveWorkbook.Name
PDFLoc = Application.ActiveWorkbook.Path & "\"
PDFName = Range("A20")
PDFSheetCount = Range("J1").Offset(Rows.Count - 1, 0).End(xlUp).Row
'Loop through column J and create a string with each tab name to be exported
For x = 2 To PDFSheetCount Step 1
If x = PDFSheetCount Then
sht = """ " & "" & Cells(x, 10) & """ "
Else
sht = """" & "" & Cells(x, 10) & """" & ", "
End If
PDFarray = PDFarray & sht
Next x
'Create PDF from the array above
Worksheets(Array(PDFarray)).Select - this is where I get the error Subscript Out Of Range
Selection.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PFDLoc & PDFName, Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False,
OpenAfterPublish:=False
Workbooks(PLFile).Activate
End Sub
I don't understand why MS makes NOT requiring variable declaration the default. Select Tools/Options/Editor and check Require Variable Declaration. This will place Option Explicit at the start of any new module. To correct this module, enter it manually at the beginning.
Doing so would have enabled you to find and correct a typo in your code.
You should also be avoiding Select, Selection and Activate. They rarely serve any purpose at all, and can cause multiple problems because they lull into avoiding explicit declarations of which workbook, worksheet, etc. you need. See How to avoid using Select in Excel VBA
However in using the ExportAsFixedFormat method to export selected worksheets, it seems Selection and ActiveSheet are required for it to work.
Array(str_variable) returns an array with a single entry that contains the entire string variable. It does not interpret the string variable so as to split it into separate elements.
So, rewriting your code somewhat (I will leave it to you to clean up the PDF document):
Option Explicit
Sub B_PDFs()
Dim PDFarray As Variant, PDFName As String, PLFile As String, PDFLoc As String
Dim wsControl As Worksheet
Dim WB As Workbook
'Consider wheter you want to use ThisWorkbook or a specific workbook
Set WB = ThisWorkbook
With WB
Set wsControl = .Worksheets("Control")
PLFile = .Name
PDFLoc = .Path & "\"
End With
With wsControl
PDFName = .Range("A20")
'create PDFarray
'This will be a 1-based 2D array starting at J1
'If you need to start at J2, alter the initial cell
PDFarray = .Range(.Cells(1, 10), .Cells(.Rows.Count, 10).End(xlUp))
End With
'convert to a 1D array
PDFarray = WorksheetFunction.Transpose(PDFarray)
'Note the use of `Select` and `ActiveSheet` when using this `ExportAsFixedFormat` method
Worksheets(PDFarray).Select
'Create PDF from the array above
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PDFLoc & PDFName, Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:=False
End Sub
What #RonRosenfeld has suggested is correct about select and selection. The expression you are building is string whereas, Excel expects it to be real array.
So in principle an approach like below shall work for you which will create an array for processing and can be used as you want to utilise.
Dim shtNames As Variant
Dim pdfArray
shtNames = Range("J2:J" & Range("J1").Offset(Rows.Count - 1, 0).End(xlUp).Row).Value
pdfArray = Application.Transpose(shtNames)
Scenario: I have a workbook with multiple worksheets. I am trying to use a function (called within a sub) to export arrays with data from certain worksheets. The arrays are created before the function with the content from the worksheet with:
If ws.Name = "AA" Then
expaa = ws.UsedRange.Value
End if
where expaa is previously defined as variant.
The function I am using apparently finishes running, but the output on the new file saved is weird: instead of having one row of headers, the first row is split into 2 for some reason (all the others remain the same).
This is the function I am using:
Function Exporter(arr As Variant, y As String, OutPath As String) As Variant
Dim lrow As Long, lColumn As Long
Dim w2 As Workbook
Dim d As Date
Workbooks.Add
Set w2 = ActiveWorkbook
w2.Worksheets(1).Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
Application.DisplayAlerts = False
w2.SaveAs Filename:=OutPath & "\" & y, FileFormat:=6
Application.DisplayAlerts = True
w2.Close True
End Function
Which I call from the main sub with:
If aa_name <> "" Then
Exporter expaa , "aa_OK", wbpath
End If
where aa_name is the name of the file used to retrieve the path.
Obs: The wbpath variable is a string with the path of my main workbook (therefore the new file is saved at the same location).
Question: What may be causing the first row of my output to be split? How can that be fixed?
Obs2: I know this can be done with copy procedure, and looping through the array and so on. I even got it to work with other methods. This post is only to understand what I am doing wrong with the current code.
Obs3: Regarding the data that is going to be passed: it is a matrix of days, identifiers and data, ex:
Item1 Item2 Item3
01/01/2000 1 1 2
02/01/2000 1 2 1
03/01/2000 2 2 2
with around 2000 rows and 3000 columns.
UPDATE: After retesting the code multiple times, It appears that the data of the first row only gets split when the file is save as csv (when the array is pasted, the output is normal). Any idea on what may be the cause for that?
I know this is old but here is my solution for the googlers. This accepts an array and creates a CSV at a path you define. Its probably not perfect but it has worked so far for me.
Option Explicit
Option Private Module
Public Function SaveTextToFile(ByVal targetarray As Variant, ByVal filepath As String) As Boolean
On Error GoTo CouldNotMakeFile
Dim fso As FileSystemObject
Set fso = New FileSystemObject
Dim fileStream As TextStream
' Here the actual file is created and opened for write access
Set fileStream = fso.CreateTextFile(filepath)
' Write something to the file
Dim Row As Long, Col As Long
For Row = LBound(targetarray, 1) To UBound(targetarray, 1)
For Col = LBound(targetarray, 2) To UBound(targetarray, 2)
fileStream.Write StringCompliance(targetarray(Row, Col)) & IIf(Col = UBound(targetarray, 2), "", ",")
Next Col
fileStream.WriteBlankLines 1
Next Row
' Close it, so it is not locked anymore
fileStream.Close
' Here is another great method of the FileSystemObject that checks if a file exists
If fso.FileExists(filepath) Then
SaveTextToFile = True
End If
CouldNotMakeFile:
End Function
Private Function StringCompliance(ByVal InputString As String) As String
Dim CurrentString As String
CurrentString = InputString
'Test if string has qoutes
If InStr(CurrentString, Chr$(34)) > 0 Then
CurrentString = Chr$(34) & Replace(CurrentString, Chr$(34), Chr$(34) & Chr$(34)) & Chr$(34)
StringCompliance = True
Else
'Tets if string has commas or line breaks
If InStr(CurrentString, ",") > 0 Or InStr(CurrentString, vbLf) > 0 Then
CurrentString = Chr$(34) & CurrentString & Chr$(34)
Else
StringCompliance = False
End If
End If
StringCompliance = CurrentString
End Function
I have 6 worksheets, each has a subcategory of data (it is important they are in separate worksheets). I am loading up the data into arrays because there are thousands of rows, then printing them out in a specific format to a .txt file.
Sub ExcelToXML()
Dim headers(), data(), attributes1(), attributes2(), attr$, r&, c&
Dim rowCount As Long
Dim columnCount As Long
Dim FF As Worksheet
Dim FOPR As Worksheet
Dim R1 As Long
Dim C1 As Long
Set FF = Worksheets("Fairy")
Set FOPR = Worksheets("Opera")
rowCount = (FF.Range("A1048576").End(xlUp).Row) 'Only one defined as rowCount should be consistent
ffcolumncount = (FF.Range("XFD1").End(xlToLeft).Column)
FOPRcolumnCount = FOPR.Range("XFD1").End(xlToLeft).Column
' load the headers and data to an array '
FFheaders = Cells(1, 1).Resize(1, ffcolumncount).Value
FFdata = Cells(1, 1).Resize(rowCount, ffcolumncount).Value
FOPRheaders = Cells(1, 1).Resize(1, FOPRcolumnCount).Value
FOPRdata = Cells(1, 1).Resize(rowCount, FOPRcolumnCount).Value
' set the size for the attributes based on the columns per child, dynamic
ReDim attributes1(1 To ffcolumncount)
ReDim attributes2(1 To FOPRcolumnCount)
' open file and print the header two main parents
Open "C:\desktop\ToGroup.xml" For Output As #1 'file path is here, going to change to save prompt
Print #1, "<Parent>"
Print #1, " <Child>"
' iterate each row non inclusive of headers
For r = 2 To UBound(FFdata)
' iterate each column '
For c = 1 To UBound(FFdata, 2)
' build each attribute '
attr = FFheaders(1, c) & "=""" & FFdata(r, c) & """"
attributes1(c) = FFheaders(1, c) & "=""" & FFdata(r, c) & """"
Next
For R1 = 2 To UBound(FOPRdata)
For C1 = 1 To UBound(FOPRdata, 2)
attr = FOPRheaders(1, c) & "=""" & FOPRdata(r, c) & """"
attributes2(c) = FOPRheaders(1, c) & "=""" & FOPRdata(r, c) & """"
Next
I cut it off at the prining and at 2 for next loops. (Not actually sure if the for..next loops are structured properly). Anyways, my question is, am I redimensioning wrong? It gives me 'subscript out of range' error on the second attribute. Is the line
ReDim attributes2(1 To FOPRcolumnCount)
the issue? As it may be dimensioning the array in the original worksheet. Perhaps I should define the arrays in separate or worksheet models? Can I and how would I reference them? Is there a way to make the array specifically refer to a worksheet?
Appreciate any input. It's really hard not having anyone around who can provide a second opinion.
Try replacing 'FOPRcolumnCount' with an actual value. If it solves your problem then the issue is with how 'FOPRcolumnCount' is calculated, which I think is where your problem lies. It's hard to tell from your example, but it appears you are trying to find the right most column on row#1 ; there are easier ways of doing that. Same with rowCount.
I notice you haven't declared it as a variable. Always declare your variables; put "Option Explicit" at the top of your module to force you to declare all variables.
I am trying to create a new result set each time I run the query... This way when I get to displaying the information, I will have different result sets for my different tables... Is it possible to change the name of a result set in a for loop?
Something like this would explain what I mean.. On the last line I do a For i = 0 to count -2... I am wondering if I could have the rsTemp(1,2,3,4,5) set to I so that if I do have 5 Queries... I would have rsTemp1,rsTemp2,rsTemp3,rsTemp4, and rsTemp5:
'1st Table Informations
strSQL = "SELECT DISTINCT custpart FROM dex_racklabels..rl_master where custpart like '%-%' and userid <> 'dakkota' and status <> 'S'"
Set rsTemp5 = conPaintDC.Execute(strSQL)
If Not rsTemp5.EOF Then
'If this returns something create an array of the returned rack serial numbers
Do While Not rsTemp5.EOF
strArrayPart = strArrayPart & "'" & rsTemp5("custpart") & "',"
rsTemp5.MoveNext
Loop
rsTemp5.MoveFirst
End If
'Split up the Part Numbers
ArraySplit = Split(strArrayPart,",")
'Count how many items you have
For each item In ArraySplit
count = count + 1
Next
'Run query for each
For i = 0 to count - 2
strSQL = "SELECT COUNT(serialnbr) FROM [dex_racklabels].[dbo].[rl_detail] where custpart = " & ArraySplit(i) & ""
Set rsTemp(1,2,3,4,5) = conPaintDC.Execute(strSQL)
If Not rsTemp(1,2,3,4,5).EOF Then
response.write(strSQL)
End If
Next
response.end()
"Numbered" variables (e.g. rsTemp1,rsTemp2,...) are a bad idea and should be avoided. In your case - a loop that deals with many recordsets sequentially - there is no need for extra/special/fancy variables at all, re-using a local variable is enough:
For Each query_spec
Set oRS = Obtain rs from query_spec
display oRS
close oRS
Next
If you need more than one recordset at the same time, use a collection (Array, Dictionary):
init collection c()
For Each query_spec
Set c(query_spec) = Obtain rs from query_spec
Next
For i = 1 To UBound(c) Step 2
WorkWith2Rs c(i-1), c(i)
Next
This is a doozy for me haha, I've pretty much checked nearly every page on Google Search and I still don't quiet understand how to do it.
I want to create a multi dimensional array in VB Script called data2.
Trying the examples that I've seen but I'm getting a "Subscript out of range" error
Dim data2()
sub grabdata
SQL_query = "SELECT * FROM MSAccess_table"
Set rsData = conn.Execute(SQL_query)
Do Until rsData.EOF = True
ReDim Preserve data2(UBound(data2) + 1)
data2(UBound(data2)) = Array(rsData("id"),rsData("column_1"),rsData("column_2"),rsData("column_3"),rsData("column_4"))
rsData.moveNext
Loop
end sub
Basically I'm trying to learn how to make a multi-dimensional array in VB script and add to it with a loop. What are some basic examples that can work in my case?
(1) The best way to get an ADO resultset into a two-dimensional array is to use the .GetRows method. Then your problem just vanishes.
(2) There are two kind of arrays in VBScript. Fixed arrays are declared by specifying their UBounds:
Dim aFix(2, 3)
They can't be resized. Dynamic arrays can be changed by ReDim [Preserve]. The best way to create such an array is
ReDim aDyn(2, 3)
if you know the starting size, or
Dim aDyn : aDyn = Array()
if you want to start with an empty one. The catch 22 is: you can use Preserve only for the last dimension.
(3) Your
Dim data2()
is an abomination - a fixed array of no size. It's a pity that the 'compiler' is too stupid to catch such a beast that VBScript can't handle properly:
>> Dim data2()
>> WScript.Echo UBound(data2)
>>
Error Number: 9
Error Description: Subscript out of range
The nastiness of the Dim a() statement is hidden by the fact that a later ReDim will store a proper dynamic array into that variable:
>> Dim data2() ' <-- abomination
>> ReDim data2(1,1) ' <-- overwritten by a dynamic array
>> data2(0,0) = 0
>> ReDim Preserve data2(1,5) ' last dimension increased; 'old' data preserved
>> data2(1,5) = 1
>> WScript.Echo data2(0,0), data2(1,5)
>>
0 1
Update wrt jmbpiano's comment:
(1) I gave evidence that you can't get the UBound for a variable dimmed with (), so I stick to my claim that such beasts are abominations. Just look at the question (or this one) to see that using the () will give you trouble.
(2) I said that you should use ReDim a(KnownUbound) to 'declare' a dynamic array with known size, but I didn't give evidence for the 'Option Explicit'-compatibility of this idiom. So :
Option Explicit
ReDim a(4711)
ReDim b(4,7,1,1)
a(0) = "qed"
b(0,0,0,0) = "qed"
WScript.Echo b(0,0,0,0)
output:
cscript 19888987.vbs
qed
This may be off-topic, but after seeing your exact code, why aren't you using the built-in ADO function: GetRows() ?
sub grabdata
SQL_query = "SELECT * FROM MSAccess_table"
Set rsData = conn.Execute(SQL_query)
If Not rsData.EOF Then aData = rsData.GetRows()
end sub
This returns all your column # as the first index, and the rows (data) in the second.
So to loop through it, you would:
If IsArray(aData) Then
For x = lBound(aData,2) to uBound(aData,2) 'loops through the rows
Col1 = aData(0,x)
Col2 = aData(1,x)
Col3 = aData(2,x)
Response.Write "Row #" & x+1 & "<br>"
Response.Write "This is the data in Column1: " & Col1 & "<br>"
Response.Write "This is the data in Column2: " & Col2 & "<br>"
Response.Write "This is the data in Column3: " & Col3 & "<br>"
Next
End If
*NOTE: Rows (and columns) start on 0 in the array by default.
set rs = conn.execute(strQry)
arrRAY = rs.GetRows()
if isarray(arrRAY) then
do stuff
end if