cjacksonjr has asked for the wisdom of the Perl Monks concerning the following question:

My first Perl program. I'm reading from a Db which contains the alarms for different power supplies (PS). How can I search Db for the alarm start (Warning) and stop (OK NOW) times and combine them into a table ordered by supply?
#! /usr/local/bin/perl-w # A perl program to do read the notifAlarmTable use FileHandle; use IPC::Open2; use Symbol; # use Tk; use Sybase::CTlib; use Time::Local; use strict; my $dbAlarm=new Sybase::CTlib 'harmless','harmless','OPSYB1',''; $dbAlarm->ct_sql("use TomTest"); my $sql = "SELECT * from notifAlarmEntry ORDER BY datetime" ; my(@records) = $dbAlarm->ct_sql($sql); my($record, $StartWarn, $StopOK); foreach $record (@records) { #build history record 4 each entry in notifAlarmEntry #Fields:Sequence,UnixTime,DateTime,Source,Process,Category,Topic,Messa +ge #this is were I will look for WARNING and OK NOW MESSAGES #to put in Table #Category contains either WARNING or OK NOW #Topic contains the PS Name print "$record->[0], $record->[1], $record->[2], $record->[3], $record +->[4], $record->[5], $record->[6], $record->[7]\n" }

2002-06-27 Edit by Corion : Changed title

  • Comment on Searching (sybase) Database and SQL help (was: Neophyte needs help.)
  • Download Code

Replies are listed 'Best First'.
Re: Searching (sybase) Database and SQL help (was: Neophyte needs help.)
by Aristotle (Chancellor) on Jun 27, 2002 at 17:32 UTC
    Why do it in Perl when SQL was made for that job? my $sql = qq(SELECT * from notifAlarmEntry ORDER BY datetime WHERE Message = 'WARNING' OR Message = 'OK NOW'); If your message contains more than just WARNING or OK NOW you may want to use Message LIKE 'WARNING%' OR Message LIKE 'OK NOW%'.

    Makeshifts last the longest.

