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

Howdy all,

So, I've got this script (below) that reads a ZSQL XML file (also below) in order to create a data structure in a database. Everything work's great. I thought. Turns out, my indices don't get created in any certain order. I've included a table that has three extra non-key indices, all three with the same four columns, in different orders (don't ask me why, it's not my structure, I just have to implement it).

Anyway, take a look at my code (with all the DBI code pulled, it just prints the statements instead of execute()/do()'ing them...)

#!perl -w use strict; no utf8; use Encode; use Unicode::String; use XML::Simple; <readmore> my %primKeys; my $schema = XMLin("./TEST_Baseline_0.99.xml", forcearray => [ qw(zsql:database zsql:table zsql:col +umn zsql:index zsql:field) ], keyattr => { "zsql:database" => 'name', "zsql:table" => 'name', "zsql:column" => 'name', "zsql:index" => 'name', "zsql:field" => 'column'} ); foreach my $db_table (sort keys %{$schema->{"zsql:table"}} ) { if ($db_table ne '') { #print "\t\t$table\n"; my $create_statement = "create table $db_table ("; my $primarykey= ", PRIMARY KEY ("; my $counter = 1; my $countto = keys %{$schema->{"zsql:table"}->{$db_table}->{'zsql: +column'}}; #print "Counter = $counter, count-to = $countto\n"; foreach my $tableColumn (sort keys %{$schema->{"zsql:table"}->{$db +_table}->{'zsql:column'}} ) { my $col_name = remCol($tableColumn); my $col_type = $schema->{'zsql:table'}->{$db_table}->{'zsql:colu +mn'}->{$tableColumn}->{type}; $create_statement .= "$col_name $col_type"; if (defined $schema->{"zsql:table"}->{$db_table}->{'zsql:column' +}->{$tableColumn}->{notnull} ) { $create_statement .= " NOT NULL"; } else { $create_statement .= " NULL"; } $counter++; if ( $counter <= $countto ) { $create_statement .= ", "; } if (defined $schema->{"zsql:table"}->{$db_table}->{'zsql:column' +}->{$tableColumn}->{primarykey} ) { push (@{$primKeys{$db_table}{key}}, $col_name); if (length($primarykey) > 15) { $primarykey .= ", $col_name"; } else { $primarykey .= "$col_name"; } } } if (length($primarykey) > 15) { $primarykey .= " )"; $create_statement .= $primarykey; } $create_statement .= ")"; print "Creating table $db_table with statement\n\t$create_statemen +t\n\n"; foreach my $db_index (sort keys %{$schema->{'zsql:table'}{$db_tabl +e}->{'zsql:index'}} ){ if (exists $schema->{"zsql:table"}->{$db_table}->{'zsql:index'}- +>{$db_index}->{'zsql:field'}) { my @thisIndex; my $sqlStatemnt = "CREATE INDEX $db_index on $db_table ("; my $fld_cnt = scalar(keys %{$schema->{"zsql:table"}->{$db_tabl +e}->{'zsql:index'}->{$db_index}->{'zsql:field'}}); my $fld_add = 1; foreach my $index_col (keys %{$schema->{"zsql:table"}->{$db_ta +ble}->{'zsql:index'}->{$db_index}->{'zsql:field'}} ) { my $col_name = remCol($index_col); push (@thisIndex, $col_name); $sqlStatemnt .= "$col_name"; if ($fld_add < $fld_cnt) { $sqlStatemnt .= ", "; } else { $sqlStatemnt .= ") "; } $fld_add++; } if (@thisIndex ne @{$primKeys{$db_table}{key}}) { print "Adding index $db_index to table $db_table with \n"; print "\t$sqlStatemnt\n\n"; } } } } } sub remCol { my ($retval) = @_; $retval =~ s/Col\d+\-\-//; return $retval; }

Here is a simple XML'ified table...

<?xml version="1.0" encoding="ISO-8859-1"?> <?xml-stylesheet type="text/xsl" href="../zsql_common.xsl"?> <!DOCTYPE zsql:database SYSTEM "zsql.dtd"> <zsql:database xmlns:zsql="zenark.com/zsql" name="TEST_Baseline_0.99" desc="A simple test zsql schema" > <zsql:table name="TEST"> <zsql:documentation> read/write status: read </zsql:documentation> <zsql:column name="Col0001--TEST_ID" type="INT" primarykey="Y" notn +ull="Y"/> <zsql:column name="Col0002--Name" type="VARCHAR (255)"/> <zsql:column name="Col0003--ItemID" type="VARCHAR (60)"/> <zsql:column name="Col0004--REF_ID" type="VARCHAR (32)"/> <zsql:column name="Col0005--Entry_Point" type="CHAR (1)"/> <zsql:column name="Col0006--Entry_Point_QA" type="CHAR (1)"/> <zsql:column name="Col0007--Action" type="CHAR (1)"/> <zsql:column name="Col0008--Agent" type="CHAR (1)"/> <zsql:index name="TEST_KEY" unique="Y"> <zsql:field column="Col0001--TEST_ID"/> </zsql:index> <zsql:index name="TEST_2ND_KEY" unique="N"> <zsql:field column="Col0004--REF_ID"/> <zsql:field column="Col0001--TEST_ID"/> <zsql:field column="Col0003--ItemID"/> <zsql:field column="Col0005--Entry_Point"/> </zsql:index> <zsql:index name="TEST_4TH_KEY" unique="N"> <zsql:field column="Col0003--ItemID"/> <zsql:field column="Col0004--REF_ID"/> <zsql:field column="Col0001--TEST_ID"/> <zsql:field column="Col0005--Entry_Point"/> </zsql:index> <zsql:index name="TEST_3RD_KEY" unique="N"> <zsql:field column="Col0001--TEST_ID"/> <zsql:field column="Col0004--REF_ID"/> <zsql:field column="Col0003--ItemID"/> <zsql:field column="Col0005--Entry_Point"/> </zsql:index> </zsql:table> </zsql:database>

Here is the output I'm getting, with all the columns in a somewhat randomized order (not the order that is in the XML)...

Creating table TEST with statement create table TEST (TEST_ID INT NOT NULL, Name VARCHAR (255) NU +LL, ItemID VARCHAR (60) NULL, REF_ID VARCHAR (32) NULL, Entry_Point CHAR (1) NULL, Entry_Point_QA CHAR (1) NULL, Action +CHAR (1) NULL, Agent CHAR (1) NULL, PRIMARY KEY (T EST_ID )) Adding index TEST_2ND_KEY to table TEST with CREATE INDEX TEST_2ND_KEY on TEST (Entry_Point, ItemID, TEST_I +D, REF_ID) Adding index TEST_3RD_KEY to table TEST with CREATE INDEX TEST_3RD_KEY on TEST (Entry_Point, ItemID, TEST_I +D, REF_ID) Adding index TEST_4TH_KEY to table TEST with CREATE INDEX TEST_4TH_KEY on TEST (Entry_Point, ItemID, TEST_I +D, REF_ID)

Any pointers appreciated...
Thanks!
amonotod

Replies are listed 'Best First'.
Re: XML::Simple and default sorting...
by diotalevi (Canon) on Aug 10, 2004 at 21:30 UTC

    keys() and values() return the list of keys/values from a hash in no particular order. This data is unsorted. If you want to sort it then you'll need to do that yourself. The only exception to this is that keys() and values() return their elements in the same order so if you say @keys = keys %h; @values = values %h you can find the matching element in the other array to get at the key / value.

    20040810 Edit by ysth: reparented from 381779; changed title from: Re^2: XML::Lite --sort order in a keys() statement...

Re: XML::Simple and default sorting...
by ikegami (Patriarch) on Aug 10, 2004 at 21:58 UTC
    After looking at the docs, I think you're shooting yourself in the foot by using keyattr. It's converting what would have been an array into a hash. Since elements of a hash are not ordered, using keyattr is causing you to lose the order of your XML records.
      Looks as though you are correct. I took out the zsql:field part of the XML parsing, and am now getting the fields in order. Just need to fix the SQL statement building now, and I'll be good to go. Will post the final code tomorrow.

      Thanks! I appreciate the time you took to look at this...
      amonotod

      I wanted to take a moment to say thanks, so, "Thanks!". I appreciate all the help that is given on this site...

      #!perl -w use strict; no utf8; use Encode; use Unicode::String; use XML::Simple; my %primKeys; my $schema = XMLin("./TEST_Baseline_0.99.xml", forcearray => [ qw(zsql:database zsql:table zsql:col +umn zsql:index) ], keyattr => { "zsql:database" => 'name', "zsql:table" => 'name', "zsql:column" => 'name', "zsql:index" => 'name'} ); foreach my $db_table (sort keys %{$schema->{"zsql:table"}} ) { if ($db_table ne '') { #print "\t\t$table\n"; my $create_statement = "create table $db_table ("; my $primarykey= ", PRIMARY KEY ("; my $counter = 1; my $countto = keys %{$schema->{"zsql:table"}->{$db_table}->{'zsql: +column'}}; #print "Counter = $counter, count-to = $countto\n"; foreach my $tableColumn (sort keys %{$schema->{"zsql:table"}->{$db +_table}->{'zsql:column'}} ) { my $col_name = remCol($tableColumn); my $col_type = $schema->{'zsql:table'}->{$db_table}->{'zsql:colu +mn'}->{$tableColumn}->{type}; $create_statement .= "$col_name $col_type"; if (defined $schema->{"zsql:table"}->{$db_table}->{'zsql:column' +}->{$tableColumn}->{notnull} ) { $create_statement .= " NOT NULL"; } else { $create_statement .= " NULL"; } $counter++; if ( $counter <= $countto ) { $create_statement .= ", "; } if (defined $schema->{"zsql:table"}->{$db_table}->{'zsql:column' +}->{$tableColumn}->{primarykey} ) { push (@{$primKeys{$db_table}{key}}, $col_name); if (length($primarykey) > 15) { $primarykey .= ", $col_name"; } else { $primarykey .= "$col_name"; } } } if (length($primarykey) > 15) { $primarykey .= " )"; $create_statement .= $primarykey; } $create_statement .= ")"; print "Creating table $db_table with statement\n\t$create_statemen +t\n\n"; foreach my $db_index (sort keys %{$schema->{'zsql:table'}{$db_tabl +e}->{'zsql:index'}} ){ if (exists $schema->{"zsql:table"}->{$db_table}->{'zsql:index'}- +>{$db_index}->{'zsql:field'}) { #print $schema->{"zsql:table"}->{$db_table}->{'zsql:index'}->{ +$db_index}->{'zsql:field'} ."\n"; my @thisIndex; my $fld_add = 1; my $sqlStatemnt = "CREATE INDEX $db_index on $db_table ("; my (@columns, $fld_cnt); if (ref($schema->{"zsql:table"}->{$db_table}->{'zsql:index'}-> +{$db_index}->{'zsql:field'}) eq "ARRAY") { foreach my $field (@{$schema->{"zsql:table"}->{$db_table}->{ +'zsql:index'}->{$db_index}->{'zsql:field'}}) { foreach (keys(%{$field}) ) { push @columns, ${$field}{$_}; + } } } elsif (ref($schema->{"zsql:table"}->{$db_table}->{'zsql:inde +x'}->{$db_index}->{'zsql:field'}) eq "HASH") { foreach (keys(%{$schema->{"zsql:table"}->{$db_table}->{'zsql +:index'}->{$db_index}->{'zsql:field'}})) { push @columns, ${$schema->{'zsql:table'}->{$db_table}->{'z +sql:index'}->{$db_index}->{'zsql:field'}}{$_}; } } $fld_cnt = scalar(@columns); if ($fld_cnt) { foreach my $index_col (@columns ) { my $col_name = remCol($index_col); push (@thisIndex, $col_name); $sqlStatemnt .= "$col_name"; if ($fld_add < $fld_cnt) { $sqlStatemnt .= ", "; } else { $sqlStatemnt .= ") "; } $fld_add++; } if (@thisIndex ne @{$primKeys{$db_table}{key}}) { print "Adding index $db_index to table $db_table with \n"; print "\t$sqlStatemnt\n\n"; } } } } } } sub remCol { my ($retval) = @_; $retval =~ s/Col\d+\-\-//; return $retval; }
      Again, thanks,
      amonotod
Re: XML::Simple and default sorting...
by amonotod (Acolyte) on Aug 10, 2004 at 21:44 UTC
    According to some advice I recieved on my other (poorly written) request for help, I've changed one line of my script, from:
    foreach my $index_col (keys %{$schema->{"zsql:table"}->{$db_ta +ble}->{'zsql:index'}->{$db_index}->{'zsql:field'}} ) {
    to the following two lines:
    my @columns = keys(%{$schema->{"zsql:table"}->{$db_table}->{'z +sql:index'}->{$db_index}->{'zsql:field'}}); foreach my $index_col (@columns ) {
    But, all to no avail. My output hasn't changed:
    Creating table TEST with statement create table TEST (TEST_ID INT NOT NULL, Name VARCHAR (255) NU +LL, ItemID VARCHAR (60) NULL, REF_ID VARCHAR (32) NULL, Entry_Point CHAR (1) NULL, Entry_Point_QA CHAR (1) NULL, Action +CHAR (1) NULL, Agent CHAR (1) NULL, PRIMARY KEY (T EST_ID )) Adding index TEST_2ND_KEY to table TEST with CREATE INDEX TEST_2ND_KEY on TEST (Entry_Point, ItemID, TEST_I +D, REF_ID) Adding index TEST_3RD_KEY to table TEST with CREATE INDEX TEST_3RD_KEY on TEST (Entry_Point, ItemID, TEST_I +D, REF_ID) Adding index TEST_4TH_KEY to table TEST with CREATE INDEX TEST_4TH_KEY on TEST (Entry_Point, ItemID, TEST_I +D, REF_ID)
    Thanks,
    amonotod