Sybase ASE
All versions
Last updated: 30 June 2004
ASE Quiz Questions: answers January - June 2002
 
More ASE quiz questions: 2013 - 2012 - 2011 - 2010 - 2009 - 2008 - 2007 - 2006 - 2005 - 2004 - 2003 - 2002 - 2001
For a randomly selected quiz question, click here.


December 2002
Is it possible to create a table without a name ?

Answer:
The answer is both 'yes' and 'no', depending on your interpretation of the word 'without'...

First, the obvious 'no': of course, each object must have a name: sysobjects.name does not allow NULL values.

The 'yes' answer is far more interesting: although, technically, an object always has a name, this does not mean that this name is always easily recognised as such. Try the following T-SQL statement:
1> set quoted_identifier on
2> go
1> create table " " ( " " int)
2> go
This creates a table with a name consisting of a single space. This is possible because we've enabled the quoted_identifier option, which allows object names with spaces. In fact, the column name is also a single space.
When doing an sp_help " ", the table name and column name are dutifully displayed, but to the human eye, it will seem that no name is displayed at all (you may have to experience this yourself to understand what happens -- so I'll leave running sp_help " " as an exercise for the reader...)

When accessing this table, the quoted name should always be specified:
1> set quoted_identifier on
2> go
1> insert " " values (123)
2> go
(1 row affected)
1> select * from " " 
2> go

 -----------
         123

(1 row affected)

Now, try the following:
1> set quoted_identifier on
2> go
1> create table "  " ( " " int, "  " int)
2> go
1> create table "   " ( " " int, "  " int)
2> go
These statements create two more tables with names consisting of two and three spaces, respectively. Note that each table has two columns, whose names consist of spaces as well.

Returning to the original question: the object name is normally expected to be an identifying and distinguishing attribute of a database object. Technically, that is the case even when these names consist of spaces. However, from a human viewpoint, it could be argued that these aren't really object names because they cannot be recognised as such.

If you don't know that such object names exist in your database, you will probably have a hard time figuring out what's going on. The usual query on sysobjects is not very useful:
1> select name, type, id
2> from sysobjects where id > 99
3> go
 name                           type id          
 ------------------------------ ---- -----------
                                U      228500368
                                U      260500482
                                U      308500653
(3 rows affected)
Instead, try this:
1> select '['+name+']' name, type, id 
2> from sysobjects where id > 99
3> go
name                             type id          
-------------------------------- ---- -----------
[ ]                              U      228500368
[  ]                             U      260500482
[   ]                            U      308500653

(3 rows affected)
By delimiting the object name with brackets, you should be getting a clue...

Lastly, if you want to scare your DBA (or one of your colleagues), try creating a table like this:
1> set quoted_identifier on
2> go
1> create table "***dbcc corruption error!***" 
2> ( "Database corrupt!" int, "Infected with 11!" int)
3> go
Now, just wait until the victim finds out about this table...
(maybe you shouldn't do this in a production database...)

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
November 2002
Suppose you're writing some T-SQL code to produce a report. You want the first lines to be formatted like this:
***************
  R E P O R T
***************
What is the least amount of T-SQL code needed to print these three lines ?

Answer:
Just one print statement is enough:
1> print "
2> ***************
3>   R E P O R T
4> ***************
5> "
6> go

***************
  R E P O R T
***************
What we're using here is the little-known fact that string expressions may extend over multiple lines (I'm not sure whether this is actually documented). All characters between the opening quote on line 1 and the closing quote on line 5 form a string, including the newline characters.
I'm sure you'll appreciate the simplicity of this code, especially when compared with the other, more common, methods below.

Most people would probably have used a separate print statement for each line, or coded the newlines explicitly by using their ASCII value:
1> declare @nl char(1) 
2> select @nl = char(10)
3> print "***************%1!  R E P O R T%2!***************",
4> @nl, @nl
5> go
Note that a single select statement can also be used, but you'll need to add an extra space in column 1 on lines 2 and 3 (because select output by default starts with a space) for each row in the result set):
1> select "***************" + char(10) + "   R E P O R T" + 
2> char(10) + " ***************"
3> go

 -----------------------------------------------------------
 ***************
   R E P O R T
 ***************
(note the dashed line: this is not printed by the select statement, but by isql. To suppress this line, start isql with the -b option)

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
October 2002
Each ASE release is identified by an "EBF number" (also called a "SWR number") in its version string. This EBF number is unique, meaning that Sybase TechSupport can tell exactly which functionality (like bug fixes) is included in the specific ASE version you're running.

The question: is it possible that two different ASE executables have the same EBF number ?

Answer:
The short answer: yes, strictly speaking, two ASE executables can indeed have the same EBF number -- but only in one particular situation.

The long answer:
As the EBF number forms a unique identification of an specific ASE release, it would seem illogical that two ASE executables could have the same EBF number.
Still, this happens!
In fact, it happens all the time: each EBF contains both a dataserver (NT: sqlsrvr.exe) and a diagserver (NT: diagsrvr.exe), and by definition, these executables have the same EBF number:
% dataserver -v 
Adaptive Server Enterprise/12.5.0.1/EBF 10292 IR/P/Linux 
Intel/Linux 2.4.7-10smp i686/rel12501/1781/32-bit/OPT/Thu 
Jun  6 14:14:09 2002
(...)

% diagserver -v
Adaptive Server Enterprise/12.5.0.1/EBF 10292 IR/P/Linux 
Intel/Linux 2.4.7-10smp i686/rel12501/1781/32-bit/DEBUG/Thu 
Jun  6 14:31:26 2002
(...)

As you can see, these executable have the same EBF numbers, but different version strings: the diagserver has DEBUG where the dataserver has OPT (OPT means "optimized"; on other platforms (e.g. Solaris), you may see FBO ("Feedback Optimized") instead of OPT).
Instead of "EBF", you may also see "SWR" preceding the EBF number. "EBF" and "SWR" mean the same thing, and were both used in the past. Recently, "SWR" has been abandoned in favor of "EBF".
(for completeness: diagserver is a diagnostic version of dataserver; you're not supposed to run diagserver unless instructed by Sybase TechSupport).
Note BTW, that the executables were compiled at different times (apparently it takes about 17 minutes to build a dataserver ?)

I admit this is a bit of a trick question - but I'm pretty sure many DBAs would have answered "no" at first sight...

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
September 2002
It is a well-known fact that all objects in an ASE database (tables, procedures, triggers, etc.) have a unique object ID. These object IDs are stored in sysobjects, among other tables.

The question: is it possible that two different objects in the same ASE database actually have the same object ID ?

Answer:
Strange as it may sound, this is indeed possible -- but only in one very specific situation.
Consider the following code:
1> create procedure p;1 as print "This is p1 !"
2> go

1> create procedure p;2 as print "This is p2 !"
2> go

1> exec p;1
2> go
This is p1 !
(return status = 0)

1> exec p;2
2> go
This is p2 !
(return status = 0)
What we're using here is the "procedure grouping" feature of ASE. The stored procedures p;1 and p;2 have the same name (i.e. 'p'), but a different version number -- the semicolon is mandatory syntax. Each procedure can be executed separately by explicitly specifying the version number. When executing, these procedures are fully independent.
(note that you may have difficulties to run the above commands when using sqsh, because sqsh uses the semicolon as a batch terminator. To fix this, edit the .sqshrc file and remove the line saying \set semicolon_hack=on, or use isql or another client instead)

BTW, when you don't specify the procedure version number, the default is 1:
1> exec sp_who;1
2> go
-- this will work !
Below the covers, these two stored procedures are actually treated as one single object: when you look in sysobjects and sysprocesses, you'll see that there is only one procedure p, with 'number' values 1 and 2:
1> select object_id("p;1")
2> go

 -----------
        NULL  <== this means that procedure "p;1" 
                  does not exist as such!


1> select object_id("p")
2> go

 -----------
  1904804621  <== ...but procedure "p" does exist!



1> select name, type, id 
2> from sysobjects where name like "p%"
3> go
 name                           type id
 ------------------------------ ---- -----------
 p                              P     1904804621


1> select distinct id, number 
2> from sysprocedures where id = 1904804621
3> go
 id          number
 ----------- ------
  1904804621      1
  1904804621      2

So this is the only situation where two different stored procedures have the same object ID. Actually, these procedures can only be dropped together, not individually:
1> drop proc p;1
2> go
Msg 102, Level 15, State 1:
Server 'S125', Line 1:
Incorrect syntax near ';'.

1> drop proc p
2> go

-- now both p;1 and p;2 have been dropped...

I think this 'procedure grouping' feature is one of the more bizarre areas of ASE functionality. It has been in ASE as long as I can remember (but I think I only discovered this feature in version 4.9 or 10). In any case, 'procedure grouping' is not ANSI-compliant, but a Sybase-specific SQL extension.
The idea behind 'procedure grouping' is to guarantee consistency between a number of stored procedures, because these cannot be dropped and recreated individually. Still, I have never had a requirement for something like this, and I have never seen this feature actually being used either. So if anyone has ever used this, I'd be interested to know more about the reasons why!.

Lastly, note that there is a configuration option "allow procedure grouping": when this is set to 1 (=default), procedure grouping is allowed; when set to 0, it is not allowed.

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
August 2002
Suppose you want to know when your ASE server was started. How many different ways would you know to determine this ?

Answer:
There are quite a few ways to find the last start date/time of an ASE server. I get as far as nine (though readers of this page have contributed additional ways -- see below):
  1. Probably the most obvious way is to look in the ASE errorlog, which will clearly show when ASE was last started. This will work for all versions and platforms.
    (When searching through the errorlog for messages related to the ASE startup, there's a gotcha -- see the question from April 2002)


  2. You may not have access to the host on which ASE is running, which is where the errorlog is located. In that case, you can also determine the last reboot by looking at the creation time of tempdb:
    select crdate 
    from master..sysdatabases where dbid=2
    
    (to display the time with full precision, use a date formatting style, for example convert(varchar,crdate,109)).

    This is probably the easiest method. It will work for at least version 11.0 and later (in fact, also for ASE version 10 -- thanks to Kazuo Otani who kindly verified this).
    I have a vague recollection of things being different in version 4.x, where the tempdb creation time was always Jan 1st, 1900 (however, I cannot verify this since I don't have access to a working ASE 4.x installation).


  3. In ASE 11.0 or later, you can query the sysengines table to find the starttime of the oldest engine (thanks to David Ferrington for suggesting this method):
    select starttime
    from master..sysengines
    where engine = 0
    
    Or simply as follows (less typing):
    select min(starttime)
    from master..sysengines
    

  4. In ASE 12.5, the server boot time has been captured in loggedindatetime of sysprocesses for the internal system processes:
    select min(loggedindatetime) 
    from master..sysprocesses where suid=0
    
  5. In ASE 12.5.0.1, it's even simpler:
    select @@boottime
    
    The static global variable @@boottime is undocumented, but happens to be identical to sysdatabases.crdate for tempdb.


  6. On Unix, the command ps -ef (or equivalent, depending on your Unix flavour) will show all processes running on that box, along with the time they were started. By looking (or grepping) for "dataserver", you can find when the server was started.
    There's one potential problem with this method: when the server has been running for more than 24 hours, you'll only see the date when the server was started, not the precise time.
    AFAIK, this method cannot be used on NT.


  7. When you're running ASE on NT, and event logging is enabled, the NT event log contains the contents of the ASE errorlog. This means you can also determine the server start time from the eventlog.


  8. When you've installed auditing, you may use the "security" option to configure it to log all ASE startup and shutdown events.


  9. Lastly, for all ASE versions: when the server has been running with a single engine since it was started, you can also find the server starttime as follows:
    select dateadd(ss, 
             floor((@@cpu_busy+@@io_busy+@@idle) *
    		       (@@timeticks/-1000000.0)
    			  ), getdate())
    
    ASE diehards will recognise the logic behind the good-old sp_monitor here. How to adjust this formula for multiple engines is left as an exercise for the reader.



  10. Actually, Brian Fitzgerald pointed out two more methods:

  11. The creation date/time of the file $SYBASE/$SYBASE_ASE/<servername>.krg also corresponds to the server starttime (all platforms).


  12. On some Unix platforms (incl. Solaris, and --I believe-- also HP-UX), you can determine the creation time (but apparently not the date) of the ASE server's shared memory segment using the command ipcs -a .



  13. In 12.5.0.3+, there's yet another way:

  14. The MDA table monState contains the server start date/time in the column StartDate (more information about the MDA tables is here).

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
July 2002
For most dbcc commands, the 3604 traceflag must be enabled to display any output at the client. However, some dbcc commands don't require the 3604 traceflag; do you know which ones ?

Answer:
The only two dbcc commands which will always display their output at the client, regardless of the 3604 traceflag, are dbcc gettrunc and dbcc settrunc (for the non-RepServer users: these dbcc commands operate on the secondary truncation point in the ASE transaction log).

Actually, I have no idea why these commands are exceptions with respect to 3604 -- if anyone knows why, please let me know!

NB: Nobody's perfect. Shortly after this question was published, Tom Oorebeek pointed out that also dbcc checkalloc/ indexalloc/ tablealloc/ checkdb/ checktable/ checkcatalog/ checkstorage/ checkverify do not require 3604. However, some of these, like checkalloc/ indexalloc/ tablealloc do print some extra info with 3604 enabled (thanks Tom !)

NB2: ...and there's more: Darrell Anderson and Mike Chachich pointed out (thanks!) that also dbcc cis and dbcc rebuild_text do not require 3604.

How would you rate this ASE quiz question?
Great    OK    Average    Boring      

June 2002
Let's use T-SQL to do some math!
How do you calculate a number's factorial, also known as n!, in a single select statement, without using any loops ?
(quick reminder: n! is defined as 1*2* ... *(n-2)*(n-1)*n, for n >= 0. Hence, 5! is 120)

Answer:
ASE contains a set of mathematical and trigonometric built-in functions, which can be used to perform various mathematical operations, including many that are more complex than those built-in functions themselves. Calculating a number's factorial is just one such example. You can do this as follows:
-- calculating the factorial of n (change n to some number)
select exp(sum(log(id))) from sysobjects where id <= n 

1> select exp(sum(log(id))) from sysobjects where id <= 5
2> go

--------------------
          120.000000

(1 row affected)
You're probably scratching your head about this calculation now (university is probably a lwhile ago for most of us...). First, we're just using sysobjects as an easy way to generate a bunch of sequential numbers, which we need for this trick; we happen to know that the system table object IDs form a series of sequential numbers (actually, there's more to say about this, see below).
Next, the mathematical basis for the expression exp(sum(log(n))) is formed by the following two formulae:

    xa * xb = xa + b
and
    eln x = x     (where e1 = 2.7182818284..., and ln is the natural logarithm)

From these formulae, and the fact that ex and ln x are implemented by the T-SQL functions exp() and log(), respectively, it follows that the factorial is calculated by the above query. Note that the sum() aggregate implements the addition of the exponents as in the first formula.

For completeness, note that the above formula could also be written (in a slightly more complicated way) as follows:
select power(exp(1), sum(log(id))) 
from sysobjects where id <= n 
As mentioned above, this trick relies on having a table with a list of numbers. It is also required that this list of numbers does not contain any gaps, and this is the limitation of using the sysobjects, because no system tables with object IDs 20, 28 or 29 exist. So as long as you're not calculating factorials of 20 or higher, you're fine (though you'll find that you'll quickly get floating-point results for increasing values of n).
For a better method, you could use something like this (admitted: this is more than one select statement now...):
create procedure sp_factorial
   @n int
as
begin
   select n=identity(3) into #factorial
   from syscolumns

   select 
   factorial = str(exp(sum(log(n))),255,0)
   from #factorial where n <= @n
end
In this code, the syscolumns table is used to generate a unique set of consecutive numbers into a #temp table; this works because syscolumns always contains at least a few hundred rows.
Note that this code converts the result to an integer string using the str() function. This will allow factorials up to 146!, as str() doesn't seem to support result strings longer than 255 characters. When leaving off str(), you can go up to 171! before arithmatic overflow occurs, but the result will be displayed in floating-point notation.


(thanks to Rutger van Veenendaal for suggesting this topic)

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
May 2002
You are happily working in an ASE server, but your collegue just can't seem to connect to the server. How do you quickly, and unambiguously, determine the network address (= host + port number) on which the ASE server is listening ?

Answer:
Determining the network address from within the ASE server is relevant when troubleshooting connectivity problems, for example when interfaces files are not correct or not found.
You can quickly find out the server's network address with the following simple query:
select * from master..syslisteners

(On ASE 12.5.1+, you can also run sp_listener 'status')

This will give you the desired information as long as you're not using TLI (on Solaris for example), as TLI uses a long hexadecimal string in which the IP address and port number are encoded. In that cases, run the following query:
select 
substring(address_info, charindex("\x", address_info), 99) a
into #t
from master..syslisteners
where address_info like "%\x%"

select 
convert(varchar(3),hextoint(substring(a,11,2))) + "." +
convert(varchar(3),hextoint(substring(a,13,2))) + "." +
convert(varchar(3),hextoint(substring(a,15,2))) + "." +
convert(varchar(3),hextoint(substring(a,17,2))) "IP address",
convert(varchar,hextoint(substring(a,7,4))) "Port"
from #t

Lastly, the network address is also listed in the ASE errorlog (but that's usually not as quick to determine as when using the above queries). Also, it involves the risk of looking in the wrong errorlog -- which is why using the above queries is the preferred method when any doubt should be avoided.

I haven't used ASE with LDAP yet, but I guess that the above queries would also use in combination with LDAP (experiences, anyone ?).

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
April 2002
In January 2002, Sybase Inc. has moved its corporate headquarters from Emeryville, CA to a new building in Dublin, CA.
Could this real-world move have any *technical* impact on the work of an ASE DBA ?

Answer:
Everyone's first reaction will be: this is nonsense. When Sybase physically moves its office to some new location, why should an ASE DBA have to care about this?

Sure. Of course. There shouldn't be any impact at all !

Well, here's what happened to me recently:
Whenever I'm troubleshooting a customer's ASE environment, I always check out the last part of the ASE errorlog. More specifically, I want to look at some things in the errorlog at the moment of the last ASE reboot (such as whether the devices are using asynch I/O or not). For many years, I've done this check as follows: open the errorlog in an editor, go to the bottom of the file, and search back upwards for the place where the last reboot occurred. To find this place, I always search for the string "meryv", which is part of "Emeryville", which happens to be in the copyright-blabla message that is written to the ASE errorlog for every ASE start (before you ask: the reason I'm not including the first letter "E" in the search string is that I normally use 'vi', which is case-sensitive -- and I'm lazy). This method always worked perfectly; I don't remember any occasion where this string also ocurred in another place.

Somewhere last month (March 2002), I did this same thing again, and my conclusion was that the server was last rebooted a few weeks earlier, somewhere in February 2002. However, the local DBA insisted that his server had been rebooted the day before! I was about to tell him that he was really wrong, because I could tell from the errorlog that the last reboot had occurred a few weeks before.
Well, on closer inspection the DBA appeared to be right -- somewhere I had gone wrong, but I didn't immediately see how. It turned out that a recent ASE EBF had been installed a few weeks before, and the copyright message in this EBF did not contain the word "Emeryville" anymore, because it had been replaced with the new coporate address in Dublin, CA. This will be the case for all new releases and EBFs from now on.
In this case, I had almost made a fool of myself by insisting that the DBA didn't know what he was doing; but of course, he was right and I was wrong...

Lesson learned: Making assumptions about the real world can be tricky in software! And from now on, I'll be searching the errorlog for "opyright" or "onfidential" (not for "ublin", because then we'll have the same trouble when dealing with an older ASE EBF...)

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
March 2002
The following query is really very simple: there's not even a table involved. What is the result of this expression ?
1> select +2*-1*-3+-2--1*+3 
2> go
Answer:
The result of this expression is 4 (did you think it was 7 ?)
This is a bit of trick question: the clue is that the characters -- are actually a comment according to the ANSI SQL syntax. Therefore, the expression being evaluated only consists of +2*-1*-3+-2, and --1*+3 is ignored as a comment.
As I said, this is a trick question, and all those + and - signs are only there to trick you into the wrong answer!
It's not completely hypothetical however: I once made this mistake myself...

How would you rate this ASE quiz question?
Great    OK    Average    Boring      
February 2002
This month's question is about a select statement which looks very, very simple...
Suppose we have the following, very simple table:
1> select a from my_tab
2> go

 a
 -----------
          10
          20
          30

(3 rows affected)
These two queries on this table seem absolutely trivial (and they are!):
1> select a from my_tab where a > 10
2> go

 a
 -----------
          20
          30

(2 rows affected)


1> select sum(a) from my_tab where a > 10
2> go

 -----------
          50

(1 row affected)

Now, here comes the question: how many rows will the following query, which is a combination of the above two queries, return ? Will there be 1, 2 or 3 rows in the result set ?
1> select a, sum(a) from my_tab where a > 10
2> go

Answer:
Contrary to what you may have expected, the correct answer is that there will be 3 rows in the result set:
1> select a, sum(a) from my_tab where a > 10
2> go

 a
 ----------- -----------
          10          50
          20          50
          30          50

(3 rows affected)
This result may seem strange and counterintuitive, given that this query really looks similar to the two previous queries. Also, when looking at the result set, the where-clause seems to apply to the sum() aggregate, but somehow not to the row for a = 10, which is also listed! What's going on ?

This result is not a bug: it is actually correct in terms of ASE's T-SQL implementation of SQL. However, and this is the clue to understanding the problem, this query is invalid according to the ANSI SQL standard, which recognises aggregates only in the context of group by or having clauses.
Specifically, the ANSI standard specifies that when a select list contains an aggregate function (here: sum()), and that select statement does not have a group by clause, then that same select list may not contain additional columns which are not aggregates. The column a in the above query violates this rule.
Still, ASE allows this non-standard ANSI SQL construct as an ASE-specific SQL extension; however, the semantics of such proprietary SQL extensions may not always be what you expect. In this case, the ASE Transact-SQL User's Guide specifies the following:
"If a select statement includes a where clause, but not a group by clause [...] an aggregate function produces a single value for the subset of rows, called a scalar aggregate. However, a select statement can also include a column in its select list (a Transact-SQL extension), that repeats the single value for each row in the result table."
This means that such a query will return as many rows as exist in the original table, effectively ignoring the where clause on this point. For generating the aggregate, the where clause is applied, however.

Lesson learned: In cases like this, where a query returns an apparently strange result set, it's a good idea to check whether the SQL statement is using some ASE-specific, non-ANSI construct, before calling Sybase Technical Support to report that bug you think you've found in ASE.
You can perform this check by enabling the fipsflagger, which prints a warning for every SQL statement violating the ANSI standard:
1> set fipsflagger on
2> go
1> select a, sum(a) from z where a > 10
2> go
FIPS WARNING: Select list contains aggregate function(s) but 
GROUP BY clause not specified.
 a
 ----------- -----------
          10          50
          20          50
          30          50

(3 rows affected)
How would you rate this ASE quiz question?
Great    OK    Average    Boring      
January 2002
In ASE 12.5, the server page size can be 2Kb, 4Kb, 8Kb or 16Kb. How can you find out which size your server has been set up with ?

Answer:
There are various ways of finding out the server page size. For example, running sp_cacheconfig will show the existing buffer pools, and the smallest pool size always corresponds to the server page size.
The simplest way of getting this information is to examine the new global variable @@maxpagesize (introduced in 12.5). This variable contains the server page size, expressed in bytes: for a 2KB page size, @@maxpagesize will be 2048; for 4Kb pages it will be 4096, etc.

Interestingly though, there is yet another global variable, promising the same information: this variable is called @@pagesize, and always contains a constant value of 2048 -- so despite the name, this variable has nothing to do with the server page size as we know it in ASE 12.5.
Though @@pagesize is formally undocumented, I believe that this variable is a remnant of the days when ASE (then SQLServer) also ran on Stratus, which was different in that the ASE disk I/O size was 4Kb, instead of 2Kb as on all other platforms (for those who remember: when doing disk init, you had to calculate the device size using 4Kb pages instead of 2Kb). @@pagesize actually indicated this platform-specific disk page size of those days. In fact, @@pagesize can still be found in various places in the standard system stored procedures, where it is used for various space-related calculations.

How would you rate this ASE quiz question?
Great    OK    Average    Boring      


More ASE quiz questions: 2013 - 2012 - 2011 - 2010 - 2009 - 2008 - 2007 - 2006 - 2005 - 2004 - 2003 - 2002 - 2001
For a randomly selected quiz question, click here.


 
 This document is located at www.sypron.nl/quiz2002a.html