Subject: Sybase FAQ: 8/16 - section 7
Date: 1 Sep 1997 06:03:06 GMT
Summary: Info about SQL Server, bcp, isql and other goodies
Posting-Frequency: monthly

Archive-name: databases/sybase-faq/part8
URL: http://reality.sgi.com/pablo/Sybase_FAQ

          Q7.2: HOW TO IMPLEMENT _IF-THEN-ELSE_ IN A _SELECT_ CLAUSE
                                       
   
     _________________________________________________________________
   
   If you need to implement the following condition in a _select_ clause:
   

if @val = 'small' then
   print 'petit'
else
   print 'grand'
fi

   do the following:
   

select isnull(substring('petit', charindex('small', @val), 255), 'grand')

   To test it out, try the following T-SQL:
   

declare @val char(20)

select @val = 'grand'

select isnull(substring('petit', charindex('small', @val), 255), 'grand')

   
     _________________________________________________________________

          Q7.4: HOW TO PAD WITH LEADING ZEROS AN _INT_ OR _SMALLINT_.
                                       
   
     _________________________________________________________________
   
   By example:

declare @Integer        int

/* Good for positive numbers only. */
select @Integer = 1000

select "Positives Only" =
       right( replicate("0", 12) + convert(varchar, @Integer), 12)

/* Good for positive and negative numbers. */
select @Integer = -1000

select "Both Signs" =
       substring( "- +", (sign(@Integer) + 2), 1) +
       right( replicate("0", 12) + convert(varchar, abs(@Integer)), 12)

select @Integer = 1000

select "Both Signs" =
       substring( "- +", (sign(@Integer) + 2), 1) +
       right( replicate("0", 12) + convert(varchar, abs(@Integer)), 12)

go

   Produces the following results:

 Positives Only
 --------------
 000000001000

 Both Signs
 -------------
 -000000001000

 Both Signs
 -------------
 +000000001000

   
     _________________________________________________________________

                        Q7.5: DIVIDE BY ZERO AND NULLS
                                       
   
     _________________________________________________________________
   
   During processing, if a divide by zero error occurs you will not get
   the answer you want. If you want the result set to come back and null
   to be displayed where divide by zero occurs do the following:

1> select * from total_temp
2> go
 field1      field2
 ----------- -----------
          10          10
          10           0
          10        NULL

(3 rows affected)
1> select field1, field1/(field2*convert(int,
                  substring('1',1,abs(sign(field2))))) from total_temp
2> go
 field1
 ----------- -----------
          10           1
          10        NULL
          10        NULL

   
     _________________________________________________________________

                   Q7.6: CONVERT MONTHS TO FINANCIAL MONTHS
                                       
   
     _________________________________________________________________
   
   To convert months to financial year months (i.e. July = 1, Dec = 6,
   Jan = 7, June = 12 )
   
  Method #1

select ... ((sign(sign((datepart(month,GetDate())-6) * -1)+1) *
   (datepart(month, GetDate())+6))
      + (sign(sign(datepart(month, GetDate())-7)+1) *
   (datepart(month, GetDate())-6)))
   ...
  from ...

  Method #1

