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

I'm preparing a sub which generates SQL INSERT statements. The parameters passed to the sub are a scalar, and a hash, and looks like this :
sub update_table{ my $l_tablename = shift; my %l_fields = shift; my $l_fieldnames; my $l_fieldvalues; my $l_prepare; my $l_fieldnames = join ",", keys %l_fields; my $l_fieldvalues = join ",", values %l_fields; $l_prepare = "INSERT $l_tablename (" . $l_fieldnames . ")\n VALUES + (" . $l_fieldvalues . ")"; }
My concern is over the keys and values. I know it's unreliable to expect a hash's contents to come out in a consistent order after manipulating it; I want to know if keys and values will ever present the same problem. That is to say, given the following
%x = (a =>1, b =>2, c =>3, d =>4);
if keys %x yields (b,d,c,a), I expect values %x to yield (2,4,3,1). Some testing confirms this, but I'd like to know if there are any special exceptions, or if I've just been lucky in my experiences.

Replies are listed 'Best First'.
Re: How hashes present keys and values.
by merlyn (Sage) on Dec 13, 2000 at 19:01 UTC
    To confirm what everyone else has said: yes, keys and values are promised to be consistent as long as you alter nothing about the hash

    But no-one pointed out that if you're using DBI, you should not be constructing SQL with arbitrary values like that. You should be using placeholders. What if someone hands you a value with don't in it?

    -- Randal L. Schwartz, Perl hacker

      Excellent point, o-sensei merlyn. Error/ value checking will occur at a different place in the script -- no unsafe value should reach this sub.
      update
      for those of you wondering, an apostrophe will stun most SQL parsers. The short answer is something like
      s/'/''/g
        Or, a short answer that works is something more like:
        $quoted = $dbh->quote($raw);

        But it's much better to use the placeholders, as they can be trained in the SQL datatypes so as to quote correctly for those DBDs that have specific needs.

        -- Randal L. Schwartz, Perl hacker

Re: How hashes present keys and values.
by mwp (Hermit) on Dec 13, 2000 at 18:31 UTC
    Short answer: I'm almost positive that keys and values will always return hash entries in the same order, until the hash is modified. If you're really concerned about it, you can do something like this:
    my($l_tablename, %l_data) = @_; my(@fields, @values); push(@fields, $_) && push(@values, $1_data{$_}) for(keys %1_data); my($1_prepare); { local $" = ','; # eliminate join statements $1_prepare = qq( INSERT INTO $1_tablename (@fields) VALUES (@values) ); }
    Long answer:
    1. The first literal character in a variable can't be a numerical value. So "1_" will have to go! =)
    2. In your original code, you assign a hash to a shift statement. This won't work because you'll only get the first value from the list you pass. The assignment arrow operator => acts the same as a comma in a list, and lists flatten. So something like this will happen:
      @list = ('arg1', ('field1' => 'value1', 'field2' => 'value2')); # above same as: @list = ('arg1', 'field1', 'value1', 'field2', 'value2'); $table = shift(@list); # 'arg1' %fields = shift(@list); # 'field1'
      The last statement, in order to put all the remaining values from @list into %fields should read: %fields = @list;
    3. Writing a function to prepare generic insert statements is generally a bad idea. This is just asking for people to call your function with invalid table names, field names, and data types. If the database itself changes, it becomes more difficult to fix the code because you have to track down every place where this subroutine is being called.

    There's probably a better way to do what you're trying to do. Writing an insert subroutine for each table in the database that allows new records might be a better solution. If you are really ambitious, you can even write an OO wrapper for each table, with select, insert, update, and delete methods. Then you can write things like:

    my $person = new MyDB::Person(4); $person->update(name => 'John B.');
    instead of     &update('person', { pk => 4, name => 'John B.' });

    ...or whatever. Just a few thoughts for you to mull over. {g}

(kudra) Re: How hashes present keys and values.
by kudra (Vicar) on Dec 13, 2000 at 18:35 UTC
    Your keys and values are output based upon internal hash order, which will be the same each time (unless you modify the hash, of course).
Re: How hashes present keys and values.
by Blue (Hermit) on Dec 13, 2000 at 18:36 UTC
    boo,
    I'm not an internals wiz to give you the reasoning for this (and there are people here who could), but the basic goes like this - order is not preserved putting items into the hash table for lookups, but both keys and values goes through the hash table in the same order.

    Programming Perl from Larry, Tom, and our very own Randal published by O'Reilly under the entry for values (pg. 239) mentions "...it is the same order as either the keys or each function would produce...".

    Hope this helps.
    =Blue
    ...you might be eaten by a grue...

Re: How hashes present keys and values.
by chipmunk (Parson) on Dec 13, 2000 at 19:26 UTC
    If you're unsure of the behavior of keys and values, the documentation is easily accessible.

    I note that the second line of your sub is: my %l_fields = shift; Is that a transcription error? As shown, it will create a hash with one element. You don't show how the subroutine is called, but that line should be either: my %l_fields = @_; or my %l_fields = %{ +shift };

      Is that a transcription error? As shown, it will create a hash with one element.
      The line is actually my ($l_tablename,%l_fields)  = @_;. I don't know what came over me to write that. I blame my general lack of caffeination.
Re: How hashes present keys and values.
by c-era (Curate) on Dec 13, 2000 at 18:31 UTC
    I'm sure merlyn will find some exception, but I've done that in some of my scripts and not had a problem. Recently I use a for loop to construct a string or 2 arrays.
Re: How hashes present keys and values.
by repson (Chaplain) on Dec 13, 2000 at 18:37 UTC
    The safest way is probably something like:
    # values %foo map {$foo{$_}} keys %foo
    That would make me more certain and probably the same for someone maintaining the code later (if that is a concern).
Re: How hashes present keys and values.
by I0 (Priest) on Dec 14, 2000 at 06:41 UTC
    keys values, and each all return the same order, as confirmed by perldoc
Re: How hashes present keys and values.
by BatGnat (Scribe) on Dec 14, 2000 at 02:16 UTC
    Why not try this:
    sub update_table{ my $l_tablename = shift; my %l_fields = shift; my $l_fieldnames; my $l_fieldvalues; map { $l_fieldnames .= $_; $l_fieldvalues .= $l_fields{$_}; } keys %l_fields; my $l_prepare = "INSERT $l_tablename (" . $l_fieldnames . ")\n VAL +UES(" . $l_fieldvalues . ")"; return $l_prepare; }

    BatGnat