PDA

View Full Version : ODBC getting specific row



martin
19-07-2009, 15:45
Hi everybody,

I'm struggeling with this problem for a week now and it makes me (almost) crazy :shock31:

Suppose I have an ODBC database with 1 column ("name") and it's filled with 5 records:
record 1 = mike
record 2 = eros
record 3 = frank
record 4 = roberto
record 5 = petr

Now I make a query with SELECT * FROM ... WHERE name LIKE 'frank'

The query gives ofcourse 1 result in this case. Now I want to know what the REAL record number (row) is of this result (in this case row 3). How should I do that? I googled a lot, but still no satisfied answers found. I read something about Bookmarks and FetchScroll but I have no idea how to use this in Thinbasic.

Can anyone help me out here?

Thanks in advance,

Martin

Petr Schreiber
19-07-2009, 16:43
Hi Martin,

how big is the database?
I am not expert on this topic, so I could only solve it in 2 ways:

#1
- Use silly statement like "SELECT * FROM Authors"
- Then something like:


...
DIM Row AS LONG
DIM UserName AS STRING
DO
OdbcFetch hStmt
IF ISTRUE(OdbcError) THEN EXIT DO
Row += 1
UserName = OdbcGetDataString(hStmt, 1)
IF UserName = what I seek THEN Return Row
LOOP


... I guess it would take time for bigger DB.

#2
- Inject column with 1,2,3,... before executing the SELECT command

I am sorry, me and SQL ...


Petr

martin
19-07-2009, 16:55
Hi Petr,

Thanks for your reply.
The database can be very large so solution 1 is too slow.
Solution 2 does not work good anymore if the columns are sorted.

In reality i have a big database with 12 columns and i need this for a find next/previous function.

martin
19-07-2009, 16:55
Solution 2 does not work good anymore if the columns are sorted.

This should be: Solution 2 does not work good anymore if the columns are sorted descending.

John Spikowski
20-07-2009, 00:07
Martin,

If you need to keep track of 'record numbers' then the best thing to do is create a column with a auto incrementing column type. The problem with this method is you have to ignore this column in updates and inserts as the SQL server maintains it. Create secondary indexes on columns you need to sort by. This speeds up the query and is most noticeable if your doing joins with other tables.

Good luck with your quest.

John

martin
20-07-2009, 09:35
Hi John,

Thanks! An auto increment column sounds good to me! I didn't know that this was possible.

I made an auto increment column with CREATE TABLE Phonebook (SID IDENTITY PRIMARY KEY ........ )

Then I try to add a record:
cbSID = %SQL_COLUMN_IGNORE
szAnalyzed = "n":cbAnalyzed = 1
OdbcAddRecord hStmt

But no succes, the record will not be added. But if I remove %SQL_COLUMN_IGNORE and give SID a value the record will be added succesfully. So somehow %SQL_COLUMN_IGNORE does not work. Do you have any idea what I am doing wrong?

Kind regards,

Martin

John Spikowski
22-07-2009, 06:56
Hi Martin,

Just ignore the auto increment column in your INSERT and UPDATE SQL statements. The SQL server will assign the next sequential number with the next INSERT. If you include the column in a UPDATE you will get an error.

Only use the auto increment column with a SELECT / WHERE clause.

Hope this helps.

John

Lionheart008
24-07-2009, 00:40
hi martin, petr, hi john :)

I have found this material for odbc ;)

perhaps martin, you can use it, but it's just a source content for your work or idea... it doesn't run at all.. I have no deeper infos or knowledge about odbc, but some day I will need it too for my liongfx manual or syntax help.


'-- testscript for martin :) by lionheart
'-- not using, as it's not running, it's just a source for your work !!!

uses "console", "liongfx"


CONST MySQLHost = <MySQLServer>
CONST MySQLUser = <Username>
CONST MySQLPW = <Password>
CONST MySQLDB = <Datafile>

dim ServerInfo, u as string = MySQLInfo()
dim sel, result, a as integer

do

liongfx_CLS
liongfx_print: liongfx_print " MYSQL-example for thinbasic": liongfx_print
liongfx_print " Serverinfo: "+ ServerInfo
liongfx_print
liongfx_print " (1) show matrix"
liongfx_print " (2) insert data"
liongfx_print " (0) close, end"
liongfx_print
DO: LOOP WHILE liongfx_INKEY <> ""
liongfx_INPUT " select: ", sel
Select Case sel
Case 0 : liongfx_error,2
Case 1
If showMatrix() < 0 Then
liongfx_print " error."
liongfx_Sleep
End If
Case 2
liongfx_print
liongfx_INPUT " Username: " + u
liongfx_INPUT " Age: " + a
liongfx_print
result = MySQLInsert(u,a)
If result < 1 Then
liongfx_print " error " & result
ELSE
liongfx_print " new data: " & result
END IF
liongfx_SLEEP
Case Else
Beep
End Select
LOOP