select charindex(datename(month,getdate()),
"          July      August    September October   November  December
January   Febuary   March     April     May       June      "
                     ) / 10

   In the above example, the embedded blanks are significant.
     _________________________________________________________________

                       Q7.7: HIERARCHY TRAVERSAL - BOMS
                                       
   
     _________________________________________________________________
   
   Alright, so you wanna know more about representing hierarchies in a
   relational database? Before I get in to the nitty gritty I should at
   least give all of the credit for this algorithm to:
   "_Hierarical_Structures:_The_Relational_Taboo!_, _(Can_
   Transitive_Closure_Queries_be_Efficient?)_", by Michael J. Kamfonas as
   published in 1992 "Relational Journal" (I don't know which volume or
   issue).
   
   The basic algorithm goes like this, given a tree (hierarchy) that
   looks roughly like this (forgive the ASCII art--I hope you are using a
   fixed font to view this):

                                    a
                                   / \
                                 /     \
                               /         \
                             b             c
                            / \           /|\
                           /   \        /  |  \
                          /     \     /    |   \
                         d       e   f     |    g

     Note, that the tree need not be balanced for this algorithm to work.
     
   The next step assigned two numbers to each node in the tree, called
   left and right numbers, such that the left and right numbers of each
   node contain the left and right numbers of the ancestors of that node
   (I'll get into the algorithm for assigning these left and right
   numbers later, but, _hint: use a depth-first search_):

                                   1a16
                                   / \
                                 /     \
                               /         \
                            2b7           8c15
                            / \           /|\
                           /   \        /  |  \
                          /     \     /    |   \
                        3d4     5e6 9f10 11g12 13h14

     Side Note: The careful observer will notice that these left and
     right numbers look an awful lot like a B-Tree index.
     
   So, you will notice that all of the children of node 'a' have left and
   right numbers between 1 and 16, and likewise all of the children of
   'c' have left and right numbers between 8 and 15. In a slightly more
   relational format this table would look like:

              Table: hier
   node   parent left_nbr  right_nbr
   -----  ------ --------  ---------
   a        NULL        1         16
   b           a        2          7
   c           a        8         15
   d           b        3          4
   e           b        5          6
   f           c        9         10
   g           c       11         12
   h           c       13         14

   So, given a node name, say @node (in Sybase variable format), and you
   want to know all of the children of the node you can do:

    SELECT h2.node
      FROM hier   h1,
           hier   h2
     WHERE h1.node      =   @node
       AND h2.left_nbr  >   h1.left_nbr
       AND h2.left_nbr

If you had a table that contained, say, the salary for each node
in your hierarchy (assuming a node is actually a individual in a company)
you could then figure out the total salary for all of the people
working underneath of @node by doing:


    SELECT sum(s.salary)
      FROM hier   h1,
           hier   h2,
           salary s
     WHERE h1.node      =   @node
       AND h2.left_nbr  >   h1.left_nbr
       AND h2.left_nbr

Pretty cool, eh?  And, conversly, if you wanted to know how much it
cost to manage @node (i.e. the combined salary of all of the boss's
of @node), you can do:


    SELECT sum(s.salary)
      FROM hier   h1,
           hier   h2,
           salary s
     WHERE h1.node      =   @node
       AND h2.left_nbr     h1.right_nbr
       AND s.node       =   h2.node


