Sorting a collection in classic ASP - sorting

It's quite a simple question - how do I sort a collection?
I've got a CSV file with rows in a random order. I'd like to sort the rows according to the date in one column. Do I add the rows to a recordset? Can I sort with a Scripting.Dictionary?
I've clearly been spoilt with .NET and Linq, and now I find myself back in the land of classic asp, realising I must have known this 7 years ago, and missing generics immensely. I feel like a complete n00b.

In this case I would get help from big brother .net. It's possible to use System.Collections.Sortedlist within your ASP app and get your key value pairs sorted.
set list = server.createObject("System.Collections.Sortedlist")
with list
.add "something", "YY"
.add "something else", "XX"
end with
for i = 0 to list.count - 1
response.write(list.getKey(i) & " = " & list.getByIndex(i))
next
Btw if the following .net classes are available too:
System.Collections.Queue
System.Collections.Stack
System.Collections.ArrayList
System.Collections.SortedList
System.Collections.Hashtable
System.IO.StringWriter
System.IO.MemoryStream;
Also see: Marvels of COM .NET interop

I'd go with the RecordSet approach. Use the Text Driver. You'll need to change the directory in the connection string and the filename in the select statement. the Extended Property "HDR=Yes" specifies that there's a header row in the CSV which I suggest as it will make writing the psuedo SQL easier.
<%
Dim strConnection, conn, rs, strSQL
strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\inetpub\wwwroot\;Extended Properties='text;HDR=Yes;FMT=Delimited';"
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open strConnection
Set rs = Server.CreateObject("ADODB.recordset")
strSQL = "SELECT * FROM test.csv order by date desc"
rs.open strSQL, conn, 3,3
WHILE NOT rs.EOF
Response.Write(rs("date") & "<br/>")
rs.MoveNext
WEND
rs.Close
Set rs = Nothing
conn.Close
Set conn = Nothing
%>

It's been a long time for me too. IIRC you don't have an option out of the box.
If I were you I'd put all the data in an array and then sort the array. I found a QuickSort implementation here: https://web.archive.org/web/20210125130007/http://www.4guysfromrolla.com/webtech/012799-3.shtml

Also look at the "Bubble Sort", works excellent with those classic asp tag cloud.
https://web.archive.org/web/20180927040044/http://www.4guysfromrolla.com:80/webtech/011001-1.shtml

A late late answer to this, but still of value.
I was working with small collections so could afford the approach where I inserted the item in the correct place on each occasion, effectively reconstructing the collection on each addition.
The VBScript class is as follows:
'Simple collection manager class.
'Performs the opration of adding/setting a collection item.
'Encapulated off here in order to delegate responsibility away from the collection class.
Class clsCollectionManager
Public Sub PopulateCollectionItem(collection, strKey, Value)
If collection.Exists(strKey) Then
If (VarType(Value) = vbObject) Then
Set collection.Item(strKey) = Value
Else
collection.Item(strKey) = Value
End If
Else
Call collection.Add(strKey, Value)
End If
End Sub
'take a collection and a new element as input parameters, an spit out a brand new collection
'with the new item iserted into the correct location by order
'This works on the assumption that the collection it is receiving is already ordered
'(which it should be if we always use this method to populate the item)
'This mutates the passed collection, so we highlight this by marking it as byref
'(this is not strictly necessary as objects are passed by reference anyway)
Public Sub AddCollectionItemInOrder(byref existingCollection, strNewKey, Value)
Dim orderedCollection: Set orderedCollection = Server.CreateObject("Scripting.Dictionary")
Dim strExistingKey
'If there is something already in our recordset then we need to add it in order.
'There is no sorting available for a collection (or an array) in VBScript. Therefore we have to do it ourself.
'First, iterate over eveything in our current collection. We have to assume that it is itself sorted.
For Each strExistingKey In existingCollection
'if the new item doesn't exist AND it occurs after the current item, then add the new item in now
'(before adding in the current item.)
If (Not orderedCollection.Exists(strNewKey)) And (strExistingKey > strNewKey) Then
Call PopulateCollectionItem(orderedCollection, strNewKey, Value)
End If
Call PopulateCollectionItem(orderedCollection, strExistingKey, existingCollection.item(strExistingKey))
Next
'Finally check to see if it still doesn't exist.
'It won't if the last place for it is at the very end, or the original collection was empty
If (Not orderedCollection.Exists(strNewKey)) Then
Call PopulateCollectionItem(orderedCollection, strNewKey, Value)
End If
Set existingCollection = orderedCollection
End Sub
End Class

Related

Retrieved parent folders from a document aren't always correct