FUNCTION MySQLInfo () As String

dim db as MYSQL ptr ' ?
Dim ServerInfo as String
db = mysql_init( NULL )
if( mysql_real_connect( db, MySQLHost, MySQLUser, MySQLPW, NULL, MYSQL_PORT, NULL, 0 ) = 0 ) then
liongfx_print "error: no connection to MySQL-Server "
mysql_close( db )
liongfx_SLEEP
end
end if
ServerInfo = *mysql_get_server_info( db )
Mysql_close(db)
Return ServerInfo
END FUNCTION

FUNCTION MySQLInsert (ByVal A As String, ByVal B As Integer) As Integer
dim db as MYSQL ptr
Dim Query As String
Dim ID As Integer
Dim result As Integer
Dim Fehler as ZString Ptr
db = mysql_init( NULL )
if mysql_real_connect( db, MySQLHost, MySQLUser, MySQLPW, NULL, MYSQL_PORT, NULL, 0 ) = 0 then Return -1
if mysql_select_db( db, MySQLDB ) then Return -2


query = "INSERT INTO testtab (`username`,`age`,`time`) VALUES (" & chr(34) & A & chr(34) & "," & chr(34) & B & chr(34) & ",CURRENT_TIMESTAMP())"
result = mysql_real_query (db,STRPTR(query),LEN(query))
If result = 0 THEN
ID = mysql_insert_id(db)
mysql_close(db)
Return ID
ELSE
Error = mysql_error(db)
If (*Error)[0] <> 0 Then
liongfx_print " "+ *mysql_error(db)
END IF
mysql_close(db)
Return -3
END IF
END FUNCTION

FUNCTION showMatrix () AS INTEGER
dim db as MYSQL ptr '?
dim row as MYSQL_ROW '?
dim res as MYSQL_RES ptr '?
Dim Query as String
Dim i as integer
db = mysql_init( NULL )
if (mysql_real_connect( db, MySQLHost, MySQLUser, MySQLPW, NULL, MYSQL_PORT, NULL, 0 ) = 0) then Return -1
if (mysql_select_db( db, MySQLDB )) then Return -2
query = "SELECT * FROM testtab ORDER BY id ASC"
If mysql_real_query (db,STRPTR(query),LEN(query)) <> 0 THEN
mysql_close(db)
Return -3
END IF
res = mysql_use_result(db)

CLS
liongfx_print: liongfx_print " useful data:": liongfx_print
liongfx_print " ID | Username | Age | Input-Time"
liongfx_print " --------------------------------------------------"

Do
row = mysql_fetch_row( res )
if( row = NULL ) then
Exit Do
ELSE
liongfx_print format$ " #### | \ \ | ### | &"+ Val(*row[0]), *row[1], Val(*row[2]), *row[3]
END IF
LOOP
mysql_close(db)

liongfx_SLEEP
RETURN 0
END FUNCTION


I adept it in some way from an old freebasic example for thinbasic-a-like-using and perhaps you can choose your things you needed for your work... but there is missing a lot of declarations about mysql stuff and much more...

I have translate it into 'lion english', but you are able to understand the content !!! :D
I am thinking this may help... 8)

best regards, Lionheart

martin
24-07-2009, 13:42
Hi All, thanks for thinking with me!

I'm still struggeling with creating an IDENTIFY column. I find examples on internet but it doesn't work in Thinbasic. But also I wondering if I really need auto incrementing as John adviced.

Example:
If I have these records:
record 1 = mike
record 2 = eros
record 3 = frank
record 4 = roberto
record 5 = petr
and make this query: SELECT * FROM mytable WHERE name LIKE 'frank' the auto increment field should return ROW 3 (from the whole table) and not row 1 (because row 1 is the only result). And for example when I sort the database decending, the auto increment field should return a different record (ROBERTO should be at ROW 1 then).

In the meantime I think this could be the solution:

