in reply to Building SQL Query on the fly

I'm curious about your setup. You mention a GUI where a user "drags" column names from one side of the window to the other, placing them in a desired order, and then you say the result of this selection is sent to a CGI script, which would suggest that the GUI is actually a web browser.

In any case, if the user's job is simply to select from a finite set of column names and put the selected items in a chosen order, the "dynamic SQL" part seems pretty easy, because you have a constant statement frame that starts with SELECT ends with  FROM my_table [WHERE whatever...]", and has potentially two slots to be filled in by the user:

You already have the set of column names and their ordering provided as CGI parameters, so just put those together with the rest of the query statement. If that's giving you trouble, post some code to show us what kind of problem you are having.

As for the XML generation part, it looks like XML::Generator might not be the right tool for your job here, because it seems to assume that the caller wants to use function calls as nodes in the XML tree.

I think you want something that will generate XML from a hash structure (which can easily be built from the data you get after executing your dynamic select statement) -- look at XML::Simple instead. Again, if you have trouble with that, post some code and sample data, or else our help will be limited to generalities or poor examples.

Replies are listed 'Best First'.
Re^2: Building SQL Query on the fly
by hallikpapa (Scribe) on Oct 08, 2007 at 04:23 UTC
    I think I got SQL Abstract working like it should, selecting the correct columns (but I am not inserting them sorted based on lowest to highest, how would I do that?) But this is what I am doing now...
    my $sql = SQL::Abstract->new; ($group,$source,$orig,$term,$i_sig_start,$cdr_day,$call_status +,$e_rel_cause,$pdd,$asr,$call_duration,$npa,$i_tg_name,$e_tg_name,$st +ate,$gc_id,$dn,$noacpn,$i_rel_cause) = @_; if ($gc_id) { push @fields, "GC_ID"; } if ($pdd) { push @fields, "PDD"; } %where = (DAY => '2007-10-06'); $table = "TABLE_NAME"; my($stmt, @bind) = $sql->select($table, \@fields, \%where); my $sth = $dbh->prepare($stmt); unless ($sth) { $dbh->disconnect; } $sth->execute(@bind); while (my $row = $sth->fetchrow_hashref ){ >>>>>>WHAT DO I DO HERE FOR XML????>>>> }
    Ideas on how to push the column names into the XML tag elements, and the values inside the tags? As for RoR, I am digging it. I don't know very advanced functions with it, but it was pretty easy to pick up, and I like incorporating it with AJAX, making this a pretty rich application. This is what I am doing with the XML::Generator on other canned/static reports, but the stuff it returns is hard coded, and not dynamic variables.
    $sth->execute(); while ( ( $gc_id, $cdr_day, $call_status, $direction, $p +dd, $state, $noacpn, $i_cause ) = $sth->fetchrow_array ) { push @output, $xml->cdr( $xml->GC_ID($gc_id), $xml->Date($cdr_day), $xml->CallStatus($call_status), $xml->Direction($direction), $xml->State($state), $xml->PDD($pdd), $xml->NOACPN($noacpn), $xml->I_REL_CAUSE($i_cause) ); } my $cgi = CGI->new; $hdr = "Custom Report"; print $cgi->header('text/xml'); print "<dataset name=\"$hdr\">\n"; print @output; print "</dataset>";
    Maybe do a...?
    my $row = $sth->fetchrow_hasharray;
    Just need help pushing the hash into the XML format properly. But since the amount of rows will always be different, I am not sure how to do it. Thanks!
      ... I know this is totally incorrect, because this way will always leave a trailing comma.

      So just do chop $SQL; or $SQL =~ s/,$//; to get rid of the comma, before you add the next part of the statement ($SQL .= " FROM ...").

      But I think the sample code you just gave is a bit off track. Let's go back to your CGI param string (from the OP):

      ../cgi-bin/script.pl?column3=1&column2=2&column1=3
      If you get your params like this:
      my $q = new CGI; my $href = $q->Vars;
      Then you can form the string of column names like this:
      my $col_string = join( ',', sort {$$href{$a} <=> $$href{$b}} keys %$hr +ef );
      I don't think you'll ever need to worry about any extra commas with that approach, but if you want to be totally certain:
      $col_string =~ s/^,+//; # remove leading commas $col_string =~ s/,+$//; # remove trailing commas $col_string =~ s/,{2,}/,/g; # reduce adjacent commas to ","
      Or, using something more like your approach, just append column names in order, and get rid of the last comma:
      my $col_string = ''; for my $var ( sort {$$href{$a} <=> $$href{$b}} keys %$href ) { $col_string .= "$var,"; } chop $col_string; # remove final comma
      Either way, now you just finish the SQL statement:
      my $sql = "SELECT $col_string FROM my_table";
      Also, in all cases, if/when you decide (or realize) that you do need to worry about security issues, make sure to test the "$href" keys (assumed table column names) against a known (untainted) list that is based directly on the how the table was defined. Delete any hash keys from the CGI parameter string that do not exactly match a known table column.

      This sort of dynamic query construction is an area where things can go horribly wrong if you are not meticulously careful about checking your tainted input. If you don't have "-T" on the shebang line of your script (to turn on taint-checking), add it now.

      This is what I am doing with the XML::Generator, but the stuff it returns is hard coded, and not dynamic variables...

      That's why I say you should use XML::Simple (or something else besides XML::Generator) -- you need to write XML data based on a data structure, where the highest (outermost) level of the structure is a hash.

        my $col_string = ''; for my $var ( sort {$$href{$a} <=> $$href{$b}} keys %$href ) { $col_string .= "$var,"; } chop $col_string; # remove final comma

        I always view the chopping of a trailing comma in building an SQL statement as something of a Red Flag. It usually means that a join (in the Perl sense) is called for. For instance, the above snippet is better written as

        my $col_string = join( ',', sort {$href->{$a} <=> $href->{$b}} keys %$href );

        • another intruder with the mooring in the heart of the Perl

        I edited my posted you commented on using SQL::Abstract, but I like your way better with the sorting. It looks like it will work well, but since other parameters are being passed besides just column names (like order by, where clauses) I can't really push it all in which is why I was doing it one by one, but then again, that's the only way I thought of doing it. So in my edited posted, I mentioned doing
        while (my $row = $sth->fetchrow_hashref ){ <<<<SOME KIND OF XML::SIMPLE PUSH HERE???>>>>> }
        I am confused on how to make the column names the element tags, and the returned values in the hash ($row), into XML tags. I will continue to read about the XML::Simple, but all pointers are extremely appreciated.