home *** CD-ROM | disk | FTP | other *** search
/ ftp.pasteur.org/FAQ/ / ftp-pasteur-org-FAQ.zip / FAQ / databases / sybase-faq / part14 < prev    next >
Encoding:
Internet Message Format  |  2004-04-21  |  61.5 KB

  1. Path: senator-bedfellow.mit.edu!dreaderd!not-for-mail
  2. Message-ID: <databases/sybase-faq/part14_1082468590@rtfm.mit.edu>
  3. Supersedes: <databases/sybase-faq/part14_1074677126@rtfm.mit.edu>
  4. Expires: 2 Aug 2004 13:43:10 GMT
  5. References: <databases/sybase-faq/part1_1082468590@rtfm.mit.edu>
  6. X-Last-Updated: 2003/03/02
  7. From: dowen@midsomer.org (David Owen)
  8. Newsgroups: comp.databases.sybase,comp.answers,news.answers
  9. Subject: Sybase FAQ: 14/19 - ASE SQL (3 of 3)
  10. Reply-To: dowen@midsomer.org (David Owen)
  11. Followup-To: comp.databases.sybase
  12. Distribution: world
  13. Organization: Midsomer Consultants Inc.
  14. Approved: news-answers-request@MIT.EDU
  15. Keywords: FAQ, DATABASES, SYBASE, ASA, ASE, REP
  16. Originator: faqserv@penguin-lust.MIT.EDU
  17. Date: 20 Apr 2004 13:45:13 GMT
  18. Lines: 1554
  19. NNTP-Posting-Host: penguin-lust.mit.edu
  20. X-Trace: 1082468713 senator-bedfellow.mit.edu 577 18.181.0.29
  21. Xref: senator-bedfellow.mit.edu comp.databases.sybase:106212 comp.answers:56958 news.answers:270298
  22.  
  23. Archive-name: databases/sybase-faq/part14
  24. URL: http://www.isug.com/Sybase_FAQ
  25. Version: 1.7
  26. Maintainer: David Owen
  27. Last-modified: 2003/03/02
  28. Posting-Frequency: posted every 3rd month
  29.    A how-to-find-the-FAQ article is posted on the intervening months.
  30.  
  31. 6.2.7: Hierarchy traversal - BOMs
  32.  
  33. -------------------------------------------------------------------------------
  34.  
  35. Alright, so you wanna know more about representing hierarchies in a relational
  36. database? Before I get in to the nitty gritty I should at least give all of the
  37. credit for this algorithm to: "_Hierarical_Structures:_The_Relational_Taboo!_,
  38. _(Can_ Transitive_Closure_Queries_be_Efficient?)_", by Michael J. Kamfonas as
  39. published in 1992 "Relational Journal" (I don't know which volume or issue).
  40.  
  41. The basic algorithm goes like this, given a tree (hierarchy) that looks roughly
  42. like this (forgive the ASCII art--I hope you are using a fixed font to view
  43. this):
  44.  
  45.                                     a
  46.                                    / \
  47.                                  /     \
  48.                                /         \
  49.                              b             c
  50.                             / \           /|\
  51.                            /   \        /  |  \
  52.                           /     \     /    |   \
  53.                          d       e   f     |    g
  54.  
  55.    
  56.     Note, that the tree need not be balanced for this algorithm to work.
  57.    
  58. The next step assigned two numbers to each node in the tree, called left and
  59. right numbers, such that the left and right numbers of each node contain the
  60. left and right numbers of the ancestors of that node (I'll get into the
  61. algorithm for assigning these left and right numbers later, but, hint: use a
  62. depth-first search):
  63.  
  64.                                    1a16
  65.                                    / \
  66.                                  /     \
  67.                                /         \
  68.                             2b7           8c15
  69.                             / \           /|\
  70.                            /   \        /  |  \
  71.                           /     \     /    |   \
  72.                         3d4     5e6 9f10 11g12 13h14
  73.  
  74.    
  75.     Side Note: The careful observer will notice that these left and right
  76.     numbers look an awful lot like a B-Tree index.
  77.    
  78. So, you will notice that all of the children of node 'a' have left and right
  79. numbers between 1 and 16, and likewise all of the children of 'c' have left and
  80. right numbers between 8 and 15. In a slightly more relational format this table
  81. would look like:
  82.  
  83.               Table: hier
  84.    node   parent left_nbr  right_nbr
  85.    -----  ------ --------  ---------
  86.    a        NULL        1         16
  87.    b           a        2          7
  88.    c           a        8         15
  89.    d           b        3          4
  90.    e           b        5          6
  91.    f           c        9         10
  92.    g           c       11         12
  93.    h           c       13         14
  94.  
  95. So, given a node name, say @node (in Sybase variable format), and you want to
  96. know all of the children of the node you can do:
  97.  
  98.     SELECT h2.node
  99.       FROM hier   h1,
  100.            hier   h2
  101.      WHERE h1.node      =   @node
  102.        AND h2.left_nbr  >   h1.left_nbr
  103.        AND h2.left_nbr  <   h1.right_nbr
  104.  
  105. If you had a table that contained, say, the salary for each node in your
  106. hierarchy (assuming a node is actually a individual in a company) you could
  107. then figure out the total salary for all of the people working underneath of
  108. @node by doing:
  109.  
  110.     SELECT sum(s.salary)
  111.       FROM hier   h1,
  112.            hier   h2,
  113.            salary s
  114.      WHERE h1.node      =   @node
  115.        AND h2.left_nbr  >   h1.left_nbr
  116.        AND h2.right_nbr >   h1.right_nbr
  117.        AND s.node       =   h2.node
  118.  
  119. Pretty cool, eh? And, conversely, if you wanted to know how much it cost to
  120. manage @node (i.e. the combined salary of all of the boss's of @node), you can
  121. do:
  122.  
  123.     SELECT sum(s.salary)
  124.       FROM hier   h1,
  125.            hier   h2,
  126.            salary s
  127.      WHERE h1.node      =   @node
  128.        AND h2.left_nbr  <   h1.left_nbr
  129.        AND h2.left_nbr  >   h1.right_nbr
  130.        AND s.node       =   h2.node
  131.  
  132. Now that you can see the algorithm in action everything looks peachy, however
  133. the sticky point is the method in which left and right numbers get assigned.
  134. And, unfortunately, there is no easy method to do this relationally (it can be
  135. done, it just ain't that easy). For an real- world application that I have
  136. worked on, we had an external program used to build and maintain the
  137. hierarchies, and it was this program's responsibility to assign the left and
  138. right numbers.
  139.  
  140. But, in brief, here is the algorithm to assign left and right numbers to every
  141. node in a hierarchy. Note while reading this that this algorithm uses an array
  142. as a stack, however since arrays are not available in Sybase, they are
  143. (questionably) emulated using a temp table.
  144.  
  145.     DECLARE @skip            int,
  146.             @counter         int,
  147.             @idx             int,
  148.             @left_nbr        int,
  149.             @node            varchar(10)
  150.  
  151.     /*-- Initialize variables --*/
  152.     SELECT @skip    = 1000,   /* Leave gaps in left & right numbers */
  153.            @counter = 0,      /* Counter of next available left number */
  154.            @idx     = 0       /* Index into array */
  155.  
  156.     /*
  157.      * The following table is used to emulate an array for Sybase,
  158.      * for Oracle this wouldn't be a problem. :(
  159.      */
  160.     CREATE TABLE #a (
  161.         idx          int           NOT NULL,
  162.         node         varchar(10)   NOT NULL,
  163.         left_nbr     int           NOT NULL
  164.     )
  165.  
  166.     /*
  167.      * I know that I always preach about not using cursors, and there
  168.      * are ways to get around it, but in this case I am more worried
  169.      * about readability over performance.
  170.      */
  171.     DECLARE root_cur CURSOR FOR
  172.       SELECT h.node
  173.         FROM hier h
  174.        WHERE h.parent IS NULL
  175.     FOR READ ONLY
  176.  
  177.     /*
  178.      * Here we are populating our "stack" with all of the root
  179.      * nodes of the hierarchy.  We are using the cursor in order
  180.      * to assign an increasing index into the "stack"...this could
  181.      * be done using an identity column and a little trickery.
  182.      */
  183.     OPEN root_cur
  184.     FETCH root_cur INTO @node
  185.     WHILE (@@sqlstatus = 0)
  186.     BEGIN
  187.       SELECT @idx = @idx + 1
  188.       INSERT INTO #a VALUES (@idx, @node, 0)
  189.       FETCH root_cur INTO @node
  190.     END
  191.     CLOSE root_cur
  192.     DEALLOCATE CURSOR root_cur
  193.  
  194.     /*
  195.      * The following cursor will be employed to retrieve all of
  196.      * the children of a given parent.
  197.      */
  198.     DECLARE child_cur CURSOR FOR
  199.       SELECT h.node
  200.         FROM hier h
  201.        WHERE h.parent = @node
  202.     FOR READ ONLY
  203.  
  204.     /*
  205.      * While our stack is not empty.
  206.      */
  207.     WHILE (@idx > 0)
  208.     BEGIN
  209.       /*
  210.        * Look at the element on the top of the stack.
  211.        */
  212.       SELECT @node      = node,
  213.              @left_nbr  = left_nbr
  214.         FROM #a
  215.        WHERE idx = @idx
  216.  
  217.       /*
  218.        * If the element at the top of the stack has not been assigned
  219.        * a left number yet, then we assign it one and copy its children
  220.        * on the stack as "nodes to be looked at".
  221.        */
  222.       IF (@left_nbr = 0)
  223.       BEGIN
  224.          /*
  225.           * Set the left number of the current node to be @counter + @skip.
  226.           * Note, we are doing a depth-first traversal, assigning left
  227.           * numbers as we go.
  228.           */
  229.          SELECT @counter  = @counter + @skip
  230.          UPDATE #a
  231.             SET left_nbr  = @counter
  232.           WHERE idx = @idx
  233.  
  234.          /*
  235.           * Append the children of the current node to the "stack".
  236.           */
  237.          OPEN child_cur
  238.          FETCH child_cur INTO @node
  239.          WHILE (@@sqlstatus = 0)
  240.          BEGIN
  241.             SELECT @idx = @idx + 1
  242.             INSERT INTO #a VALUES (@idx, @node, 0)
  243.             FETCH child_cur INTO @node
  244.          END
  245.          CLOSE child_cur
  246.  
  247.       END
  248.       ELSE
  249.       BEGIN
  250.          /*
  251.           * It turns out that the current node already has a left
  252.           * number assigned to it, so we just need to assign the
  253.           * right number and update the node in the actual
  254.           * hierarchy.
  255.           */
  256.          SELECT @counter = @counter + @skip
  257.  
  258.          UPDATE h
  259.             SET left_nbr  = @left_nbr,
  260.                 right_nbr = @counter
  261.           WHERE h.node    = @node
  262.  
  263.          /*
  264.           * "Pop" the current node off our "stack".
  265.           */
  266.          DELETE #a WHERE idx = @idx
  267.          SELECT @idx = @idx - 1
  268.       END
  269.     END /* WHILE (@idx > 0) */
  270.     DEALLOCATE CURSOR child_cur
  271.  
  272. While reading through this, you should notice that assigning the left and right
  273. numbers to the entire hierarchy is very costly, especially as the size of the
  274. hierarchy grows. If you put the above code in an insert trigger on the hier
  275. table, the overhead for inserting each node would be phenomenal. However, it is
  276. possible to reduce the overall cost of an insertion into the hierarchy.
  277.  
  278.  1. By leaving huge gaps in the left & right numbers (using the @skip
  279.     variable), you can reduce the circumstances in which the numbers need to be
  280.     reassigned for a given insert. Thus, as long as you can squeeze a new node
  281.     between an existing pair of left and right numbers you don't need to do the
  282.     re-assignment (which could affect all of the node in the hierarchy).
  283.  2. By keeping an extra flag around in the hier table to indicate which nodes
  284.     are leaf nodes (this could be maintained with a trigger as well), you avoid
  285.     placing leaf nodes in the array and thus reduce the number of updates.
  286.  
  287. Deletes on this table should never cause the left and right numbers to be
  288. re-assigned (you could even have a trigger automagically re-parent orphaned
  289. hierarchy nodes).
  290.  
  291. All-in-all, this algorithm is very effective as long as the structure of the
  292. hierarchy does not change very often, and even then, as you can see, there are
  293. ways of getting around a lot of its inefficiencies.
  294.  
  295. Back to top
  296.  
  297. -------------------------------------------------------------------------------
  298.  
  299. 6.2.8: Calling OS commands from a trigger or a stored procedure
  300.  
  301. -------------------------------------------------------------------------------
  302.  
  303. 11.5 and above
  304.  
  305. The Adaptive Server (11.5) will allow O/S calls from within stored procedures
  306. and triggers. These stored procedures are known as extended stored procedures.
  307.  
  308. Pre-11.5
  309.  
  310. Periodically folks ask if it's possible to make a system command or call a UNIX
  311. process from a Trigger or a Stored Procedure.
  312.  
  313. Guaranteed Message Processing
  314.  
  315. The typical ways people have implemented this capability is:
  316.  
  317.  1. Buy Open Server and bind in your own custom stuff (calls to system() or
  318.     custom C code) and make Sybase RPC calls to it.
  319.  2. Have a dedicated client application running on the server box which
  320.     regularly scans a table and executes the commands written into it (and
  321.     tucks the results into another table which can have a trigger on it to
  322.     gather results...). It is somewhat tricky but cheaper than option 1.
  323.  
  324. Sybase ASE 10.0.2.5 and Above - syb_sendmsg()
  325.  
  326. This release includes a new built-in function called syb_sendmsg(). Using this
  327. function you can send a message up to 255 bytes in size to another application
  328. from the ASE. The arguments that need to be passed to syb_sendmsg() are the IP
  329. address and port number on the destination host, and the message to be sent.
  330. The port number specified can be any UDP port, excluding ports 1-1024, not
  331. already in use by another process. An example is:
  332.  
  333. 1> select syb_sendmsg("120.10.20.5", 3456, "Hello")
  334. 2> go
  335.  
  336. This will send the message "Hello" to port 3456 at IP address '120.10.20.5'.
  337. Because this built-in uses the UDP protocol to send the message, the ASE does
  338. not guarantee the receipt of the message by the receiving application.
  339.  
  340.     Also, please note that there are no security checks with this new function.
  341.     It is possible to send sensitive information with this command and Sybase
  342.     strongly recommends caution when utilizing syb_sendmsg to send sensitive
  343.     information across the network. By enabling this functionality, the user
  344.     accepts any security problems which result from its use (or abuse).
  345.    
  346. To enable this feature you should run the following commands as the System
  347. Security Officer.
  348.  
  349.  1. Login to the ASE using 'isql'.
  350.  2. Enable the syb_sendmsg() feature using sp_configure.
  351.     1> sp_configure "allow sendmsg", 1
  352.     2> go
  353.     
  354.     1> sp_configure "syb_sendmsg port number", <port number>
  355.     2> go
  356.     
  357.     1> reconfigure with override  -- Not necessary with 11.0 and above
  358.     2> go
  359.  
  360. The server must be restarted to set the port number.
  361.  
  362. Using syb_sendmsg() with Existing Scripts
  363.  
  364. Since syb_sendmsg() installs configuration parameter "allow sybsendmsg",
  365. existing scripts that contain the syntax
  366.  
  367. 1> sp_configure allow, 1
  368. 2> go
  369.  
  370. to enable updates to system tables should be altered to be fully qualified as
  371. in the following:
  372.  
  373. 1> sp_configure "allow updates", 1
  374. 2> go
  375.  
  376. If existing scripts are not altered they will fail with the following message:
  377.  
  378. 1> sp_configure allow, 1
  379. 2> go
  380. Configuration option is not unique.
  381. duplicate_options
  382. ----------------------------
  383. allow updates
  384. allow sendmsg
  385.  
  386. (return status = 1)
  387.  
  388. (The above error is a little out of date for the latest releases of ASE, there
  389. are now 8 rows that contain "allow", but the result is the same.)
  390.  
  391. Backing Out syb_sendmsg()
  392.  
  393. The syb_sendmsg() function requires the addition on two config values. If it
  394. becomes necessary to roll back to a previous ASE version which does not include
  395. syb_sendmsg(), please follow the instructions below.
  396.  
  397.  1. Edit the RUNSERVER file to point to the SWR ASE binary you wish to use.
  398.  2. isql -Usa -P<sa password> -Sserver_name -n -iunconfig.sendmsg -ooutput_file
  399.  
  400. Sample C program
  401.  
  402. #include <stdlib.h>
  403. #include <stdio.h>
  404. #include <sys/types.h>
  405. #include <sys/socket.h>
  406. #include <netinet/in.h>
  407. #include <arpa/inet.h>
  408. #include <unistd.h>
  409. #include <fcntl.h>
  410.  
  411. main(argc, argv)
  412. int argc; char *argv[];
  413. {
  414.  
  415.     struct sockaddr_in sadr;
  416.     int portnum,sck,dummy,msglen;
  417.     char msg[256];
  418.  
  419.     if (argc <2) {
  420.         printf("Usage: udpmon <udp portnum>\n");
  421.         exit(1);
  422.     }
  423.  
  424.     if ((portnum=atoi(argv[1])) <1) {
  425.         printf("Invalid udp portnum\n");
  426.         exit(1);
  427.     }
  428.  
  429.     if ((sck="socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))" < 0) {
  430.         printf("Couldn't create socket\n");
  431.         exit(1);
  432.     }
  433.  
  434.     sadr.sin_family      = AF_INET;
  435.     sadr.sin_addr.s_addr = inet_addr("0.0.0.0");
  436.     sadr.sin_port        = portnum;
  437.  
  438.     if (bind(sck,&sadr,sizeof(sadr)) < 0) {
  439.         printf("Couldn't bind requested udp port\n");
  440.         exit(1);
  441.     }
  442.  
  443.     for (;;)
  444.     {
  445.  
  446.         if((msglen="recvfrom(sck, msg, sizeof(msg), 0, NULL, &dummy))" < 0)
  447.             printf("Couldn't recvfrom() from udp port\n");
  448.  
  449.         printf("%.*s\n", msglen, msg);
  450.     }
  451.  
  452. Back to top
  453.  
  454. -------------------------------------------------------------------------------
  455.  
  456. 6.2.9: Identities and Sequential Keys
  457.  
  458. -------------------------------------------------------------------------------
  459.  
  460. This has several sections, culled from various sources. It is better described
  461. as "Everything you've ever wanted to know about identities." It will serve to
  462. answer the following frequently asked questions:
  463.  
  464. What are the Features and Advantages of using Identities?
  465. What are the Problems with and Disadvantages of Identities?
  466. Common Questions about Identities
  467.  
  468.   * Is Identity the equivalent of Oracle's Auto-sequencing?
  469.   * How do I configure a table to use the Identity field?
  470.   * How do I configure the burn factor?
  471.   * How do I find out if my tables have Identities defined?
  472.   * What is my current identity burn factor vulnerability?
  473.  
  474. How do I optimize the performance of a table that uses Identities?
  475. How do I recover from a huge gap in my identity column?
  476. How do I fix a table that has filled up its identity values?
  477.  
  478. OK, I hate identities. How do I generate sequential keys without using the
  479. Identity feature?
  480. How do I optimize a hand-made sequential key system for best performance?
  481.  
  482. - Question 8.1 of the comp.database.sybase FAQ has a quick blurb about
  483. identities and sequential numbers. Search down in the page for the section
  484. titled, "Generating Sequential Numbers." Question 8.1 is a general document
  485. describing Performance and Tuning topics to be considered and thus doesn't go
  486. into as much detail as this page.
  487.  
  488. - There's a white paper by Malcolm Colton available from the sybase web site.
  489. Goto the Sybase web site http://www.sybase.com and type Surrogate in the search
  490. form. Select the Surrogate Primary Keys, Concurrency, and the Cache Hit Ratio
  491. document.
  492.  
  493. -------------------------------------------------------------------------------
  494.  
  495. Advantages/Features of Using Identities
  496.  
  497.  
  498. There's an entire section devoted to Identity columns in the ASE Reference
  499. manual, Chapter 5
  500.  
  501. Sybase System 10 introduced many changes over the 4.9.x architecture. One of
  502. these changes was the Identity feature. The identity column is a special column
  503. type that gets automatically updated by the server upon a new row insert. Its
  504. purpose is to guarantee a unique row identifier not based on the other data in
  505. the row. It was integrated with the server and made memory based for fast value
  506. retrieval and no locking (as was/is the case with homegrown sequential key
  507. generation schemes).
  508.  
  509. The Advantages and Features of Identities include:
  510.  
  511.   * A non-SQL based solution to the problem of having an default unique value
  512.     assigned to a row. ASE prefetches identity values into cache and adds them
  513.     automatically to rows as they're inserted into tables that have a type
  514.     Identity column. There's no concurrency issues, no deadlocking in
  515.     high-insert situations, and no possibility of duplicate values.
  516.   * A high performance Unique identifier; ASE's optimizer is tuned to work well
  517.     with Unique indexes based on the identity value.
  518.   * The flexibility to insert into the identity field a specific value in the
  519.     case of a mistaken row deletion. (You can never update however). You
  520.     accomplish this by:
  521.     1> set identity_insert [datababase]..[table] on
  522.     2> go
  523.    
  524.     Note however that the System will not verify the uniqueness of the value
  525.     you specifically insert (unless of course you have a unique index existing
  526.     on the identity column).
  527.    
  528.   * The flexibility during bcp to either retain existing identity values or to
  529.     reset them upon bcping back in. To retain the specific identity values
  530.     during a bcp out/in process, bcp your data out normally (no special
  531.     options). Then create your bcp in target table with ddl specifying the
  532.     identity column in the correct location. Upon bcp'ing back in, add the "-E"
  533.     option at the end of the bcp line, like this (from O/S prompt):
  534.     % bcp [database]..[new_table] in [bcp datafile] -Usa -S[server] -f [fmt file] -E
  535.    
  536.     For procedures on resetting identity values during a bcp, see the section
  537.     regarding Identity gaps.
  538.    
  539.   * Databasewide Identity options: 1) The ability to set Sybase to
  540.     automatically create an Identity column on any table that isn't created
  541.     with a primary key or a unique constraint specified. 2) Sybase can
  542.     automatically include an Identity field in all indexes created,
  543.     guaranteeing all will be unique. These two options guarantee increased
  544.     index performance optimization and guarantees the use of updateable cursors
  545.     and isolation level 0 reads.
  546.     These features are set via sp_dboption, like this:
  547.     1> sp_dboption [dbname], "auto identity", true
  548.     2> go
  549.     or
  550.     1> sp_dboption [dbname], "identity in nonunique index", true
  551.     2> go
  552.    
  553.     To tune the size of the auto identity (it defaults to precision 10):
  554.    
  555.     1> sp_configure "size of auto identity", [desired_precision]
  556.     2> go
  557.    
  558.     (the identity in nonunique index db_option and the size of auto identity
  559.     sp_configure value are new with System 11: the auto identity existed with
  560.     the original Identity feature introduction in System 10)
  561.    
  562.     Like other dboptions, you can set these features on the model database
  563.     before creating new databases and all your future databases will be
  564.     configured. Be warned of the pitfalls of large identity gaps however; see
  565.     the question regarding Burn Factor Vulnerability in the Common Questions
  566.     about Identities section.
  567.    
  568.   * The existence of the @@identity global variable, which keeps track of the
  569.     identity value assigned during the last insert executed by the server. This
  570.     variable can be used programming SQL around tables that have identity
  571.     values (in case you need to know what the last value inserted was). If the
  572.     last value inserted in the server was to a non-identity table, this value
  573.     will be "0."
  574.  
  575. Back to start of 6.2.9
  576.  
  577. -------------------------------------------------------------------------------
  578.  
  579. Disadvantages/Drawbacks of Using Identities
  580.  
  581. Despite its efficacy of use, the Identity has some drawbacks:
  582.  
  583.   * The mechanism that Sybase uses to allocate Identities involves a memory
  584.     based prefetch scheme for performance. The downside of this is, during
  585.     non-normal shutdowns of ASE (shutdown with nowait or flat out crashes) ASE
  586.     will simply discard or "burn" all the unused identity values it has
  587.     pre-allocated in memory. This sometimes leaves large "gaps" in your
  588.     monotonically increasing identity columns and can be unsettling for some
  589.     application developers and/or end users.
  590.    
  591.     NOTE: Sybase 11.02.1 (EBF 6717) and below had a bug (bugid 96089) which
  592.     would cause "large gaps to occur in identity fields after polite
  593.     shutdowns." The Sybase 11.02.2 rollup (EBF 6886) fixed this problem. If
  594.     you're at or below 11.02.1 and you use identities, you should definitely
  595.     upgrade.
  596.    
  597.   * (paraphrased from Sybooks P&T guide, Chapter 6): If you do a large number
  598.     of inserts and you have built your clustered index on an Identity column,
  599.     you will have major contention and deadlocking problems. This will
  600.     instantly create a hot spot in your database at the point of the last
  601.     inserted row, and it will cause bad contention if multiple insert requests
  602.     are received at once. Instead, create your clustered index on a field that
  603.     will somewhat randomize the inserts across the physical disk (such as last
  604.     name, account number, social security number, etc) and then create a
  605.     non-clustered index based on the identity field that will "cover" any
  606.     eligible queries.
  607.    
  608.     The drawback here, as pointed out in the Identity Optimization section in
  609.     more detail, is that clustering on another field doesn't truly resolve the
  610.     concurrency issues. The hot spot simply moves from the last data page to
  611.     the last non-clustered index page of the index created on the Identity
  612.     column.
  613.    
  614.   * If you fill up your identity values, no more inserts can occur. This can be
  615.     a big problem, especially if you have a large number of inserts and you
  616.     have continually crashed your server. However this problem most often
  617.     occurs when you try to alter a table and add an Identity column that's too
  618.     small, or if you try to bcp into a table with an identity column thetas too
  619.     small. If this occurs, follow the procedures for recovering from identity
  620.     gaps.
  621.   * I've heard (but not been able to reproduce) that identities jump
  622.     significantly when dumping and loading databases. Not confirmed.
  623.    
  624.    
  625.     NOTE: there are several other System 11 bugs related to Identities. EBF
  626.     7312 fixes BugId 97748, which caused duplicate identity values to be
  627.     inserted at times. EBF 6886 fixed (in addition to the above described bug)
  628.     an odd bug (#82460) which caused a server crash when bcping into a table w/
  629.     an identity added via alter table. As always, try to stay current on EBFs.
  630.    
  631. Back to start of 6.2.9
  632.  
  633. -------------------------------------------------------------------------------
  634.  
  635. Common questions about Identities
  636.  
  637. Is the Identity the equivalent of Oracle's auto-sequencing?:
  638.  
  639. Answer: More or less yes. Oracle's auto-sequencing feature is somewhat
  640. transparent to the end user and automatically increments if created as a
  641. primary key upon a row insert. The Sybase Identity column is normally specified
  642. at table creation and thus is a functional column of the table. If however you
  643. set the "auto identity" feature for a database, the tables created will have a
  644. "hidden" identity column that doesn't even appear when you execute a select *
  645. from [table]. See the Advantages of Identities for more details.
  646.  
  647.   * How do I configure Identities?: You can either create your table initially
  648.     with the identity column:
  649.     1> create table ident_test
  650.     2> (text_field varchar(10),
  651.     3>  ident_field numeric(5,0) identity)
  652.     4> go
  653.    
  654.     Or alter an existing table and add an identity column:
  655.    
  656.     1> alter table existing_table
  657.     2> add new_identity_field numeric(7,0) identity
  658.     3> go
  659.    
  660.     When you alter a table and add an identity column, the System locks the
  661.     table while systematically incrementing and adding unique values to each
  662.     row. IF YOU DON'T SPECIFY a precision, Sybase defaults the size to 18!
  663.     Thats 1,000,000,000,000,000,000-1 possible values and some major major
  664.     problems if you ever crash your ASE and burn a default number of values...
  665.     (10^18 with the default burn factor will burn 5^14 or 500,000,000,000,000
  666.     values...yikes).
  667.    
  668.    
  669.   * How do I Configure the burn factor?: The number of identity values that
  670.     gets "burned" upon a crash or a shutdown can by found by logging into the
  671.     server and typing:
  672.     1> sp_configure "identity burning set factor"
  673.     2> go
  674.    
  675.     the Default value set upon install is 5000. The number "5000" in this case
  676.     is read as ".05% of all the potential identity values you can have in this
  677.     particular case will be burned upon an unexpected shutdown." The actual
  678.     number depends on the size of the identity field as you specified it when
  679.     you created your table.
  680.    
  681.     To set the burn factor, type:
  682.    
  683.     1> sp_configure "identity burning set factor", [new value]
  684.     2> go
  685.    
  686.     This is a static change; the server must be rebooted before it takes
  687.     effect.
  688.    
  689.    
  690.   * How do I tell which tables have identities?: You can tell if a table has
  691.     identities one of two ways:
  692.    
  693.      1. sp_help [tablename]: there is a field included in the sp_help output
  694.         describing a table called "Identity." It is set to 1 for identity
  695.         fields, 0 otherwise.
  696.      2. Within a database, execute this query:
  697.         1> select object_name(id) "table",name "column", prec "precision"
  698.         2> from syscolumns
  699.         3> where convert(bit, (status & 0x80)) = 1
  700.         4> go
  701.    
  702.     this will list all the tables and the field within the table that serves as
  703.     an identity, and the size of the identity field.
  704.    
  705.    
  706.   * What is my identity burn factor vulnerability right now?:
  707.     In other words, what would happen to my tables if I crashed my server right
  708.     now?
  709.    
  710.     Identities are created type numeric, scale 0, and precision X. A precision
  711.     of 9 means the largest identity value the server will be able to process is
  712.     10^9-1, or 1,000,000,000-1, or 999,999,999. However, when it comes to
  713.     Burning identities, the server will burn (based on the default value of
  714.     5000) .05% of 1,000,000,000 or 500,000 values in the case of a crash. (You
  715.     may think an identity precision allowing for 1 Billion rows is optimistic,
  716.     but I once saw a precision set at 14...then the database crashed and their
  717.     identity values jumped 5 TRILLION. Needless to say they abandoned their
  718.     original design. Even worse, SQL server defaults precision to 18 if you
  719.     don't specify it upon table creation...that's a MINIMUM 10,000,000,000 jump
  720.     in identity values upon a crash with the absolute minimum burn factor)
  721.    
  722.     Lets say you have inserted 5 rows into a table, and then you crash your
  723.     server and then insert 3 more rows. If you select all the values of your
  724.     identity field, it will look like this:
  725.     1> select identity_field from id_test
  726.     2> go
  727.     identity_field
  728.     --------------
  729.     1
  730.     2
  731.     3
  732.     4
  733.     5
  734.     500006
  735.     500007
  736.     500008
  737.     
  738.     (8 rows affected)
  739.    
  740.     Here's your Identity burning options (based on a precision of 10^9 as
  741.     above):
  742.    
  743.     Burn value  % of values     # values burned during crash
  744.     5000                .05%            500,000
  745.     1000                .01%            100,000
  746.     100         .001%           10,000
  747.     10          .0001%          1,000
  748.     1           .00001%         100
  749.    
  750.     So, the absolute lowest amount of numbers you'll burn, assuming you
  751.     configure the burn factor down to 1 (sp_configure "identity burning set
  752.     factor", 1) and a precision of 9, is 100 values.
  753.    
  754.     Back to start of 6.2.9
  755.    
  756.     ---------------------------------------------------------------------------
  757.    
  758.     Optimizing your Identity setup for performance and maintenance
  759.    
  760.     If you've chosen to use Identities in your database, here are some
  761.     configuration tips to avoid typical Identity pitfalls:
  762.       + Tune the burn factor!: see the vulnerability section for a discussion
  763.         on what happens to identity values upon ASE crashes. Large jumps in
  764.         values can crash front ends that aren't equipped to handle and process
  765.         numbers upwards of 10 Trillion. I've seen Powerbuilder applications
  766.         crash and/or not function properly when trying to display these large
  767.         identity values.
  768.       + Run update statistics often on tables w/ identities: Any index with an
  769.         identity value as the first column in the search condition will have
  770.         its performance severely hampered if Update statistics is not run
  771.         frequently. Running a nightly update statistics/sp_recompile job is a
  772.         standard DBA task, and should be run often regardless of the existence
  773.         of identities in your tables.
  774.       + Tune the "Identity Grab Size": ASE defaults the number of Identity
  775.         values it pre-fetches to one (1). This means that in high insert
  776.         environments the Server must constantly update its internal identity
  777.         placeholder structure before adding the row. By tuning this parameter
  778.         up:
  779.         1> sp_configure "identity grab size", [number]
  780.         2> go
  781.        
  782.         You can prefetch larger numbers of values for each user as they log
  783.         into the server an insert rows. The downside of this is, if the user
  784.         doesn't use all of the prefetched block of identity values, the unused
  785.         values are lost (seeing as, if another user logs in the next block gets
  786.         assigned to him/her). This can quickly accelerate the depletion of
  787.         identity values and can cause gaps in Identity values.
  788.         (this feature is new with System 11)
  789.        
  790.       + Do NOT build business rules around Identity values. More generally
  791.         speaking the recommendation made by DBAs is, if your end users are EVER
  792.         going to see the identity field during the course of doing their job,
  793.         then DON'T use it. If your only use of the Identity field is for its
  794.         advertised purpose (that being solely to have a uniquely identifying
  795.         row for a table to index on) then you should be fine.
  796.       + Do NOT build your clustered index on your Identity field, especially if
  797.         you're doing lots of inserts. This will create a hot spot of contention
  798.         at the point of insertion, and in heavier OLTP environments can be
  799.         debilitating.
  800.    
  801.     - There is an excellent discussion in document http://www.sybase.com/
  802.     detail?id=860 on the performance and tuning aspects of Identities. It
  803.     supplements some of the information located here (Note: this will open in a
  804.     new browser window).
  805.    
  806.     Back to start of 6.2.9
  807.    
  808.     ---------------------------------------------------------------------------
  809.    
  810.     Recovery from Large Identity value gaps or
  811.     Recovery from Identity insert errors/Full Identity tables
  812.    
  813.    
  814.     This section will discuss how to re-order the identity values for a table
  815.     following a crash/abnormal shutdown that has resulted in huge gaps in the
  816.     values. The same procedure is used in cases where the identity field has
  817.     "filled up" and does not allow inserts anymore. Some applications that use
  818.     Identities are not truly candidates for this process (i.e., applications
  819.     that depend on the identity field for business purposes as opposed to
  820.     simple unique row identifiers). Applications like this that wish to rid
  821.     their dependence on identities will have to re-evaluate their database
  822.     design.
  823.       + Method 1:bcp out and in:
  824.         - First, (from O/S command line):
  825.         % bcp database..table out [data_file] -Usa -S[server] -N
  826.        
  827.         This will create a binary bcp datafile and will force the user to
  828.         create a .fmt file. The -N option tells the server to skip the identity
  829.         field while bcp'ing out.
  830.         - drop and recreate the table in question from ddl (make sure your
  831.         table ddl specifies the identity field).
  832.         - Now bcp back in:
  833.        
  834.         % bcp database.table in [data_file -Usa -S[server] -f[fmt file] -N
  835.        
  836.         The -N option during bcp in tells the server to ignore the data file's
  837.         placeholder column for the defined identity column.
  838.        
  839.        
  840.         Coincidentally, if you bcp out w/o the -N option, drop the table,
  841.         recreate from ddl specifying the identity field, and bcp back in w/o
  842.         the -N option, the same effect as above occurs.
  843.        
  844.         (note: if you bcp out a table w/ identity values and then want to
  845.         preserve the identity values during the bcp back in, use the "-E"
  846.         option.)
  847.        
  848.       + Method 2: select into a new table, adding the identity column as you go
  849.         : Follow this process:
  850.         1> select [all columns except identity column]
  851.         2> [identity column name ] = identity(desired_precision)
  852.         3> into [new_table]
  853.         4> from [old table]
  854.         5> go
  855.       + There are alternate methods that perform the above in multi steps, and
  856.         might be more appropriate in some situations.
  857.           o You can bcp out all the fields of a table except the identity
  858.             column (create the bcp format file from the original table, edit
  859.             out the identity column, and re-bcp). At this point you can create
  860.             a new table with or without the identity column; if you create it
  861.             with, as you bcp back in the Server will assign new identity
  862.             values. If you create it without, you can bcp back in normally and
  863.             then alter the table and add the identity later.
  864.           o You can select all columns but the identity into a new table, then
  865.             alter that table and add an identity later on.
  866.    
  867.     Back to start of 6.2.9
  868.    
  869.     ---------------------------------------------------------------------------
  870.    
  871.     How do I generate Sequential Keys w/o the Identity feature?
  872.    
  873.    
  874.     There are many reasons not to use the Identity feature of Sybase. This
  875.     section will present several alternative methods, along with their
  876.     advantages and drawbacks. The methods are presented in increasing order of
  877.     complexity. The most often implemented is Method 3, which is a more robust
  878.     version of Method 2 and which uses a surrogate-key storage table.
  879.    
  880.     Throughout this section the test table I'm adding lines to and generating
  881.     sequential numbers for is table inserttest, created like this:
  882.    
  883.     1> create table inserttest
  884.     2> (testtext varchar(25), counter int)
  885.     3> go
  886.       + Method 1: Create your table with a column called counter of type int.
  887.         Then, each time you insert a row, do something like this:
  888.         1> begin tran
  889.         2> declare @nextkey int
  890.         3> select @nextkey=max(counter)+1 from inserttest holdlock
  891.         4> insert inserttest (testtext,counter) values ("test_text,@nextkey")
  892.         5> go
  893.         1> commit tran
  894.         2> go
  895.        
  896.         This method is rather inefficient, as large tables will take minutes to
  897.         return a max(column) value, plus the entire table must be locked for
  898.         each insert (since the max() will perform a table scan). Further, the
  899.         select statement does not guarantee an exclusive lock when it executes
  900.         unless you have the "holdlock" option; so either duplicate values might
  901.         be inserted to your target table or you have massive deadlocking.
  902.        
  903.        
  904.       + Method 2: See Question 10.1.1 of the comp.database.sybase FAQ is the
  905.         May 1994 (Volume 3, Number 2) Sybase Technical Note (these links will
  906.         open in a new browser window). Search down in the tech note for the
  907.         article titled, "How to Generate Sequential Keys for Table Key
  908.         Columns." This has a simplistic solution that is expanded upon in
  909.         Method 3.
  910.        
  911.       + Method 3: Create a holding table for keys in a common database: Here's
  912.         our central holding table.
  913.         1> create table keystorage
  914.         2> (tablename varchar(25),
  915.         4>  lastkey int)
  916.         5> go
  917.        
  918.         And initially populate it with the tablenames and last values inserted
  919.         (enter in a 0 for tables that are brand new).
  920.        
  921.         1> insert into keystorage (tablename,lastkey)
  922.         2> select "inserttest", max(counter) from inserttest
  923.         3> go
  924.        
  925.         Now, whenever you go to insert into your table, go through a process
  926.         like this:
  927.        
  928.         1> begin tran
  929.         2> update keystorage set lastkey=lastkey+1 where tablename="inserttest"
  930.         3> go
  931.         
  932.         1> declare @lastkey int
  933.         2> select @lastkey = lastkey from keystorage where tablename="inserttest"
  934.         3> insert inserttest (testtext,counter) values ("nextline",@lastkey)
  935.         4> go
  936.         
  937.         1> commit tran
  938.         2> go
  939.        
  940.         There is plenty of room for error checking with this process: for
  941.         example (code adapted from Colm O'Reilly (colm@mail.lk.blackbird.ie)
  942.         post to Sybase-L 6/20/97):
  943.        
  944.         1> begin tran
  945.         2>   update keystorage set lastkey=lastkey+1 where tablename="inserttest"
  946.         3>   if @@rowcount=1
  947.         4>   begin
  948.         5>     declare @lastkey int
  949.         6>     select @lastkey=lastkey from keystorage where tablename="inserttest"
  950.         7>   end
  951.         8> commit tran
  952.         9> begin tran
  953.         10>   if @lastkey is not null
  954.         11>   begin
  955.         12>     insert inserttest (testtext,counter) values ("third line",@lastkey)
  956.         13>   end
  957.         14> commit tran
  958.         15> go
  959.        
  960.         This provides a pretty failsafe method of guaranteeing the success of
  961.         the select statements involved in the process. You still have a couple
  962.         of implementation decisions though:
  963.           o One transaction or Two? The above example uses two transactions to
  964.             complete the task; one to update the keystorage and one to insert
  965.             the new data. Using two transactions reduces the amount of time the
  966.             lock is held on keystorage and thus is better for high insertion
  967.             applications. However, the two transaction method opens up the
  968.             possibility that the first transaction will commit and the second
  969.             will roll back, leaving a gap in the sequential numbers. (of
  970.             course, this gap is small potatoes compared to the gaps that occur
  971.             in Identity values). Using one transaction (deleting lines 8 and 9
  972.             in the SQL above) will guarantee absolutely no gaps in the values,
  973.             but will lock the keystorage table longer, reducing concurrency in
  974.             high insert applications.
  975.           o Update first or select first? The examples given generally update
  976.             the keystorage table first, THEN select the new value. Performing
  977.             the select first (you will have to rework the creation scheme
  978.             slightly; by selecting first you're actually getting the NEXT key
  979.             to add, where as by updating first, the keystorage table actually
  980.             holds the LAST key added) you allow the application to continue
  981.             processing while it waits for the update lock on the table.
  982.             However, performing the update first guarantees uniqueness (selects
  983.             are not exclusive).
  984.        
  985.        
  986.         Some DBAs experienced with this keystorage table method warn of large
  987.         amounts of blocking in high insert activity situations, a potential
  988.         drawback.
  989.        
  990.        
  991.       + Method 4: Enhance the above method by creating an insert trigger on
  992.         your inserttest table that performs the next-key obtainment logic. Or
  993.         you could create an insert trigger on keystorage which updates the
  994.         table and obtains your value for you. Integrating the trigger logic to
  995.         your application might make this approach more complex. Also, because
  996.         of the nature of the trigger you'll have to define the sequence number
  997.         columns as allowing NULL values (a bad thing if you're depending on the
  998.         sequential number as your primary key). Plus, triggers will slow the
  999.         operation down because after obtaining the new value via trigger,
  1000.         you'll have to issue an extra update command to insert the rest of your
  1001.         table values.
  1002.       + Method 5: (Thanks to John Drevicky (jdrevicky@tca-techsys.com))
  1003.         The following procedure is offered as another example of updating and
  1004.         returning the Next Sequential Key, with an option that allows automatic
  1005.         reuse of numbers......
  1006.           -----------------------------------------------------------------
  1007.           ----
  1008.           --
  1009.           DECLARE @sql_err int, @sql_count int
  1010.           --
  1011.           begin tran
  1012.           --
  1013.           select @out_seq = 0
  1014.           --
  1015.           UPDATE NEXT_SEQUENCE
  1016.              SET next_seq_id
  1017.                     =  ( next_seq_id
  1018.                      * ( sign(1 + sign(max_seq_id - next_seq_id) ) -- evaluates: 0 [when
  1019.                                                                    -- next > max]; else 1
  1020.                      *   sign(max_seq_id - next_seq_id)            -- evaluates: 0 [when next = max];
  1021.                                                                    --            1 [next < max];
  1022.                                                                    --           -1 [next > max]
  1023.                        )                                           -- both evaluate to 1 when next < max
  1024.                                 ) + 1                              -- increment by [or restart at] 1
  1025.            WHERE seq_type = @in_seq_type
  1026.           --
  1027.           select @sql_err = @@error, @sql_count = @@rowcount
  1028.           --
  1029.           IF @sql_err = 0 and @sql_count = 1
  1030.           BEGIN
  1031.             select @out_seq = next_seq_id
  1032.               from NEXT_SEQUENCE
  1033.              where seq_type = @in_seq_type
  1034.             --
  1035.             commit tran
  1036.             return 0
  1037.           END
  1038.           ELSE
  1039.           BEGIN
  1040.             RAISERROR 44999 'Error %1! returned from proc derive_next_sequence...no update occurred', @sql_err
  1041.             rollback tran
  1042.           END
  1043.       + Other Methods: there are several other implementation alternatives
  1044.         available that involve more complex logic but which might be good
  1045.         solutions. One example has a central table that stores pre-inserted
  1046.         sequential numbers that are deleted as they're inserted into the
  1047.         production rows. This method allows the sequence numbers to be recycled
  1048.         if their associated row is deleted from the production table. An
  1049.         interesting solution was posted to Sybase-L 6/20/97 by Matt Townsend ( 
  1050.         mtowns@concentric.net) and is based on the millisecond field of the
  1051.         date/time stamp. His solution guarantees uniqueness without any
  1052.         surrogate tables or extra inserts/updates, and is a superior performing
  1053.         solution to other methods described here (including Identities), but
  1054.         cannot support exact sequential numbers. Some other solutions are
  1055.         covered in a white paper available at Sybase's Technical library
  1056.         discussing Sequential Keys (this will open in a new browser window).
  1057.    
  1058.     Back to start of 6.2.9
  1059.    
  1060.     ---------------------------------------------------------------------------
  1061.    
  1062.     Optimizing your home grown Sequential key generating process for any
  1063.     version of Sybase
  1064.    
  1065.       + max_rows_per_page/fillfactor/table padding to simulate row level
  1066.         locking: This is the most important tuning mechanism when creating a
  1067.         hand -made sequence key generation scheme. Because of Sybase's page
  1068.         level locking mechanism, your concurrency performance in higher-insert
  1069.         activity situations could be destroyed unless the server only grabs one
  1070.         row at a time. However since Sybase doesn't currently have row-level
  1071.         locking, we simulate row-level locking by creating our tables in such a
  1072.         way as to guarantee one row per 2048 byte page.
  1073.           o For pre-System 11 servers; Calculate the size of your rows, then
  1074.             create dummy fields in the table that get populated with junk but
  1075.             which guarantee the size of the row will fill an entire page. For
  1076.             example (code borrowed from Gary Meyer's 5/8/94 ISUG presentation (
  1077.             gmeyer@netcom.com)):
  1078.             1> create table keystorage
  1079.             2> (tablename varchar(25),
  1080.             3>  lastkey int,
  1081.             4>  filler1 char(255) not null,
  1082.             5>  filler2 char(255) not null,
  1083.             6>  filler3 char(255) not null,
  1084.             7>  filler4 char(255) not null,
  1085.             8>  filler5 char(255) not null,
  1086.             9>  filler6 char(255) not null,
  1087.             9>  filler7 char(255) not null)
  1088.             10> with fillfactor = 100
  1089.             11> go
  1090.            
  1091.             We use 7 char(255) fields to pad our small table. We also specify
  1092.             the fillfactor create table option to be 100. A fillfactor of 100
  1093.             tells the server to completely fill every data page. Now, during
  1094.             your initial insertion of a line of data, do this:
  1095.            
  1096.             1> insert into keystorage
  1097.             2>   (tablename,lastkey,
  1098.             3>   filler1,filler2,filler3,filler4,filler5,filler6,filler7)
  1099.             4> values
  1100.             5>   ("yourtable",0,
  1101.             6>   replicate("x",250),replicate("x",250),
  1102.             7>   replicate("x",250),replicate("x",250),
  1103.             8>   replicate("x",250),replicate("x",250),
  1104.             9>   replicate("x",250))
  1105.             10> go
  1106.            
  1107.             This pads the row with 1750 bytes of junk, almost guaranteeing
  1108.             that, given a row's byte size limit of 1962 bytes (a row cannot
  1109.             span more than one page, thus the 2048 page size minus server
  1110.             overhead == 1962), we will be able to simulate row level locking.
  1111.            
  1112.           o In Sybase 11, a new create table option was introduced:
  1113.             max_rows_per_page. It automates the manual procedures above and
  1114.             guarantees at a system level what we need to achieve; one row per
  1115.             page.
  1116.             1> create table keystorage
  1117.             2> (tablename varchar(25),
  1118.             3>  lastkey int)
  1119.             4> with max_rows_per_page = 1
  1120.             5> go
  1121.       + Create unique clustered indexes on the tablename/entity name within
  1122.         your keystorage table. This can only improve its performance. Remember
  1123.         to set max_rows_per_page or the fillfactor on your clustered index, as
  1124.         clustered indexes physically reorder the data.
  1125.       + Break up the process into multiple transactions wherever possible; this
  1126.         will reduce the amount of time any table lock is held and will increase
  1127.         concurrency in high insertion environments.
  1128.       + Use Stored Procedures: Put the SQL commands that update the keystorage
  1129.         table and then insert the updated key value into a stored procedure.
  1130.         Stored procedures are generally faster than individual SQL statements
  1131.         in your code because procedures are pre-compiled and have optimization
  1132.         plans for index usage stored in Sybase's system tables.
  1133.       + Enhance the keystorage table to contain a fully qualified table name as
  1134.         opposed to just the tablename. This can be done by adding fields to the
  1135.         table definition or by just expanding the entity name varchar field
  1136.         definition. Then place the keystorage table in a central location/
  1137.         common database that applications share. This will eliminate multiple
  1138.         keystorage tables but might add length to queries (since you have to do
  1139.         cross-database queries to obtain the next key).
  1140.        
  1141.         - There is an excellent discussion located in the whitepapers section
  1142.         of Sybase's home page discussing the performance and tuning aspects of
  1143.         any type of Sequential key use. It supplements the information here
  1144.         (note: this page will open in a new browser window).
  1145.        
  1146. Back to start of 6.2.9
  1147.  
  1148. Back to top
  1149.  
  1150. -------------------------------------------------------------------------------
  1151.  
  1152. 6.2.10: How can I execute dynamic SQL with ASE?
  1153.  
  1154. -------------------------------------------------------------------------------
  1155.  
  1156. Adaptive Server Enterprise: System 12
  1157.  
  1158. ASE 12 supports dynamic SQL, allowing the following:
  1159.  
  1160. declare @sqlstring varchar(255)
  1161. select @sqlstring = "select count(*) from master..sysobjects"
  1162. exec (@sqlstring)
  1163. go
  1164.  
  1165. Adaptive Server Enterprise: 11.5 and 11.9
  1166.  
  1167. There is a neat trick that was reported first by Bret Halford ( bret@sybase.com
  1168. ).  (If anyone knows better, point me to the proof and I will change this!)  It
  1169. utilises the CIS features of Sybase ASE.
  1170.  
  1171.   * Firstly define your local server to be a remote server using
  1172.     sp_addserver LOCALSRV,sql_server[,INTERFACENAME]
  1173.     go
  1174.  
  1175.   * Enable CIS
  1176.     sp_configure "enable cis",1
  1177.     go
  1178.  
  1179.   * Finally, use sp_remotesql, sending the sql to the server defined in point
  1180.     1.
  1181.     declare @sqlstring varchar(255)
  1182.     select @sqlstring = "select count(*) from master..sysobjects"
  1183.     sp_remotesql LOCALSRV,@sqlstring
  1184.     go
  1185.  
  1186. Remember to ensure that all of the databases referred to in the SQL string are
  1187. fully qualified since the call to sp_remotesql places you back in your default
  1188. database.
  1189.  
  1190.  
  1191.  
  1192. Sybase ASE (4.9.x, 10.x and 11.x before 11.5)
  1193.  
  1194. Before System 11.5 there was no real way to execute dynamic SQL.  Rob Verschoor
  1195. has some very neat ideas that fills some of the gaps (http://www.euronet.nl/
  1196. ~syp_rob/dynsql.html).
  1197.  
  1198. Dynamic Stored Procedure Execution
  1199.  
  1200. With System 10, Sybase introduced the ability to execute a stored procedure
  1201. dynamically.
  1202.  
  1203. declare @sqlstring varchar(255)
  1204. select @sqlstring = "sp_who"
  1205. exec @sqlstring
  1206. go
  1207.  
  1208. For some reason Sybase chose never to document this feature.
  1209.  
  1210. Obviously all of this is talking about executing dynamic SQL within the server
  1211. itself ie stored procedures and triggers.  Dynamic SQL within client apps is a
  1212. different matter altogether.
  1213.  
  1214. Back to top
  1215.  
  1216. -------------------------------------------------------------------------------
  1217.  
  1218. 6.2.11: Is it possible to concatenate all the values from a column and return a
  1219. single row?
  1220.  
  1221. -------------------------------------------------------------------------------
  1222.  
  1223. Hey, this was quite cool I thought. It is now possible to concatenate a series
  1224. of strings to return a single column, in a sort of analogous manner to sum
  1225. summing all of the numbers in a column.  Obviously, in versions before 12.5,
  1226. the longest string that you can have is 255 characters, but with very long
  1227. varchars, this may prove useful to someone.
  1228.  
  1229. Use a case statement, a la,
  1230.  
  1231. 1> declare @string_var varchar(255)
  1232. 2>
  1233. 3> select @string_var = ""
  1234. 4>
  1235. 5> select @string_var = @string_var +
  1236. 6>                       (case 1 when 1
  1237. 7>                               then char_col
  1238. 8>                        end)
  1239. 9> from tbl_a
  1240. 10>
  1241. 11> print "%1!", @string_var
  1242. 12> go
  1243. (1 row affected)
  1244.  ABCDEFGH
  1245. (8 rows affected)
  1246. 1> select * from tbl_a
  1247. 2> go
  1248.  char_col
  1249.  --------
  1250.  A
  1251.  B
  1252.  C
  1253.  D
  1254.  E
  1255.  F
  1256.  G
  1257.  H
  1258.  
  1259. (8 rows affected)
  1260. 1> 
  1261.  
  1262. Back to top
  1263.  
  1264. -------------------------------------------------------------------------------
  1265.  
  1266. 6.2.12: Selecting rows N to M without Oracle's rownum?
  1267.  
  1268. -------------------------------------------------------------------------------
  1269.  
  1270. Sybase does not have a direct equivalent to Oracle's rownum but its
  1271. functionality can be emulated in a lot of cases.
  1272.  
  1273. If you are simply trying to retrieve the first N rows of a table, then simple
  1274. use:
  1275.  
  1276. set rowcount 
  1277.  
  1278. replacing <N> with your desired number of rows.  (set rowcount 0 restores
  1279. normality.) If it is simply the last N rows, then use a descending order-by
  1280. clause in the select.
  1281.  
  1282. 1> set rowcount 
  1283. 2> go
  1284. 1> select foo
  1285. 2> from bar
  1286. 3> order by barID desc
  1287. 4> go
  1288.  
  1289. If you are trying to retrieve rows 100 to 150, say, from a table in a given
  1290. order.  You could use this to retrieve rows for a set of web pages, but there
  1291. are probably more efficient ways using cursors or well written queries or even
  1292. Sybperl!  The general idea is select the rows into a temporary table adding an
  1293. identity column at the same time.  Only select enough rows to do the job using
  1294. the rowcount trick.  Finally, return the rows from the temporary table where
  1295. the identity column is between 100 and 150.  Something like this:
  1296.  
  1297. set rowcount 150
  1298.  
  1299. select pseudo_key = identity(3),
  1300.        col1,
  1301.        col2
  1302.   into #tempA
  1303.   from masterTable
  1304.  where clause...
  1305.  order by 2,3
  1306.  
  1307. select col1,col2 from #tempA where pseudo_key between 100 and 150
  1308.  
  1309. Remember to reset rowcount back to 0 before issuing any more SQL or you will
  1310. only get back 150 rows!
  1311.  
  1312. A small optimisation would be to select only the key columns for the source
  1313. table together with the identity key. Once you have the set of rows you require
  1314. in the temporary table, join this back to the source using the key columns to
  1315. get any data that you require.
  1316.  
  1317. An alternative, which might be better if you needed to join back to this table
  1318. a lot, would be to insert enough rows to cover the range as before, but then
  1319. delete the set of unwanted rows. This would be a very efficient mechanism if
  1320. the majority of your queries involved the first few rows of a table. A typical
  1321. application for this might be a search engine displaying relevant items first.
  1322. The chances are that the user is going to be bored after the first couple of
  1323. pages and go back to playing 'Internet Doom'.
  1324.  
  1325. set rowcount 150
  1326.  
  1327. select col1,
  1328.        col2
  1329.   into #tempA
  1330.   from masterTable
  1331.  where clause...
  1332.  
  1333. set rowcount 100
  1334.  
  1335. delete #tempA
  1336.  
  1337. Sybase does not guarantee to return rows in any particular order, so the delete
  1338. may not delete the correct set of rows. In the above example, you should add an
  1339. order-by to the 'select' and build a clustered index on a suitable key in the
  1340. temporary table.
  1341.  
  1342. The following stored proc was posted to the Sybase-L mailing list and uses yet
  1343. another mechanism. You should check that it works as expected in your
  1344. environment since it relies on the fact a variable will be set using the last
  1345. row that is returned from a result set. This is not published behaviour and is
  1346. not guaranteed by Sybase.
  1347.  
  1348. CREATE PROCEDURE dbo.sp_get_posts
  1349.     @perpage    INT,
  1350.     @pagenumber INT
  1351. WITH RECOMPILE
  1352. AS
  1353.  
  1354.    -- if we're on the first page no need to go through the @postid push
  1355.    IF @pagenumber = 1
  1356.    BEGIN
  1357.       SET ROWCOUNT @perpage
  1358.  
  1359.       SELECT ...
  1360.       RETURN
  1361.    END
  1362.  
  1363.    -- otherwise
  1364.  
  1365.    DECLARE @min_postid NUMERIC( 8, 0 ),
  1366.            @position   INT
  1367.  
  1368.    SELECT @position = @perpage * ( @pagenumber - 1 ) + 1
  1369.  
  1370.    SET ROWCOUNT @position
  1371.  
  1372.    -- What happens here is it will select through the rows
  1373.    -- and order the whole set.
  1374.    -- It will stop push postid into @min_postid until it hits
  1375.    -- ROWCOUNT and does this out of the ordered set (a work
  1376.    -- table).
  1377.  
  1378.    SELECT @min_postid = postid
  1379.      FROM post
  1380.     WHERE ...
  1381.     ORDER BY postid ASC
  1382.  
  1383.    SET ROWCOUNT @perpage
  1384.  
  1385.    -- we know where we want to go (say the 28th post in a set of 50).
  1386.     SELECT ...
  1387.       FROM post
  1388.      WHERE postid >= @min_postid
  1389.            ...
  1390.   ORDER BY postid ASC
  1391.  
  1392. Yet another solution would be to use a loop and a counter. Probably the least
  1393. elegant, but again, it would depend on what you were trying to do as to what
  1394. would be most appropriate.
  1395.  
  1396. As you can see, none of these are particularly pretty. If you know of a better
  1397. method, please forward it to dowen@midsomer.org.
  1398.  
  1399. Back to top
  1400.  
  1401. -------------------------------------------------------------------------------
  1402.  
  1403. 6.2.13: How can I return number of rows that are returned from a grouped query
  1404. without using a temporary table?
  1405.  
  1406. -------------------------------------------------------------------------------
  1407.  
  1408. This question is certainly not rocket science, but it is often nice to know how
  1409. many rows are returned as part of a group by. This might be for a report or a
  1410. web query, where you would want to tell the user how many rows were returned on
  1411. page one. It is easy using a temp table, but how to do it without a temp table
  1412. is a little harder. I liked this solution and thought that it might not be
  1413. obvious to everyone, it was certainly educational to me. Thanks go to Karl Jost
  1414. for a very nice answer.
  1415.  
  1416. So, give data like:
  1417.  
  1418.  name     item
  1419.  ----     ----
  1420.  Brown    1
  1421.  Smith    2
  1422.  Brown    5
  1423.  Jones    7
  1424.  
  1425. you wish to return a result set of the form:
  1426.  
  1427.  name    sum(item)   rows
  1428.  ----    ---------   ----
  1429.  Brown   6           3
  1430.  Jones   7           3
  1431.  Smith   2           3
  1432.  
  1433. rather than
  1434.  
  1435.  name    sum(item)   rows
  1436.  ----    ---------   ----
  1437.  Brown   6           2
  1438.  Jones   7           1
  1439.  Smith   2           1
  1440.  
  1441. Use the following, beguilingly simple query:
  1442.  
  1443. select name, sum(item), sum(sign(count(*)))
  1444. from data
  1445. group by name
  1446.  
  1447. Back to top
  1448.  
  1449. -------------------------------------------------------------------------------
  1450.  
  1451. Useful SQL Tricks SQL Fundamentals ASE FAQ
  1452.  
  1453.                                Useful SQL Tricks                               
  1454.                                                                                
  1455.  
  1456.  
  1457.     6.3.1    How to feed the result set of one stored procedure into another.
  1458.     6.3.2    Is it possible to do dynamic SQL before ASE 12?
  1459.    
  1460. Open Client SQL Advanced ASE FAQ
  1461.  
  1462. -------------------------------------------------------------------------------
  1463.  
  1464. Note: A number of the following tips require CIS to be enabled (at this precise
  1465. moment, all of them require CIS :-) The optimiser does take on a different
  1466. slant, however small, when CIS is enabled, so it is up to you to ensure that
  1467. things don't break when you do turn it on. Buyer beware. Test, test, test and
  1468. when you have done that, check some more.
  1469.  
  1470. -------------------------------------------------------------------------------
  1471.  
  1472. 6.3.1: How to feed the result set of one stored procedure into another.
  1473.  
  1474. -------------------------------------------------------------------------------
  1475.  
  1476. I am sure that this is all documented, but it is worth adding here. It uses
  1477. CIS, as do a number of useful tricks. CIS is disabled by default before 12.0
  1478. and not available before 11.5. It is courtesy of BobW from
  1479. sybase.public.ase.general, full acceditation will be granted if I can find out
  1480. who he is. Excellent tip!
  1481.  
  1482. So, the scenario is that you have a stored procedure, AP_A, and you wish to use
  1483. the result set that it returns in a query.
  1484.  
  1485. Create a proxy table for SP_A.
  1486.  
  1487. create table proxy_SP_A (
  1488.   a int,
  1489.   b int,
  1490.   c int,
  1491.   _p1 int null,
  1492.   _p2 int null
  1493. ) external procedure
  1494.  at "SELF.dbname.dbo.SP_A"
  1495.  
  1496. Columns a, b, c correspond to the result set of SP_A. Columns _p1, _p2
  1497. correspond to the @p1, @p2 parameters of SP_A. "SELF" is an alias put in
  1498. sysservers to refer back to the local server.
  1499.  
  1500. If you only have one row returned the proxy table can be used with the
  1501. following:
  1502.  
  1503. declare @a int, @b int, @c int
  1504. select @a = a, @b = b, @c = c from proxy_SP_B
  1505. where _p1 = 3 and _p2 = 5
  1506.  
  1507. More rows can be handled with a cursor.
  1508.  
  1509. Back to top
  1510.  
  1511. -------------------------------------------------------------------------------
  1512.  
  1513. 6.3.2: Is it possible to do dynamic SQL before ASE 12?
  1514.  
  1515. -------------------------------------------------------------------------------
  1516.  
  1517. Again, using CIS, it is possible to fake dynamic SQL. Obviously for this to
  1518. work, CIS must be enabled. In addition, the local server must be added to
  1519. sysservers as a remote server. There is a stored procedure, sp_remotesql, that
  1520. takes as an arguments a remote server and a string, containing SQL.
  1521.  
  1522. As before, adding SELF as the 'dummy' server name pointing to the local server
  1523. as if it were a remote server, we can execute the following:
  1524.  
  1525. sp_remotesql "SELF","select * from sysdatabases"
  1526.  
  1527. Which will do just what you expect, running the query on the local machine. The
  1528. stored proc will take 251 (according to its own documentation) arguments of
  1529. char(255) or varchar(255) arguments, and concatenate them all together. So we
  1530. can do the following:
  1531. 1> declare @p1 varchar(255),@p2 varchar(255),@p3 varchar(255), @p4 varchar(255)
  1532. 2>
  1533. 3> select @p1 = "select",
  1534. 4>        @p2 = " name ",
  1535. 5>        @p3 = "from ",
  1536. 6>        @p4 = "sysdatabases"
  1537. 7>
  1538. 8> exec sp_remotesql "SELF", @p1, @p2, @p3, @p4
  1539. 9> go
  1540. (1 row affected)
  1541.  name
  1542.  ------------------------------
  1543.  bug_track
  1544.  dbschema
  1545.  master
  1546.  model
  1547.  sybsystemprocs
  1548.  tempdb
  1549.  
  1550. (6 rows affected, return status = 0)
  1551.  
  1552. Obviously, when the parameters are concatenated, they must form a legal T-SQL
  1553. statement. If we remove one of the spaces from the above statement, then we
  1554. see:
  1555.  
  1556. 1> declare @p1 varchar(255),@p2 varchar(255),@p3 varchar(255), @p4 varchar(255)
  1557. 2>
  1558. 3> select @p1 = "select",
  1559. 4>        @p2 = "name ",
  1560. 5>        @p3 = "from ",
  1561. 6>        @p4 = "sysdatabases"
  1562. 7>
  1563. 8> exec sp_remotesql "SELF", @p1, @p2, @p3, @p4
  1564. 9> go
  1565. Msg 156, Level 15, State 1
  1566. , Line 1
  1567. Incorrect syntax near the keyword 'from'.
  1568. (1 row affected, return status = 156)
  1569.  
  1570. Back to top
  1571.  
  1572. -------------------------------------------------------------------------------
  1573.  
  1574. Open Client SQL Advanced ASE FAQ
  1575.  
  1576.