B4J Question Funny Problem With Maps: Map.ContainsKey(row(x))

rgarnett1955

Active Member
Licensed User
Longtime User
Hi,

I am inserting records into an sqLite table. I am using REPLACE rather than insert as I have a primary key constraint. Before I do the insert I check if there as records in the target table with primary keys that match the ones I wish to insert. I do this so I can flag the records in the target table as being replaced rather than original.

I search for each primary key in the target if the key exists I Put the value in a "duplicates" map. The key and value I put in each map entry are the same; double value (the julian date/time)

So my duplicates map ends up with:

Key Value
2458790.55902778 2458790.55902778
2458790.5625 2458790.5625
...

When I do the REPLACE query I check whether the primary key field value is in the duplicates map and if it is I enter the DateTime ticks into the "conflictPrimKey" field of the table otherwise I enter Null into this field.

The parameters to be inserted in the query are held in a List called aggListArg.

List Item 0 => row(0) = 2458790.55902778
List Item 1 => row(0) = 2458790.5625


If I use the following code valid keys are not found:

This never finds keys in the map:
        If duplicates.ContainsKey(row(0)) Then
            ArgArray.Add(batchNo)
        Else
            ArgArray.Add(Null)
        End If

If I use this code it works fine

This works:
    Dim primaryKey As Double = row(0)

    If duplicates.ContainsKey(primaryKey) Then
        ArgArray.Add(batchNo)
    Else
        ArgArray.Add(Null)
    End If

I wonder what I'm doing wrong.

When you look at row(0) and the map key/values in the debugger they appear identical yet looking for the row(0) value in the map doesn't work.

I have attached a little program that demo's this issue.

Test Program:
Sub AppStart (Args() As String)
    Dim duplicates As Map
    duplicates.Initialize
    
    Dim aggListArg As List
    aggListArg.Initialize
    
    duplicates.Put(2458790.55902778, 2458790.55902778)
    duplicates.Put(2458790.5625,     2458790.5625)
    
    aggListArg.Add(2458790.55902778)
    aggListArg.Add(2458790.5625)
    
    For Each row As String In aggListArg
        Dim primaryKey As Double = row
        
        Log(" ")
        Log("Using intermediate variable - primaryKey")
        If duplicates.ContainsKey(primaryKey) Then
            Log("   Add Batch No - Correct")
        Else
            Log("   Add Null     - Incorrect")
        End If
        
        Log("Using row value")
        If duplicates.ContainsKey(row) Then
            Log("  Add Batch No - Correct")
        Else
            Log("   Add Null     - Incorrect")
        End If
    Next
    StartMessageLoop
End Sub

Log Output:
Waiting for debugger to connect...
Program started.
 
Using intermediate variable - primaryKey
   Add Batch No - Correct
Using row value
   Add Null     - Incorrect
 
Using intermediate variable - primaryKey
   Add Batch No - Correct
Using row value
   Add Null     - Incorrect

Any ideas?

Best regards
Rob
 

Attachments

  • MapContainsKey.zip
    1.4 KB · Views: 127

stevel05

Expert
Licensed User
Longtime User
Keys of Maps have to be of the same type when you access them as when you create them.

Your problem is in this line:

B4X:
For Each row As String In aggListArg

Which should be

B4X:
For Each row As Double In aggListArg

As the keys in the Map are stored as Doubles.

Numeric literals without a decimal point are treated as Integers, with a decimal point they are treated as doubles.

To satisfy yourself try this:

B4X:
Log(GetType(200))
Log(GetType(200.0))
 
Last edited:
Upvote 0

rgarnett1955

Active Member
Licensed User
Longtime User
Hi Steve

I thought that was the problem, that is why I used the intermediate variable primaryKey as Double.

Where the issue arises is that I use the DBUtils.ExecuteMemoryTable to get my db data.

Execute Memory Table:
Public Sub ExecuteMemoryTable(SQL As SQL, Query As String, StringArgs() As String, Limit As Int) As List
    Dim cur As ResultSet
    If StringArgs = Null Then
        Dim StringArgs(0) As String
    End If
    cur = SQL.ExecQuery2(Query, StringArgs)
'    Log("ExecuteMemoryTable: " & Query)
    Dim table As List
    table.Initialize
    Do While cur.NextRow
        Dim values(cur.ColumnCount) As String
        For col = 0 To cur.ColumnCount - 1
            values(col) = cur.GetString2(col)
        Next
        table.Add(values)
        If Limit > 0 And table.Size >= Limit Then Exit
    Loop
    cur.Close
    Return table
End Sub

This treats all columns of the database table as Strings and disregards the data type.

I wasn't happy about this at the time, but I thought I'd get away with it.

I think I will create my own version of ExecuteMemoryTable that preserves the data type in the output list. I guess a list can have different types for each column?

Best regards
Rob
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
There is no problem with casting a string to a double, provided that it is a valid number which you can check with the isNumber operator.
 
Upvote 0

tchart

Well-Known Member
Licensed User
Longtime User
If you're mixing types then use Object in the For loop

B4X:
For Each row As Object In aggListArg

But that means you need to check the type using GetType before assigning the row - so its simpler to keep them the same type and be explicit like @stevel05 said.
 
Upvote 0

tchart

Well-Known Member
Licensed User
Longtime User
Also be careful when using the Null keyword, there is lot of discussiuon around not using them as null <> Null <> "null"

 
Upvote 0
Top