dim r , RowNum as long
r=SQLGetStmtAttr(hStmt,%SQL_ATTR_ROW_NUMBER,RowNum,%SQL_IS_INTEGER,0)
printl "Current row is: " & RowNum
But the problem is that I get errors about BYVAL and BYREF if I try to use this code in my script.
Pffff....ODBC is more difficult then I thought. :oops: But I will never give up :lol:

Have a nice day!

Martin

Petr Schreiber
25-07-2009, 09:11
Hi Martin,

I checked the declare of SQLGetStmtAttr:


DECLARE FUNCTION SQLGetStmtAttr LIB "ODBC32.DLL" ALIAS "SQLGetStmtAttr" ( _
BYVAL StatementHandle AS DWORD, _
BYVAL Attribute AS LONG, _
BYREF Value AS ANY, _
BYVAL BufferLength AS LONG, _
BYREF StringLength AS LONG _
) AS INTEGER


So the BYREF is needed for the last one too. BYREF means it wants variable passed by reference, not value. So you can pass dummy variable:


dim r , RowNum as long
dim length as long = 0

r=SQLGetStmtAttr(hStmt,%SQL_ATTR_ROW_NUMBER,RowNum,%SQL_IS_INTEGER,length)
printl "Current row is: " & RowNum



It should stop complaining at least, cannot say if it works as I do not understand dark magic around ODBC/SQL that much.


Petr

John Spikowski
25-07-2009, 10:27
Martin,

I didn't realize that accessing the ODBC interface in thinBASIC was at the API level.

With the ScriptBasic ODBC/unixODBC extension module interface it only takes a few lines of code to access your data.



dbh = ODBC::RealConnect("DSN","user","password")

ODBC::query(dbh,"SELECT * FROM contact")

ODBC::FetchHash(dbh,column)

PRINT column{"ID"},"\n"
PRINT column{"NAME"},"\n"
PRINT column{"ADDRESS"},"\n"
PRINT column{"CITY"},"\n"
PRINT column{"STATE"},"\n"
PRINT column{"ZIP"},"\n"
PRINT column{"PHONE"},"\n"
PRINT column{"EMAIL"},"\n"
PRINT column{"URL"},"\n"


In the above example I'm using the associative array feature of the ODBC::FetchHash() function. The quoted names are the column names for the table.

You can use a standard array to return your results with the ODBC:FetchArray() function. I use this function when the results of my query may be from a join of multiple tables.

I thought someone here was working on a thinBASIC database module for MySQL or SQLite. I think an easy to use DB interface is imperative if thinBASIC is going to be used for general utilities or business applications.

John

martin
25-07-2009, 17:10
Hello Petr,

I must confess that I never exactly understood BYVAL and BYREF, I never used it. But you are right, your code is fine --> no errors anymore! Great! But unfortunately RowNum always returns value 0. Maybe this function is not 100% developed in ThinBasic yet. Maybe Eros can tell us...by the way where is our master? Very busy I guess?


To John:

hmmm maybe I must step over to Scriptbasic :-) Just kidding, my thinbasic script is too long now. And Thinbasic satisfy me at most points I need. But Scriptbasic language is also very interesting, I will certainly study it in the future (the same for TBGL, I really would learn that too someday!).
In the past I created an ADO database in Visual Basic. That was VERY easy to code. I saw an example on this forum for ADO in Thinbasic but I can't find the needed include-file. I guess Eros deleted it because Thinbasic can handle ODBC now.

Greetings,

Martin

Michael Hartlef
25-07-2009, 18:07
Hi martin,

which ODBC driver do you use?

Michael

Petr Schreiber
25-07-2009, 18:21
Hi Martin,

I like your spirit to keep fighting the problem! :occasion:

Well, ODBC header comes from José Roca - what Yoda is to mastering of Force, José Roca is to Win32 programming.
So to learn from source you can pay a visit to Dagobah his forum (http://www.jose.it-berater.org/smfforum/index.php?board=23.0) and learn from his examples.

They are in PowerBASIC, but PB is very close to ThinBASIC, so the learning process should be very smooth (ODBC examples use same API as we in ThinBASIC).
I see databases are very hot topic, I will do my best to prepare some resources on this topic for next ThinBASIC Journal.

BYVAL, BYREF ... this might seem complicated, but it is not.

When parameter is passed BYVAL, it means BY VALUE. So you can pass number/string, function returning value or variable.

BYREF is slightly different beast. It means BY REFERENCE. It needs variable, and it is interesting way allowing to manipulate external variables from inside of FUNCTION/SUB.

Regarding the Master :) - according to my last information Eros should be finishing some work by now and appear back here quite soon. I also miss his presence here, but I respect his work.