Re: Searching (sybase) Database and SQL help (was: Neophyte needs help.)
by BlueBlazerRegular (Friar) on Jun 27, 2002 at 17:40 UTC

    To get this right, I'd normally need some more information on how the database tables are laid out. But for now, I'll just guess at the table layout and work from there. This solution assumes the following:

    1) The table has all the past alarms ('warning' and 'ok now' messages). For example:

          Topic      Category    DateTime
          Process1   WARNING     06/25/2002 12:32:14.001
          Process2   WARNING     06/25/2002 13:45:12.020
          Process1   OK NOW      06/25/2002 13:56:12.050
          Process3   WARNING     06/25/2002 14:10:01.123
          Process2   OK NOW      06/25/2002 14:15:10.005
          Process1   WARNING     06/25/2002 14:20:15.001
          Process3   OK NOW      06/25/2002 14:32:10.005
          Process2   WARNING     06/25/2002 14:46:45.001
    

    2) You only want to see the latest alarm(s) for each process.

    If the above assumptions are correct, then the following should get you close. The bulk of the effort is in the SQL statement...

    #! /usr/local/bin/perl-w # A perl program to do read the notifAlarmTable use FileHandle; use IPC::Open2; use Symbol; # use Tk; use Sybase::CTlib; use Time::Local; use strict; my $dbAlarm=new Sybase::CTlib 'harmless','harmless','OPSYB1',''; $dbAlarm->ct_sql("use TomTest"); # # WARNING! UNTESTED CODE FOLLOWS!!! # # # Build the SQL statement. This statement will pull together # a list of all Topics, with their most current alarm start # ('WARNING') and alarm stop ('OK NOW'). # # The parsing of the SQL statement is left as an exercise for the # reader... # my $sql = "select q1.Topic, q2.DateTime, q3.DateTime from ( select Topic from notifAlarmEntry group by Topic ) q1 ( select Topic, Category, max(DateTime) from notifAlarmEntry where Category = 'WARNING' group by Topic, Category ) q2 ( select Topic, Category, max(DateTime) from notifAlarmEntry where Category = 'OK NOW' group by Topic, Category ) q3 where q1.Topic *= q2.Topic and q1.Topic *= q3.Topic order by nAE.Topic"; # # get an array containing all the records returned from # the SQL statement # my( @records ) = $dbAlarm->ct_sql( $sql ); # # look thru each retrieved record # foreach my $record ( @records ) { # # Put all the fields from the array entry into their own fields. # # Not really needed, but it's easier to understand what you # are doing if things have meaningful names. # # Note: I'm handling each field individually, but if you know # what the field separator is, then you can do the # following: # # my( $topic, # $alarm_start, # $alarm_stop ) = split( /X/o, $record ); # # (where X is the field separator). Since I don't know # Sybase::CTlib all that well (ok, at all), I just did the # simple thing. # my $topic = $record->[0]; my $alarm_start = $record->[1]; my $alarm_stop = $record->[2]; # if the 'ok now' date is from before the 'warning' date, # reset the 'ok now' date (since it isn't the message # associated with this warning) if ( $alarm_stop < $alarm_start ) { $alarm_stop = ""; } print "$topic $alarm_start $alarm_stop\n"; }

    This should get you the following results (using the sample data from above):

          Process1  06/25/2002 14:20:15.001
          Process2  06/25/2002 14:46:45.001
          Process3  06/25/2002 14:10:01.123  06/25/2002 14:32:10.005
    

    Of course, if the data is in a different layout, then this solution won't necessarily work.

    And while we're here - a few comments about your code. First, congratulations on using '-w' and 'use strict' - they've saved my butt many times. But try not to use 'select *' in your SQL statements. Only pull the fields you need - you save memory that way and by explicitly requesting the fields you want you save yourself some heartbreak later on if someone adds columns to the table. And one final thing - indent your code. It's hard to tell where the 'foreach' statment ends when everything if pushed over to the left side.

    Pat

      Neat.

      Unfortunately that SQL statement won't work with Sybase - specifically the

      select ... from (select ...) alias
      syntax is not supported.

      Usually the same thing can be achieved with temporary tables (which is what the above will do under the covers anyway), or with some interesting GROUP BY and HAVING clauses.

      Michael

        Whoops. I'm currently using SQL Server which does allow these types of things and so I thought|assumed that Sybase did too.

        A lesson to all you young Monks out there - this is what happens when you don't test your code (or look in the book to make sure it'll work).

        Going from Sybase to Oracle and now to SQL Server has left me confused on who allows what. I guess I could stay with just the 'core' bits, but that takes all the fun out of things.

        Pat

Re: Searching (sybase) Database and SQL help (was: Neophyte needs help.)
by mpeppler (Vicar) on Jun 28, 2002 at 15:08 UTC
    I'm not sure I understand exactly what you are trying to do.

    If you just want to query the table to get each WARNING/OK NOW pair then you can do something like this:

    select topic, category, date from alarms order by topic, date
    and you could process this in perl to merge matching rows:
    my $data = $dbh->ct_sql($query); my $previous_row; foreach (@$data) { if($previous_row && $_->[0] eq $previous_row->[0]) { print "$previous_row->[0] $previous_row->[1] $previous_row->[2 +] $r_->[1] $r_->[2]\n"; $previous_row = undef; } elsif($previous_row) { # There is no matching OK NOW row for this entry print "$previous_row->[0] $previous_row->[1] $previous\n"; } else { $previous_row = $_; } } # print orphan, if there is one if($previous_row) { print "$previous_row->[0] $previous_row->[1] $previous\n"; }
    Not very pretty, but it should merge the two rows. Note that this will print all the rows - not just the latest instance for each topic.

    If you want the latest entry for each topic, you can do something like this:

    select id = max(id), topic, category, date = max(date) into #tmp from alarms group by topic, category select t1.topic, t1.category, t1.date, t2.category, t2.date from #tmp t1, #tmp t2 where t1.category = "WARNING" and t2.category = "OK NOW" and t1.topic = t2.topic and t1.date < t2.date UNION select t1.topic, t1.category, t1.date, NULL, NULL from #tmp t1, #tmp t2 where t1.category = "WARNING" and t2.category = "OK NOW" and t1.topic = t2.topic and t1.date > t2.date
    This will give you the latest row for each topic/category pair, with NULL values for the second category/date values if the last entry in the table is a WARNING.

    There are probably other ways that this can be achieved at the SQL level...

    Michael