Now that you can see the algorithm in action everything looks peachy,
however the sticky point is the method in which left and right numbers
get assigned. And, unfortunately, there is no easy method to do this
relationally (it can be done, it just ain't that easy).  For an real-
world application that I have worked on, we had an external program
used to build and maintain the hierarchies, and it was this program's
responsibility to assign the left and right numbers.




But, in brief, here is the algorithm to assign left and right
numbers to every node in a hierarchy.  Note while reading this
that this algorithm uses an array as a stack, however since arrays
are not available in Sybase, they are (questionably) emulated using
a temp table.


    DECLARE @skip            int,
            @counter         int,
            @idx             int,
            @left_nbr        int,
            @node            varchar(10)

    /*-- Initialize variables --*/
    SELECT @skip    = 1000,   /* Leave gaps in left & right numbers */
           @counter = 0,      /* Counter of next available left number */
           @idx     = 0       /* Index into array */

    /*
     * The following table is used to emulate an array for Sybase,
     * for Oracle this wouldn't be a problem. :(
     */
    CREATE TABLE #a (
        idx          int           NOT NULL,
        node         varchar(10)   NOT NULL,
        left_nbr     int           NOT NULL
    )

    /*
     * I know that I always preach about not using cursors, and there
     * are ways to get around it, but in this case I am more worried
     * about readability over performance.
     */
    DECLARE root_cur CURSOR FOR
      SELECT h.node
        FROM hier h
       WHERE h.parent IS NULL
    FOR READ ONLY

    /*
     * Here we are populating our "stack" with all of the root
     * nodes of the hierarchy.  We are using the cursor in order
     * to assign an increasing index into the "stack"...this could
     * be done using an identity column and a little trickery.
     */
    OPEN root_cur
    FETCH root_cur INTO @node
    WHILE (@@sqlstatus = 0)
    BEGIN
      SELECT @idx = @idx + 1
      INSERT INTO #a VALUES (@idx, @node, 0)
      FETCH root_cur INTO @node
    END
    CLOSE root_cur
    DEALLOCATE CURSOR root_cur

    /*
     * The following cursor will be employed to retrieve all of
     * the children of a given parent.
     */
    DECLARE child_cur CURSOR FOR
      SELECT h.node
        FROM hier h
       WHERE h.parent = @node
    FOR READ ONLY

    /*
     * While our stack is not empty.
     */
    WHILE (@idx > 0)
    BEGIN
      /*
       * Look at the element on the top of the stack.
       */
      SELECT @node      = node,
             @left_nbr  = left_nbr
        FROM #a
       WHERE idx = @idx

      /*
       * If the element at the top of the stack has not been assigned
       * a left number yet, then we assign it one and copy its children
       * on the stack as "nodes to be looked at".
       */
      IF (@left_nbr = 0)
      BEGIN
         /*
          * Set the left number of the current node to be @counter + @skip.
          * Note, we are doing a depth-first traversal, assigning left
          * numbers as we go.
          */
         SELECT @counter  = @counter + @skip
         UPDATE #a
            SET left_nbr  = @counter
          WHERE idx = @idx

         /*
          * Append the children of the current node to the "stack".
          */
         OPEN child_cur
         FETCH child_cur INTO @node
         WHILE (@@sqlstatus = 0)
         BEGIN
            SELECT @idx = @idx + 1
            INSERT INTO #a VALUES (@idx, @node, 0)
            FETCH child_cur INTO @node
         END
         CLOSE child_cur

      END
      ELSE
      BEGIN
         /*
          * It turns out that the current node already has a left
          * number assigned to it, so we just need to assign the
          * right number and update the node in the actual
          * hierarchy.
          */
         SELECT @counter = @counter + @skip

         UPDATE h
            SET left_nbr  = @left_nbr,
                right_nbr = @counter
          WHERE h.node    = @node

         /*
          * "Pop" the current node off our "stack".
          */
         DELETE #a WHERE idx = @idx
         SELECT @idx = @idx - 1
      END
    END /* WHILE (@idx > 0) */
    DEALLOCATE CURSOR child_cur


While reading through this, you should notice that assigning the
left and right numbers to the entire hierarchy is very costly,
especially as the size of the hierarchy grows. If you put the
above code in an insert trigger on the hier table, the overhead
for inserting each node would be phenominal. However, it is possible
to reduce the overall cost of an insertion into the hierarchy.

    1. By leaving huge gaps in the left & right numbers (using the @skip
       variable), you can reduce the circumstances in which the numbers
       need to be reassigned for a given insert. Thus, as long as you can
       squeeze a new node between an existing pair of left and right
       numbers you don't need to do the re-assignment (which could affect
       all of the node in the hierarchy).
    2. By keeping an extra flag around in the hier table to indicate
       which nodes are leaf nodes (this could be maintained with a
       trigger as well), you avoid placing leaf nodes in the array and
       thus reduce the number of updates.
   
   Deletes on this table should never cause the left and right numbers
   to be re-assigned (you could even have a trigger automagically
       re-parent
   orphaned hierarchy nodes).
   