So if you have some time, please check out that Josés resource. If it does not answer your question, I will be happy to seek solution with you. José also provides some newer headers, I will check them and let you know.


Petr

Michael Hartlef
25-07-2009, 18:39
Yes martin,

thumbs up for your spirit. I like that. Maybe there is a bug in the header file. I hope to find time to rebuild what you do so I can seek for a solution myself.

Michael

Petr Schreiber
25-07-2009, 19:15
Hi,

I think SQLGetStmtAttr is ODBC 3.x+, but I am not able to create context for it on my PC (old ODBC driver?)
This is translated example from José Roca forum, including handy function to "describe error".



uses "Console"

#INCLUDE "%APP_INCLUDEPATH%\ODBC352.INC"

GLOBAL hEnv AS DWORD
GLOBAL hDbc AS DWORD
GLOBAL hStmt AS DWORD
' ========================================================================================
' Main
' ========================================================================================

FUNCTION TBMAIN()

LOCAL r AS INTEGER
LOCAL szBuf AS ASCIIZ * 256
LOCAL szInConnectionString AS ASCIIZ * 1025
LOCAL szOutConnectionString AS ASCIIZ * 1025

' Allocates the environment handle
r = SQLAllocHandle (%SQL_HANDLE_ENV, %SQL_NULL_HENV, hEnv)
PRINTL "Allocating handle..."
IF ISFALSE SQL_SUCCEEDED(r) OR ISFALSE hEnv THEN EXIT FUNCTION

' Tells to the driver manager that is an application that uses the ODBC driver 3.x
LOCAL ByvalValue AS LONG = %SQL_OV_ODBC3
LOCAL ByvalValue2 AS LONG
r = SQLSetEnvAttr (hEnv, %SQL_ATTR_ODBC_VERSION, ByvalValue, %SQL_IS_INTEGER)
PRINTL "Trying to set ODBC version to 3.x..."
IF ISFALSE SQL_SUCCEEDED(r) THEN
printl SQLGetErrorInfo(%SQL_HANDLE_ENV, hEnv)
Terminate()
END IF

' Allocates the connection handle
r = SQLAllocHandle (%SQL_HANDLE_DBC, hEnv, hDbc)
IF ISFALSE SQL_SUCCEEDED(r) THEN Terminate()
PRINTL "Connection handle allocated..."

' Connection string
szInConnectionString = "DRIVER={Microsoft Access Driver (*.mdb)};" & _
"DBQ=biblio.mdb;UID=;PWD=;"
' Connects with the ODBC driver
ByvalValue = 0
r = SQLDriverConnect (hDbc, _
%HWND_DESKTOP, _
szInConnectionString, _
LEN(szInConnectionString), _
szOutConnectionString, _
SIZEOF (szOutConnectionString), _
ByvalValue, _
%SQL_DRIVER_COMPLETE)

PRINTL "Connected..."

' Check for errors
IF ISFALSE SQL_SUCCEEDED(r) THEN
Terminate
END IF

' Allocates an statement handle
r = SQLAllocHandle (%SQL_HANDLE_STMT, hDbc, hStmt)
IF ISFALSE SQL_SUCCEEDED(r) OR ISFALSE hStmt THEN Terminate
' Cursor type
ByvalValue = %SQL_CURSOR_KEYSET_DRIVEN
r = SQLSetStmtAttr(hStmt, %SQL_ATTR_CURSOR_TYPE, ByvalValue, %SQL_IS_UINTEGER)
' Optimistic concurrency
ByvalValue = %SQL_CONCUR_VALUES
ByvalValue2 = %SQL_IS_UINTEGER
r = SQLSetStmtAttr(hStmt, %SQL_ATTR_CONCURRENCY, ByvalValue, ByvalValue2)

' Generates a result set
r = SQLExecDirect (hStmt, "SELECT TOP 20 * FROM Titles ORDER BY Title", %SQL_NTS)
' Check for errors
IF ISFALSE SQL_SUCCEEDED(r) THEN
PRINTL SQLGetErrorInfo(%SQL_HANDLE_STMT, hStmt, r)
Terminate
END IF

' Get the current setting or values of the descriptor record
' for the 9th field ("Price")

LOCAL hIpd AS DWORD
LOCAL szName AS ASCIIZ * 256
LOCAL cbLength AS LONG
LOCAL Precision AS INTEGER
LOCAL nLength AS LONG

