Read only scrollable snapshot cursors can be easily implemented in the same way 
as they are implemented in other database engines.  You just have to do the 
work yourself.  Example:

cn = sqlite3.connection(...)
cr = cr.cursor()

# create the scrollable cursor

cr.execute('create temporary table myscrollablecursor as select [columns] from 
[various tables] where [join conditions] order by [columns];')
maxrow = cr.execute('select max(rowid) from myscrollablecursor;').fetchone()[0]

# to retrieve the data forward from row do

cr.execute('select rowid, * from myscrollablecursor where rowid >= ? order by 
rowid;', (row,))

# to retrieve rows backward from row do

cr.execute('select rowid, * from myscrollablecursor where rowid <= ? order by 
rowid desc;', (row,))

# to close the scrollable cursor do

cr.execute('drop temporary table myscrollablecursor;')



You retrieve the data from the cursor as you now do (cr.fetchone, cr.fetchmany, 
iterator, whatever).  item 0 of the result tuple is the current row number.  
You can even have random access to the rows based on the row number if you so 
desire.  If you need multiple scrollable cursors, at the same time you just 
need to have different cursor() objects and temporary table names for each.  
These are what other drivers might term a SERVER-SIDE SCROLLABLE SNAPSHOT.  If 
you wish to see updates you need to drop and re-create the snapshot.  Be aware 
that while you have the cursor() open and are navigating about, the database is 
locked from updates.

You can do things in chunks if you want to by using queries of the form:

rows = cr.execute('select rowid, * from myscrollablecursor where rowid >= ? 
limit ? order by rowid;', (page * pagesize, pagesize)).fetchall()
rows = cr.execute('select rowid, * from myscrollablecursor where rowid <= ? 
limit ? order by rowid desc'', ((page+1)*pagesize-1, pagesize)).fetchall()

where maxpage = int((maxrows + pagesize - 1) / pagesize)

which will retrieve you pagesize chunks to play with and reset the cursor 
between page retrievals (thus unlocking the database).  Your last row is, of 
course, rows[-1] when reading forward the page = maxpage.

Doing a keyset cursor covering multiple tables is more complicated because you 
need to either hand craft it for each keyset query or do significant work to 
parse the database schema to store and retrieve the desired information in 
order to generalize it.  Even for a keyset driven cursor, only the data will 
see committed updates.  The keyset is a snapshot and new rows will not appear 
until you regenerate the keyset snapshot, and you have to be prepared to deal 
with rows that have been deleted from the database but still show up in the 
keyset (but cannot be retrieved -- same if the primary key of a row in a table 
referenced in the snapshot is updated which will make it look as if the result 
was deleted).  That is why keyset cursors usually have all the complicated bits 
implemented within the database engine itself.

-----Original Message-----

You cannot do any of these things in any relational database.  You can only do 
this in navigational databases.  There are various kludges which permit you to 
simulate navigational abilities on top of relational databases, but they are 
all kludges which are performed by various forms of fakery to impose 
navigability on top of a model which inherently is not navigable.  

For example, many database drivers can kludge you up what is called a "keyset" 
driven cursor.  It does this by executing the query and storing a temporary 
table containing the primary keys of each table in the query for each result 
row (this is stored either in the driver (for a client-driven keyset) or on the 
server (for a server driven keyset).  When you ask for a row from the keyset, 
the primary keys are used to issue a bunch of queries to "reconstruct" the 
"present view" of the result that would be at that navigational location for 
you.  There are also, usually in these same drivers, what are called 
"scrollable" cursors.  These differ from a "keyset" cursor in that the query 
result set is stored in a temporary table (rather than a keyset table).  The 
driver can then "pretend" there is ordering and record numbers on the results 
and can internally reissue queries against the temporary table and its row 
numbers so that it appears you can scroll forwards and backwards and access rand
 om rows of the result set (this type of cursor is almost always implemented on 
the server as a temp table and the only information sent to the client are the 
extents of the rowset).  The key difference is that keyset cursors can be used 
to update the database (since you have the primary keys for the original data 
rows stored away) whereas plain scrollable cursors are read only.

In some systems these types of capabilities exist solely in the drivers.  In 
others, there is inherent support in the database engine itself.  There are 
even cases where there is a combination of both, or where you can select 
whether the support should be implemented server-side or client-side.  In some 
cases the choice of implementation method is taken away from you in order to 
protect you from doing something "abysmal", such as retrieving the primary keys 
for a billion row keyset into driver storage.)

This is a kludge to give the appearance of navigational capabilities where they 
inherently do not exist.

The other way to do it is the way primitive folks do it -- retrieve the entire 
result set into a big list of records in your programs storage, and then 
navigate through your list in memory.  This is popular with Microsoft tools, 
for example, and is why most Microsoft tools take aeons to open (try to open 
the event viewer on a busy Microsoft server, or open DSA against a domain with 
a several hundred thousand objects.  You come in to work and sign on, then open 
the tool and lock the screen, then go for breakfast, and meetings, and lunch.  
When you return after lunch the tool is ready to use.  Or it has crashed 
because it ran out of memory.)

It is also quite common for "bitty system" developers to do this.  Everything 
works swimmingly well on their test database with 5 customers and 3 products.  
However, once the "production" database is loaded that contains a few thousand 
customers and several millions of products and components, the system craps out 
or is abysmally slow.  You then read stories in the newspaper about how some 
company (or government) spent hundreds of millions or billions of dollars on a 
failed computer system.

There are still other products which do not provide drivers which kludge up any 
of these illusions for you, and the database engine does not have the baked in 
complication to assist with the creation of these illusions.  For these systems 
you have to do all the skull-duggery yourself.  SQLite falls into this 
category.  I don't think anyone was written a "driver" which implements this in 
automated fashion either, so you have to "roll your own" as it were.

>I'm racking my brain trying to figure out how to get directly to the last
>item in a (potentially) sorted or ordered table.  At least oe of the 
>tables will be ordered by a name and a date, so uising the rtowid won't work.

Read all the results until you run out of results.  At this point the last 
result you successfully retrieved was the last.  If this takes too long then,

>Also, how to traverse a table or cursor in a reverse direction.

Issue the same query again, and "reverse" the sort order of each column in the 
group by clause.  You will now retrieve the result set in the opposite order 
and the "first" row will be the "last" and the "last" shall be "first".  If you 
wish to start "in the middle" devise some constraints to add (to the where 
clause) which determine the appropriate slicing of the result set.

>Going from first to last is easy in pysqlite, just use fetchone().
>Geting directly to the first record is also easy, just open the 
>desired table and the first fetch is the first record.

No, the first fetch returns the first row of the result set.  If and only if 
you issued a query with an order by clause giving a specific fully determined 
ordering to the results is ordering guaranteed.  It is the order by clause 
which determines the order.  If you do not have an order by clause in the 
query, then the row that you are calling the "first" row is only that by 
happenstance and the gyrational flux of subatomic particles at the time of the 
observation.  Be aware, however, that like Schroedingers Cat, your observation 
of the subatomic gyrations will fix the duality in a random fashion that may be 
different for each observation.

>But, if I'm, for example,  at the 10th record of an ordered table
>(cursor), how do I go backward one or more times?  

Issue an appropriate query to generate a result set containing the set of 
result rows that you want in the order that you desire, and retrieve them.

>And how do I go directly to the last?

Re-issue the query with the order by clause inverted (change ASC to DESC and 
DESC to ASC for every item in the order by clause).  Now when you retrieve the 
"first" row you will get what was the "last" when ordered in the opposite 
direction, and the "last" will now be "first".

Set theory, relational algebra, and the higher mathematics on which relational 
databases are built is both extremely complex and amazingly simple all at the 
same time.  The first thing you have to do is realize that there is no such 
thing as "order".  If you want "order", you must ask for it explicitly.  
Relying on happenstance is not a substitute and is unwise.  Also, the 
row-by-row access wherein you think you are "stepping through" a result set is 
an illusion which is created by the technological limitations resulting from 
adapting relational and set operations to the unfortunately linear basis of 
human procedural implementation of the technology.

See:  http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor for simple examples. 
 There are certainly lots of others.




_______________________________________________
sqlite-users mailing list
sqlite-users@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to