We're migrating a library content from Filenet Content Services to Filenet P8.
So we wrote an extractor which outputs both folders tree and document list, in XML format, each document with versions, properties and parent folder. This extractor relies on a home-made dll which virtualizes FileNet objects.
Documents are retrieved this way (huge sql request) :
Public Function getAllDocumentIds() As ADODB.Recordset
Dim cmdProperties As New Dictionary
cmdProperties.Item("Maximum Rows") = 0
cmdProperties.Item("Page Size") = 0
Set getAllDocumentIds = _
executeADOQuery("SELECT idmId, idmVerFileName, " & vbNewLine & _
" idmVerCreateDate, idmAddedByUser" & vbNewLine & _
" FROM FNDOCUMENT ORDER BY idmId ASC", & _
cmdProperties)
End Function
But we encounter issues when we retrieve parent folders this way (slightly modified to be used as an example) :
Public Function getFolders(document As IDMObjects.document) As Collection
Dim f As IDMObjects.Folder
' [...]
For Each f In document.FoldersFiledIn '
' folders retrieval
Next
End Function
For a little amount of documents, some "wrong" parent folders ("folders [the document is] filed in") are reported.
"Wrong" because the following way doesn't report the document has being filed in them (slightly modified code too) :
Public Function getDocumentIds(folder As IDMObjects.Folder) As Collection
Dim rs As ADODB.Recordset
Dim cmdProperties As New Dictionary
' Actually there is a control here, to prevent Filenet crashing.
' Indeed, setting "Page Size" to 0 crashes if no results are returned.
' *Maybe* a product bug.
cmdProperties.Add "SearchFolderName", internalObject.id ' Folder parent
cmdProperties.Item("Maximum Rows") = 0 ' No limits
cmdProperties.Item("Page Size") = 0 ' No limits. Crashes if Recordset is empty
' Here, the cmdProperties entries are copied to an
' ADODB.Command object, into the "properties" member.
' The query are copied to this object, into the "CommandText" member.
' Then ADODB.Command.Execute is invoked and returs an ADODB.RecordSet.
' So this Recordset contains all documents filled in this folder.
Set rs = executeADOQuery("SELECT * from FnDocument", cmdProperties)
Exit Function
End Function
We're working on a workaround, which may take more resources (for each document, double-check...). But understanding why we don't get same results could be relevant to check our library.
If I understand the problem correctly, I believe the quick answer is that the logical order of parent and child records in the result set can not be guaranteed by the query. You are making an assumption about the ID sequences. A document can be moved so there is nothing to guarantee a folder id will occur before that of a document id or vice versa. For a large document set, to solve this without recursion, defer the child records without a parent and resolve them later (in the sample I used a sentinel to flag/filter such records). Depending upon the number of 'orphaned' rows, you may be able to do this in memory, or it may require a second pass. The getrows method will allow you to handle 'huge' datasets, especially if you're using XML and don't want to run out of memory.

Run-time error '91' when adding data to a record set

I want to insert some information into a database in VB6, but I get runtime error '91'.
My code:
Private sub btn_click()
Fname = txtFname.text
Adodc1.Recordset.AddNew
Adodc1.Recordset.Fields("Fname") = Fname
Adodc1.Recordset.Update
End sub
The debuger shows error on: Adodc.Recordset.AddNew
You haven't posted the rest of the code as to where the Adhoc1 variable is created etc...
This post suggests that the RecordSet has not been initialized prior to working with it, but it's hard to be more specific without the code.
Runtime error '91' is Object variable or With block variable not set, which is a slightly confusing way to say that your variable contains Nothing.
You either forgot to initialise the Adodc1 properly, or, and this is more likely, you need to initialise Adodc1.RecordSet to something useful (like a Set Adodc1.RecordSet = New RecordSet or related) before you can use it.
By the way you posted the code, I believe that you will populate a Recordset to insert into the database. Try as follows:
sub btn_click()
dim Adodc1 as adodb.recordset
set Adodc1 = new adodb.recordset
Fname = txtFname.text
Rs.Fields.Append "Fname", adVarChar, 20 'adVarChar = text, followed by the amount of characters
Adodc1.open()
Adodc1.Recordset.AddNew
Adodc1.Recordset.Fields("Fname") = Fname
Adodc1.Recordset.Update
End sub

VB6 - Items not adding to collection

