This
page contains answers to the ASE quiz questions from 2003.
More ASE Quiz questions: 2010 - 2009 - 2008 - 2007 - 2006 - 2005 - 2004 - 2003 - 2002 - 2001
June 2003
Since version 11.9, ASE supports datapages and datarows locking as well as the classic allpages lock scheme.
Data-only locking (the collective name for datapages and datarows), abbreviated DOL, is functionally identical to allpages (APL) but offers better concurrency and some additional functionality that APL doesn't have (such as reorg rebuild table_name index_name).
The question: which functionality cannot be implemented for a DOL table (or in a clumsy way at best), while this is easy for an APL table?
Answer:
Warning: this is going to be rather technical, and possibly a bit confusing, so brace yourself...
We tend to think of APL as just a functional subset of DOL: any functionality you can implement with APL tables, you can also do with DOL tables. For most things that is indeed true, but there is one aspect of APL that does not have a direct functional equivalent in DOL.
This difference concerns the handling of duplicate rows in tables with a non-unique clustered index. First, let's get the terminology straight: a duplicate row occurs when all columns in a row have the same value (provided there are no text, image, identity or timestamp columns). In ASE, duplicate rows have historically been relevant for tables with a non-unique clustered index, due to the way rows are stored in a a classic APL clustered index.
Now, for APL tables with a non-unique clustered index, duplicate rows are not allowed to be inserted (or created by an update) by default, though this can be allowed with the allow_dup_row option for the non-unique clustered index. The ignore_dup_row option can be used to silently discard inserted duplicate rows without aborting the transaction (as happens by default).
For DOL tables, things are a bit different. allow_dup_row and ignore_dup_row can be used for non-unique clustered indexes on DOL tables as well, but they have an effect only when the index is created. When inserting into (or updating) a DOL table, allow_dup_row and ignore_dup_row do not have any effect (as opposed to APL tables, where they do).
The difference in functionality between APL and DOL tables lies in the case where you want to automatically reject or ignore inserted duplicate rows for a table with more than 31 columns. For APL tables, this can be done by creating a non-unique clustered index on an arbitrary column, which will reject inserted duplicate rows; to ignore these rows, specify the ignore_dup_row option for the clustered index. However, for a DOL table, such an index does not have any effect on inserted duplicate rows at all (see above).
For DOL tables with 31 columns or less, you can still get the same effect by creating a unique index (either clustered or nonclustered) on all columns of the table, and specify the ignore_dup_key option. However, you cannot create an index on more than 31 columns, so for DOL tables with more than 31 columns, this workaround is not available.
One of the factors in this difference in behaviour is that the ignore_dup_row (and allow_dup_row) option for a non-unique clustered index are not related to the columns included in the index, but to the entire row. In other words, the treatment of duplicate rows has nothing to with the actual columns in the non-unique clustered index. This may look a bit awkward, since the ignore_dup_key option on a unique nonclustered index applies only to the combined columns in the index (but then, the difference between ignore_dup_row and ignore_dup_key can indeed be a bit confusing).
Now, there is actually another workaround for this problem. In an insert/update trigger, a check can be added to verify whether there are any duplicate rows. If so,the trigger can delete 'm (when resulting from an insert) or reject 'm (when resulting from an update). However, this solution doesn't qualify as easy: you'll need to join all columns of the base table to the inserted table in the trigger - and remember we're talking about more than 31 columns here...
Also, the trigger 'solution' is nowhere near as fast as the ignore_dup_row option. With a non-unique clustered index, duplicate rows are detected immediately when they are inserted, directly at the start of the insert operation. The trigger-based solution will detect the duplicates only after they have been inserted. When there are additional nonclustered indexes, or many duplicates in multi-row inserts, the difference in performance can be significant.
By now, you're probaby wondering why you should bother about this whole thing in the first place. Ignoring duplicate rows by means of the ignore_dup_row option is a handy way of filtering out duplicates rows -- especially because you don't have to specify all columns in the index. I have used this feature various times as an efficient way of removing duplicates from a table: create a new table with the same columns, add a non-unique clustered index with ignore_dup_row, and simply insert all rows into this new table. When the insert is completed, all duplicate rows will have been removed.
Removing duplicate rows may not be an everyday requirement, but it isn't academic either. Anyway, if nothing else, were you already aware of the difference between ignore_dup_key and ignore_dup_row?
May 2003
As we all know, probably through finding out the hard way, you cannot create a table inside a trigger since DDL is not allowed in a transaction by default.
To allow this anyway, you need to enable the database option ddl in tran.
The question: how can you create a table from within a trigger without using ddl in tran ?
(hint: this is already possible since at least version 10, so it doesn't involve CIS...)
Answer:
The answer is surprisingly simple: just commit inside the trigger, and no transaction will be active anymore. Then you're free to create pretty much any table you want.
It goes like this:
create proc mytrigproc
as
select @@trancount "@@trancount in proc"
create table tempdb..xxx (xxx_column1 int)
select * from tempdb..xxx
go
create table mytab (a int)
go
create trigger mytrigger on mytab for insert
as
while @@trancount > 0 commit
exec mytrigproc
go
When inserting a row, table tempdb..xxx will be created automatically:
1> insert mytab values(1)
2> go
@@trancount in proc
-------------------
0
(1 row affected)
xxx_column1
-----------
(0 rows affected)
Let's look at the technical details involved in this trick:
- First, the trigger commits up until the outermost transaction. Yes, you can do this in a trigger! (though it's not a good idea)
- Then the trigger executes the stored proc. ASE actually checks whether a trigger contains a create table or select...into statement, and raises an error message during create trigger. But when you put the table creation in a stored proc, ASE won't notice.
- Obviously, when inserting another row, the table already exists (but that's not the point here)
Warning:
Though nothing technically stops you from doing this kind of thing, your sense of responsibility as a DBA should.
Needless to say, this sort of trick destroys any guarantee of transactional integrity: after an insert into this trigger, no rollback is possible anymore.
Therefore: don't use this in your code!
(Frankly, this is one of those "solutions without a problem" -- I wouldn't know how this trick could be practically useful. But it's a nice one buy itself...)
April 2003
It is common knowledge that select * will display all columns in a table.
But is this always true? Is it possible to create 'hidden' columns?
Answer:
The answers are 'no' and 'yes', respectively.
Before getting into details, let's review some basics. Check out this code:
1> create table mytable (a int)
2> go
1> select * from mytable
2> go
a
-----------
(0 rows affected)
1> select count(*) from syscolumns
2> where id = object_id("mytable")
3> go
-----------
2
(1 row affected)
So, according to syscolumns, there are two columns in this table, although we created only one column. Eh ... ?
Don't question your sanity: you'll see this behaviour when the database option auto identity (or unique auto_identity index) is enabled for the current database. As documented, this will add a column named SYB_IDENTITY_COL to each table created without an identity column -- which is why there are two columns in the table, even though we specified only one in the create table statement. Also documented is that this column won't be visible when using select *.
So far, there's nothing special (you did know all this, didn't you?). The fun comes when you create a non-identity column named SYB_IDENTITY_COL: any column with this name just won't be visible with select *.
To explicitly create a column named SYB_IDENTITY_COL, we'll have to do a little trick, because you cannot simply create a column with this name:
1> create table mytable2 (a char(5), SYB_IDENTITY_COL int)
2> go
Msg 7368, Level 15, State 2
Server 'SYBASE', Line 1
Can not create column by name 'SYB_IDENTITY_COL'. This
name has special meaning when 'auto_identity' option is ON.
We can work around this by renaming the column instead (note: run this in a database where auto identity is not enabled, or you'll get a 'column already exists' message):
1> create table mytable2 (a char(5), b int)
2> go
1> sp_rename 'mytable2.b', SYB_IDENTITY_COL
2> go
Column name has been changed.
(return status = 0)
1> select * from mytable2
2> go
a
-----
(0 rows affected)
We can insert rows into this table in the normal way, but the renamed column simply won't be included by select *:
1> insert mytable2 values ("Row 1", 1)
2> insert mytable2 values ("Row 2", 2)
3> insert mytable2 values ("Row 3", 3)
4> go
(1 row affected)
(1 row affected)
(1 row affected)
1> select * from mytable2
2> go
a
-----
Row 1
Row 2
Row 3
(3 rows affected)
When renaming the column back, it will be included again:
1> sp_rename 'mytable2.SYB_IDENTITY_COL', b
2> go
Column name has been changed.
(return status = 0)
1> select * from mytable2
2> go
a b
----- -----------
Row 1 1
Row 2 2
Row 3 3
(3 rows affected)
So you can indeed create a 'hidden' column in the sense that it won't be included in the results of select *, by simply renaming it to SYB_IDENTITY_COL. Note that the column can always be selected simply by naming it explicitly in the select list.
This trick will work for columns of any datatype. The reason that it works at all, is that SYB_IDENTITY_COL is not a T-SQL reserved word (try renaming a column to 'trigger' instead...). This also means that SYB_IDENTITY_COL is case-sensitive, and becomes an ordinary column name, without any special behaviour, when it's not fully in uppercase.
Frankly, I wouldn't immediately know a good application of this trick -- perhaps except for driving your collegues mad...
March 2003
The ASE system stored procedures are usually named quite logically.
What do sp_reportstats, sp_clearstats,
sp_flushstats, sp_modifystats and sp_object_stats have in common?
Answer:
Mostly, just the first and last part of their names. There is more similarity between the names of these procedures than between their functionality:
- sp_reportstats reports the amount of CPU and I/O spent by each login.
sp_clearstats resets the counters for these statistics.
These two procedures are very old -- they were already there when I first worked with ASE in 1989.
They are very much remnants of the mainframe era, where individual CPU seconds were charged to your project budget.
My guess is that these stored procedures are hardly ever used by anyone today (in fact, did anyone ever use these procs at all?).
- sp_flushstats has absolutely nothing to do with the two procedures mentioned above.
sp_flushstats was introduced in 11.9, and explicitly flushes the in-memory statistics
for a specific table to systabstats (these statistics are things like the rowcount, cluster ratio information, etc.; not the histogram & density
information generated by update statistics). This flushing is normally done automatically by the housekeeper task, but in case the housekeeper is disabled,
you can force the data to be flushed.
- sp_modifystats allows you to manipulate the density statistics in sysstatistics, which were generated by update statistics.
Using this procedure makes sense only when you have a very good understanding of the ins and outs of the ASE query optimizer. Typically, you'll be the kind of person who has read everything Eric Miner has written.
This procedure was introduced in later versions of 11.9, and new functionality was added in 12.5.
- sp_object_stats is in a completely different area. It
will tell you on which tables the most lock contention occurs. This procedure is extremely useful when diagnosing concurrency problems.
Unlike the 4 procedure above, which you may never use, sp_object_stats is a must-know for anyone who's interested in ASE performance.
This procedure was introduced 11.9.
February 2003
Recently, I was writing some SQL code where I needed to do X%100 ('X modulo 100'), where X was of the numeric(10) datatype.
Because -unfortunate but true- ASE allows the modulo operator only on int, smallint or tinyint datatypes, I had to implement this function myself.
This should have been a simple, 30-second job: the following expression is equivalent to X%100:
select @X - ((@X/100)*100)
However, this did not work as expected: this expression always returns 0 for numeric datatypes.
Why?
Answer:
Though mathematically correct, this expression always produces 0 as a result:
1> declare @X numeric(10)
2> select @X = 123
3> select @X - ((@X/100)*100)
4> go
------------------------------------
0.00000000000
On the other hand, the logic cannot be completely wrong: on an int datatype, the expression performs as expected (although we don't need it here because the modulo operator works for an int):
1> declare @X int
2> select @X = 123
3> select @X - ((@X/100)*100)
4> go
-----------
23
So the question is: why does this expression work fine for an int , but not for a numeric without decimals?
Obviously, I had overlooked something. I tried to narrow the problem down by simplifying the expression:
1> declare @X numeric(10)
2> select @X = 123
3> select (@X/100)*100, @X/100
4> go
------------------ -----------------
123.00000000000 1.23000000000
My expectation had been that 123/100 = 1, truncated to an integer, because @X is defined to have no decimals (its scale is 0).
However, these queries clearly show that this was an incorrect assumption...
After some head-scratching, a vague remembrance emerged about ASE having some rules about the scale and precision of numeric expressions. I actually had to look it up in the ASE documentation, and things started to become clear...
First, is it important to understand that the datatype of an expression is not necessarily the same as the datatypes of the terms in the expression.
When an expression involves a numeric value and an int, the expression's result will be numeric.
Second, when performing a division involving numeric data, the quotient will also be numeric,
with a scale (=#decimals) that is usually greater than that of the individual terms. So even though @X is a numeric(10) (i.e. with a scale of 0), the result of @X/100 has a datatype of numeric(21,11)
Thus, the quotient is not truncated to an integer, but remains 1.23000000000. In my original code, this value is then multiplied by 100 again, so the final result is always 0.
In contrast, when @X is an int and no numeric value is involved, the quotient will also be an int, and therefore all decimals will immediately be truncated, and my original code works as expected.
So, finally, I rewrote my expression for doing a modulo on numeric value like this:
1> declare @X numeric(10)
2> select @X = 123
3> select @X - (floor(@X/100)*100)
4> go
-------------------------
23
The floor() function converts the numeric quotient to an int, truncating the decimals. Overall, it took me about an hour to get to this point, and understand why...
If you're interested in the detailes, the story about datatype conversion rules, precision and scale, is described in the first chapter of the ASE Reference Manual, under "Datatype of mixed-mode expressions" -> 'Determining precision and scale".
I've always found this one of those 'boring' chapters in the ASE docs; the last time I read this material was when preparing for my first ASE 11.0 certification exam, years ago. Well, it wasn't as boring as I thought...
Lesson learned (1):
don't expect calculations on int data to behave identically on numeric data!
Lesson learned (2):
don't dismiss any part of the ASE documentation as 'boring'!
Update:
As of ASE 1.2.5.4 and 15.0.2, the modulo operator works on all numeric datatypes, including 'numeric', 'money', 'float' etc.
January 2003
A close and trusted friend, who works at a different company, has given you the source code for some handy DBA stored procedures which he's written himself.
He assures you that the SQL code is valid Transact-SQL. Still, when executed in your ASE 12.5 server, his code causes many syntax errors.
Your conclusion is that it is not Transact-SQL-compliant.
Is your friend a liar, a terrible programmer, or is your own judgement flawed?
Answer:
In fact, you may both be right -- the clue is in the definition of "Transact-SQL". But first, a bit of history.
A long time ago, Microsoft was a partner of Sybase, reselling the Sybase database server as "Microsoft SQL Server" for OS/2 and NT.
In 1994, the partnership ended, and Microsoft went its own way with a copy of the source code. This led to a situation where Sybase was selling a product called "Sybase SQL Server" while Microsoft had a competing product called "Microsoft SQL Server". To distinguish itself better from MS, Sybase renamed its product to "Adaptive Server Enterprise" in 1997. Today, "SQL Server" is commonly associated with Microsoft's database product.
Against this background, both ASE and MS SQL Server have an SQL implementation called "Transact-SQL". Due to the common heritage, these SQL versions are quite similar. However, over the years, some incompatibilities have emerged as well. This means that you can have SQL code which is valid Transact-SQL in MS SQL Server, but not valid Transact-SQL in ASE (or the other way around).
As an example, the following Transact-SQL is valid in MS SQL server, but not in ASE:
select stdev(age) from students
-- stdev() is an aggregate to calculate standard deviation
Likewise, the following Transact-SQL works in ASE but not in MS SQL server:
begin tran
lock table MyTable in exclusive mode
-- explicitly locks a table
(these are just two examples; more incompatible features exist)
The question was aiming at this, somewhat confusing, situation.
Therefore, whenever "Transact-SQL" is mentioned, check whether this concerns the Sybase or the Microsoft flavour.
It is not surprising that "Transact-SQL" and "SQL Server" are trademarks ("Transact-SQL" is even a registered trademark).
However, it is interesting that these trademarks have actually long been owned by Sybase, not by Microsoft.
It is interesting that Microsoft has apparently chosen to stick with "Transact-SQL" rather than renaming it to something like "Microsoft SQL". I can only guess for the reasons why, but could it be that "Transact-SQL" has earned such a solid reputation that Microsoft doesn't want to throw this away?
It may be hard to admit for the Microsoft fans, but both Transact-SQL and SQL Server were not developed by Microsoft, but acquired from Sybase...
Go here for a full list of all quiz questions.
|