All-in-all, this algorithm is very effective as long as the structure
of the hierarchy does not change very often, and even then, as you can
see, there are ways of getting around a lot of its inefficiencies.


  __________________________________________________________________________

          Q7.8: CALLING UNIX FROM A _TRIGGER_ OR A _STORED PROCEDURE_
                                       
   
     _________________________________________________________________
   
   Periodically folks ask if it's possible to make a system command or
   call a UNIX process from a Trigger or a Stored Procedure.
   
  Guaranteed Message Processing
  
   The typical ways people have implemented this capability is:
    1. Buy Open Server and bind in your own custom stuff (calls to
       system() or custom C code) and make Sybase RPC calls to it.
    2. Have a dedicated client application running on the server box
       which regularly scans a table and executes the commands written
       into it (and tucks the results into another table which can have a
       trigger on it to gather results...). It is somewhat tricky but
       cheaper than option 1.
       
  Sybase SQL Server 10.0.2.5 and Above - syb_sendmsg()
  
   This release includes a new built-in function called syb_sendmsg().
   Using this function you can send a message up to 255 bytes in size to
   another application from the SQL Server. The arguments that need to be
   passed to syb_sendmsg() are the IP address and port number on the
   destination host, and the message to be sent. The port number
   specified can be any UDP port, excluding ports 1-1024, not already in
   use by another process. An example is:

1> select syb_sendmsg("120.10.20.5", 3456, "Hello")
2> go

   This will send the message "Hello" to port 3456 at IP address
   '120.10.20.5'. Because this built-in uses the UDP protocol to send the
   message, the SQL Server does not guarantee the receipt of the message
   by the receiving application.
   
     _Also, please note that there are no security checks with this new
     function. It is possible to send sensitive information with this
     command and Sybase strongly recommends caution when utilizing
     syb_sendmsg to send sensitive information across the network. By
     enabling this functionality, the user accepts any security problems
     which result from its use (or abuse). _
     
   
   
   To enable this feature you should run the following commands as the
   System Security Officer.
    1. Login to the SQL Server using 'isql'.
    2. Enable the syb_sendmsg() feature using sp_configure.

1> sp_configure "allow sendmsg", 1
2> go

1> sp_configure "syb_sendmsg port number",
2> go

1> reconfigure with override
2> go

    Using syb_sendmsg() with Existing Scripts
    
   Since syb_sendmsg() installs configuration parameter "allow
   sybsendmsg", existing scripts that contain the syntax

1> sp_configure allow, 1
2> go

   to enable updates to system tables should be altered to be fully
   qualified as in the following:

1> sp_configure "allow updates", 1
2> go

   If existing scripts are not altered they will fail with the following
   message:

1> sp_configure allow, 1
2> go
Configuration option is not unique.
duplicate_options
----------------------------
allow updates
allow sendmsg

(return status = 1)

    Backing Out syb_sendmsg()
    
   The syb_sendmsg() function requires the addition on two config values.
   If it becomes necessary to roll back to a previous SQL Server version
   which does not include syb_sendmsg(), please follow the instructions
   below.
    1. Edit the RUNSERVER file to point to the SWR SQL Server binary you
       wish to use.
    2. isql -Usa -P -S_server_name_ -n -iunconfig.sendmsg -o_output_file_
       
    Sample C program
    

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>

main(argc, argv)
int argc; char *argv[];
{
        struct sockaddr_in sadr;
        int portnum,sck,dummy,msglen;
        char msg[256];

        if (argc < 2) {
                printf("Usage: udpmon <udp portnum>\n");
                exit(1);
        }

        if ((portnum=atoi(argv[1])) < 1) {
                printf("Invalid udp portnum\n");
                exit(1);
        }

        if ((sck=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) {
                printf("Couldn't create socket\n");
                exit(1);
        }
        
        sadr.sin_family = AF_INET;
        sadr.sin_addr.s_addr = inet_addr("0.0.0.0");
        sadr.sin_port = portnum;
        
        if (bind(sck,&sadr,sizeof(sadr)) < 0) {
                printf("Couldn't bind requested udp port\n");
                exit(1);
        }

        for (;;)
        {
                if((msglen=recvfrom(sck,msg,sizeof(msg),0,NULL,&dummy)) < 0)
                        printf("Couldn't recvfrom() from udp port\n");
                printf("%.*s\n", msglen, msg);
        }
}

   
     _________________________________________________________________
-- 
Pablo Sanchez              | Ph # (415) 933.3812        Fax # (415) 933.2821
pablo@sgi.com              | Pg # (800) 930.5635  -or-  pablo_p@pager.sgi.com
===============================================================================
I am accountable for my actions.   http://reality.sgi.com/pablo [ /Sybase_FAQ ]