I'm having problems building a collection of data. The problem code is as follows:
'Basic defitions are as follows:
Private mCol As Collection
Dim mcnn As ADODB.Connection
Dim mrs As New ADODB.Recordset
Dim uCustomClass As CustomClass
On Error GoTo 0
'set Find to false to catch any errors
Find = False
'checks for an active connection and then..
Set mCol = Nothing
Set mCol = New Collection
With mrs
.Open AN_SQL_SELECT_STATEMENT , mcnn, adOpenForwardOnly, adLockOptimistic
While Not .EOF
Set uCustomClass = New CustomClass
Set uCustomClass.Connection = mcnn
uCustomClass.CustomerName = NullToEquiv(.Fields("customer_name").Value,NULL_STRING)
uCustomClass.NumberOfOrders = NullToEquiv(.Fields("num_of_orders").Value, NULL_LONG)
uCustomClass.FavoriteColour = NullToEquiv(.Fields("favorite_colour").Value, NULL_STRING)
'Cache orginal values in case the keys change
uCustomClass.CacheOriginalValues
'add to collection
mCol.Add uCustomClass
.MoveNext
Wend
Now the result of this in run time is that the uCustomClass tree structure looks like:
-uCustomClass
+connection
count
+ mcnn
-mCol
+Item1
+Item2
+Item3
+mrs
mvarChangedCount
+NewEnum
It's all good bar I'm not getting Item1, Item2 and Item3 directly under the uCustomClass but only in mCol. I've what appears to be the exact same code running elsewhere for a different custom class and I'm getting what I want e.g.
-uCustomClassThatWorks
+connection
count
+ mcnn
-mCol
+Item1
+Item2
+Item3
+mrs
mvarChangedCount
+NewEnum
+Item1
+Item2
+Item3
Any ideas where the problem might be?
Not sure how uCustomClass would ever get those items added. Is there some missing code or something?
One point worth making is that collections can't have the same keys more than once, which would explain why they are able to be added in one area, but not able to be added again. There might be something that is even trimming strings or something that would aggravate the situation. So just make sure your keys are unique.

VB6 Tuple Equivalent?

I'm porting some C# code to VB6 because legacy applications. I need to store a list of pairs. I don't need to do associative lookups, I just need to be able to store pairs of items.
The snippet I'm porting from looks like this:
List<KeyValuePair<string, string>> listOfPairs;
If I were to port this to C++, I'd use something like this:
std::list<std::pair<string, string> > someList;
If this were python I'd just use a list of tuples.
someList.append( ("herp", "derp") )
I'm looking for a library type, but will settle for something else if necessary. I'm trying to be LAZY and not have to write cYetAnotherTinyUtilityClass.cls to get this functionality, or fall back on the so-often-abused string manipulation.
I've tried googling around, but VB6 is not really documented well online, and a lot of what's there is, well challenged. If you've ever seen BigResource, you'll know what I mean.
Collections of Variants can be quite flexible and unless you are really beating on them performance is not an issue:
Private Sub SomeCode()
Dim Pair As Variant
Dim ListOfPairs As Collection
Set ListOfPairs = New Collection
With ListOfPairs
Pair = Array("this", "that")
.Add Pair
.Add Array("herp", "derp")
.Add Array("weet", "tweet")
MsgBox .Item(1)(0) 'Item index is base-1, array index base-0.
Pair = .Item(2)
MsgBox Pair(1)
ReDim Pair(1)
Pair(0) = "another"
Pair(1) = "way"
.Add Pair
MsgBox .Item(4)(1)
End With
End Sub
If its literally just for storage you can use a Type:
Public Type Tuple
Item1 As String
Item2 As String
End Type
Its a bit more concise than needing a class to do the storage.
The problem with Types (known more widely as UDTs) is that there are restrictions on what you can do with them. You can make an array of a UDT. You cannot make a collection of a UDT.
In terms of .Net they're most similar to Struct.
There's a walkthrough of the basics here or here.
I had a similar scenario and used Dictionary by including a reference to Microsoft Scripting Runtime library in my VB6 project. This was suggested by a colleague of mine and worked really well.
Dim dictionary As New Dictionary
Dim index As Integer
dictionary.Add "Index1", "value for first index"
dictionary.Add "Index2", "value for second index"
'To get the value for a key
Debug.Print dictionary("Key1")
'To get the value for all keys
For index = 0 To UBound(dictionary.Keys)
Debug.Print dictionary.Keys(index) & "=" & dictionary(dictionary.Keys(index))
Next index
List Class? (see VB section):
http://msdn.microsoft.com/en-us/library/6sh2ey19#Y0
Dictionary Class?
http://msdn.microsoft.com/en-us/library/xfhwa508
You could use a Collection
dim c as new collection
c.add "a", "b"

A generic VBscript function to connect to any database and execute any SQL query