ByvalValue = 0
r = SQLGetstmtAttr(hStmt, %SQL_ATTR_IMP_ROW_DESC, hIpd, %SQL_IS_UINTEGER, ByvalValue)
IF ISFALSE hIpd THEN Terminate
r = SQLGetDescField(hIpd, 9, %SQL_DESC_NAME, szName, SIZEOF(szName), cbLength)
r = SQLGetDescField(hIpd, 9, %SQL_DESC_PRECISION, Precision, %SQL_IS_SMALLINT, cbLength)
r = SQLGetDescField(hIpd, 9, %SQL_DESC_OCTET_LENGTH, nLength, %SQL_IS_INTEGER, cbLength)

printl szName
printl Precision
printl nLength

Terminate()
END FUNCTION
' ========================================================================================

SUB Terminate()
PRINTL "Terminating"
' Closes the cursor
IF hStmt THEN SQLCloseCursor(hStmt)
' Closes the statement handle
IF hStmt THEN SQLFreeHandle(%SQL_HANDLE_STMT, hStmt)
' Closes the connection
IF hDbc THEN
SQLDisconnect(hDbc)
SQLFreeHandle (%SQL_HANDLE_DBC, hDbc)
END IF
' Frees the environment handle
IF hEnv THEN SQLFreeHandle(%SQL_HANDLE_ENV, hEnv)
waitkey
STOP
END SUB

' ========================================================================================
' SQLGetErrorInfo uses SQLGetDiagRec to retrieve an error description.
' HandleType must be one of the following:
' %SQL_HANDLE_ENV
' %SQL_HANDLE_DBC
' %SQL_HANDLE_STMT
' %SQL_HANDLE_DESC
' hndl is the handle of the environment, connection, statement or descriptor.
' ========================================================================================
FUNCTION SQLGetErrorInfo (BYVAL HandleType AS INTEGER, BYVAL hndl AS DWORD, OPTIONAL BYVAL iErrorCode AS INTEGER) AS STRING

LOCAL iResult AS INTEGER
LOCAL szSqlState AS ASCIIZ * 6
LOCAL lNativeError AS DWORD
LOCAL szErrMsg AS ASCIIZ * %SQL_MAX_MESSAGE_LENGTH + 1
LOCAL strErrMsg AS STRING
LOCAL cbbytes AS INTEGER
LOCAL ErrorCount AS LONG
LOCAL i AS LONG

iResult = SQLGetDiagField(HandleType, hndl, 0, %SQL_DIAG_NUMBER, ErrorCount, %SQL_IS_INTEGER, cbbytes)

IF ErrorCount THEN
FOR i = 1 TO ErrorCount
iResult = SQLGetDiagRec(HandleType, hndl, 1, szSqlState, lNativeError, szErrMsg, SIZEOF(szErrMsg), cbbytes)
IF iResult <> %SQL_SUCCESS AND iResult <> %SQL_SUCCESS_WITH_INFO THEN EXIT FOR
strErrMsg = strErrMsg & "SqlState: " & szSqlState & $CRLF & _
"Native error: " & FORMAT$(lNativeError) & $CRLF & szErrMsg
NEXT
ELSEIF iErrorCode THEN
SELECT CASE iErrorCode
CASE -1 : strErrMsg = "SQL error" ' "SQL_ERROR"
CASE 2 : strErrMsg = "Still executing" ' "SQL_STILL_EXECUTING"
CASE -2 : strErrMsg = "Invalid handle" '"SQL_INVALID_HANDLE"
CASE 99 : strErrMsg = "Need data" ' "SQL_NEED_DATA"
CASE 100 : strErrMsg = "No data" '"SQL_NO_DATA"
CASE ELSE
strErrMsg = "Error " & FORMAT$(iErrorCode)
END SELECT
END IF

FUNCTION = $CRLF+"Error occurred:"+$CRLF+strErrMsg+$CRLF

END FUNCTION
' ========================================================================================
FUNCTION SQL_SUCCEEDED(byval param as long) AS LONG
if param = %SQL_SUCCESS or param = %SQL_SUCCESS_WITH_INFO THEN Return 1
Return 0
END FUNCTION

John Spikowski
25-07-2009, 21:13
hmmm maybe I must step over to Scriptbasic :-) Just kidding, my thinbasic script is too long now. And Thinbasic satisfy me at most points I need. But Scriptbasic language is also very interesting,


My goal with ScriptBasic is to provide a easy to use general purpose scripting language based on a traditional Basic syntax that runs on everything. I think there is a need for a language tool to solve one off tasks that doesn't require a development budget to get the job done.

ScriptBasic is fast with a < 500KB foot print that's was designed from the ground up to be embeddable. The console mode interpreter and multi-threaded HTTPD server are variation examples of language API.

Give it a try and I'm sure it will find a place in your development toolbox.

John Spikowski
26-07-2009, 02:09
I noticed that my CGI / ODBC example on the www.scriptbasic.org site was returning a error. It worked fine on the old server. :read: The problem ended up being a permission issue for the .odbc.ini file in the site home directory. (ODBC trace to the rescue)

http://www.scriptbasic.org/forum/index.php/topic,4.0.html

martin
26-07-2009, 09:50
Good morning Mike, Petr & John :arms:

I think the spirit to keep fighting a problem should be the spirit of every programmer. If I can't solve this problem, I will remove all ODBC code and try something else.
Ofcourse I already noticed José Roca's website and forum as it is mentioned in the ThinBasic Helpfile. I did a lot of research there and also googled a lot but as I said no succes. But maybe I should post a message at that forum, that might help.

I use version 2.5 of ODBC so that could be the problem indeed. So Petr I can't run your translated example as well. And I don't want to install another driver because I want that potential users of my script can run it without installing of any drivers.

To John: I will give ScriptBasic a try for sure. But first I want to finish my thinbasic script.

All the best,

Martin

John Spikowski
27-07-2009, 00:46
To John: I will give ScriptBasic a try for sure. But first I want to finish my thinbasic script.

Problem solving is life's best teacher. :violent:

I would like to see the thinBASIC community take a serious look at creating a ODBC extension module that is easy to use. Sooner or later you will need to move beyond spiffy games.

After a quick search of the thinBASIC site I found this link in the modules help section.

ODBC Include (http://www.thinbasic.com/public/products/thinBasic/help/html/odbc.htm)

Have you tried this include file Martin? Is this what your trying to get working?

Michael Clease
27-07-2009, 13:38
I would like to see the thinBASIC community take a serious look at creating a ODBC extension module that is easy to use. Sooner or later you will need to move beyond spiffy games.


John if its that important to you then why dont you write the ODBC module?

Some people have no interest in ODBC, now you have pointed out my error I will rewrite my windows key revealer to use OBDC because thats just what it needs. :fool:

off to write somemore spiffy games now.

John Spikowski
27-07-2009, 18:12
John if its that important to you then why dont you write the ODBC module?


Michael,

I do very little development under Windows anymore. I use the MySQL C API interface for most my DB needs. I suggested the ODBC module for thinBASIC as it gives the Windows programmer a greater selection of client options and Microsoft includes the management tools as part of the OS.

Maybe I was a bit harsh with the "something more then games" comment but without DB support, it really limits thinBASIC's use for most utility based applications which I assume was the void thinBASIC was trying to fill. If the direction is something else, my apology.

John

ErosOlmi
21-08-2009, 11:48
TOPIC MOVED to ODBC specific forum.
_________________________________________________

In DBMS record position inside the DB has no meaning because the position where the record is stored is demanded to the DBMS engine. And decision to where to store data is subject to many factors: from DBMS engine rules to optimization factors.

What is important in order to be able to identify your data is a KEY: a value (or a set of values) used to retrieve or connect other info in the DBMS or in outside references. Usually an auto increment unique field counter maintained by the DBMS itself is enough. Many applications instead use counters plus personalized fields (like, for example, a RecordID) maintained by personalized counters: InvoiceNumber, OrderNumber, CustomerID, PaymentCode, ...
Once you have identified your main data with a SELECT statement returning just one row, you will use whatever ID stored in your main record to retrieve data from other source of info: related tables in the same or other DB or external info.

In designing DB, a great attention must be placed to KEYs and ID. In the posted example, record number should be replaced with the concept of KEY field so something like:
record 1 = mike
record 2 = eros
record 3 = frank
record 4 = roberto
record 5 = petr

should be replaced with something like
RECID, UserName, ...
1, mike, ...
2, eros, ...
3, frank, ...
4, roberto, ...
5, petr, ...

Where RECID is a unique identifier (in this case a DBMS maintained counter is OK) used to link every user with it's data and to other sub data in linked tables ( Relational_model) or in external references.

I will try to write down an example using an auto increment counter and post here.

Regarding why I followed José way to interface with ODBC is exactly because it is low level interface with API and if you get it, you get ODBC in one go. After that you can write any wrapper. But if there will be real interest, I can think to write a specific thinBasic module.

Eros