the function should just take connection string and a SQL query as input and it should connect to any database(SQL, ORACLE, SYBASE, MS ACCESS) and execute any query which i have passed as the parameters to the function.
I have written the below function for that task, Can you please check this once and tell me is this correct or pls tell me if i am wrong anywhere.
#
Public Function ConnectDB (strCon, strQuery)
Set objConnection = CreateObject("ADODB.Connection")
Set objRecordSet = CreateObject("ADODB.Recordset")
objConnection.Open strCon
objRecordSet.Open strQuery,objConnection
objRecordSet.MoveFirst
Do Until objRecordset.EOF
Msgbox "Number of records: " & objRecordset.RecordCount
Msgbox objRecordset(0)
Msgbox objRecordset(1)
objRecordset.MoveNext
Loop
objRecordSet.Close
objConnection.Close
Set objConnection = Nothing
Set objRecordSet = Nothing
End Function
#
Call ConnectDB ("Provider = Microsoft.Jet.OLEDB.4.0; " & _
"Data Source = inventory.mdb","SELECT * FROM EMP ORDER BY EMPName")
UPDATE:
Thank you so much for the replies.
Actually i have been asked to write a function which performs the task of connecting to any database and executing any query(given by user) in that connected database.
I have started to learn VBScript and want to have indepth knowledge of writing functions. Ekkehard horner can you please tell me where can i read to get know all about routines(functions and sub procedure). Presently, i have only the basic idea on routines and i referred MSDN, where they have given only basic information. Please help me where to study more about routines. It is so difficult to write programs without knowing about them correctly.
Hi Sanpaco, below is the class i wrote. Please check it once and let me know the corrections.
I am very new to VBScript.Suggest me ways to improve my programming knowledge.
Class DBCommunicator
Public Function DBConnect(StrCon)
Option Eplicit
Dim oConn
set oConn = CreateObject("ADODB.Connection")
oConn.Open Strcon
Function DBConnect = True
End Function
Public Function QueryDB(StrQuery)
Option Eplicit
Dim oRst, oField
set oRst = CreateObject("ADODB.recordset")
oRst.Open "StrQuery", oConn
Do Until oRst.EOF
For each oField in oRst.Fields
Print oField.Name & " = " & oField.Value
Next
oRst.MoveNext
loop
Public Function DBdisConnect
oRst.close
oConn.close
End Function
End Class
########################################
Option Explicit
Dim strResult
strResult=DBCommunicator.DBConnect("<<Connection String of any database User want to connect>>")
If strResult<>True Then
wscript.echo "DB Connection Failed"
End If
DBCommunicator.QueryDB("Select * from EMP")
DBCommunicator.DBdisConnect
I fixed your code, not sure I agree with it but it seems to work. I don't think VB Script recognizes classes.
Option Explicit
'https://stackoverflow.com/questions/8429313/a-generic-vbscript-function-to-connect-to-any-database-and-execute-any-sql-query
'https://www.connectionstrings.com/microsoft-sql-server-odbc-driver/
Dim oConn, oRst
Public Function DBConnect(StrCon)
set oConn = CreateObject("ADODB.Connection")
oConn.Open Strcon
DBConnect = True
End Function
Public Function DBQuery(StrQuery)
Dim oField
set oRst = CreateObject("ADODB.recordset")
oRst.Open StrQuery, oConn
Do Until oRst.EOF
For each oField in oRst.Fields
wscript.echo oField.Name & " = " & oField.Value
Next
oRst.MoveNext
Loop
End Function
Public Function DBdisConnect
oRst.close
oConn.close
End Function
Dim strResult
strResult=DBConnect("<<Connection String of any database User want to connect>>")
If strResult<>True Then
wscript.echo "DB Connection Failed"
End If
DBQuery("Select * from EMP")
DBdisConnect
A routine (Sub or Function) should do exactly one repeatable/reusable task. Your
Function creates, opens, and closes a connection, creates, uses, and closes a
recordset, and annoys the user with message boxes. If you want to do something
sensible tomorrow, you'll have to write (by copy & paste & modify) another
routine.
A Function should return a value; yours doesn't. A Function should have no
side effects; yours does by doing IO. The work/doings of a routine should
be determined by its parameters alone; yours depends on the default settings/values
for the numerous parameters to the .Open methods you don't provide.
Code should not contain fat; .MoveFirst before a .EOF loop, displaying the
.RecordCount in the loop, and setting object variables to Nothing immediately
before the routine's end is just that. VBScript code should start with "Option
Explicit"; yours obviously doesn't.
While independency of a specific DBMS is attractive when you are learning
or investigating, a professional solution for a real world problem should
be based on the decision for the 'best' DBMS for the task; this will lead
to DBMS specific code using DBMS specific features. Then the switch from
one DBMS to another by changing just the ConnectionString is illusionary.
Database work is either of the "connect-do one thing-disconnect" style of
.Net's ADO or of the "connect on start-do many different things-disconnect on
termination" style of 'classical' ADO. If you indicate, what kind of tasks
you have in mind, I may be willing to append to this answer.
You might consider creating a DatabaseCommunicator class that does separate database functions (Connect, ExecuteQuery, Disconnect) instead of trying to do everything with one method. Also if you are wanting to use different types of providers dynamically then you would need to validate the query format to make sure it uses the correct syntax for whatever provider you are using.
I would be very interested in seeing how you accomplish this task should you take it on.

Resources