Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Database field names in code

by disciple (Pilgrim)
on Apr 10, 2004 at 01:17 UTC ( [id://344068]=perlmeditation: print w/replies, xml ) Need Help??

I didn't want to hijack someone else's thread, so I started a new one. Over here, jonadab mentioned not hardcoding database field names in your code. I would love to hear some good ideas to avoid scattering database field names all over my code.

Update Apparently I misunderstood jonadab's point. I in fact never rely upon the default order of fields, in any kind of SQL statement. But my question still stands, so read on. :-)

I have been building db apps for 5 years now and have not found a decent way to follow this advice. I have done a lot of web apps using languages like ASP with vbscript, vb.net, and C#, and a few using Perl. Every single application I have seen hardcodes the field names in the source code.

It gets worse when you use stored procedures in database servers that support them. Add on to that a data abstraction layer. Now you have the stored procedures to change, the data abstraction classes, and the code that uses those data abstraction classes. Any ideas?

Replies are listed 'Best First'.
Re: Database field names in code
by Zaxo (Archbishop) on Apr 10, 2004 at 01:48 UTC

    I don't think he meant that you can't know the names anywhere. He appears to be referring to hardcoding the table structure in queries.

    Anyhow, you should make a set of constant "accessors" (as if this were an object) which return the field and table names, column numbers, etc. That way, when tables change or spawn, you have only one place to change the data.

    After Compline,
    Zaxo

      I don't think he meant that you can't know the names anywhere. He appears to be referring to hardcoding the table structure in queries.

      Yes, that's closer to what I meant. In context I was talking about the fact that you're going to add fields later, even if you currently don't think so.

      I consider it okay for any given bit of code to know the names of the fields it is actively working with. As disciple points out, this is nearly unavoidable. The trouble comes when you write a piece of code that relies on knowing all of the fields. Don't do that, because you'll have to change every single query whenever you add fields.

      If I'd known about Class::DBI when I first ran into this problem, that's what I'd have used. I didn't, so I rolled my own set of routines that do essentially the same thing but with a function interface rather than an object interface. I call it db.pl, and I have a number of CGI database thingies at work that use it. The interface looks like this:

      $result = db::addrecord(tablename, $record_as_hashref); $id = db::added_record_id; @change = @{db::updaterecord(tablename, $record_as_hashref)}; %record = %{db::getrecord(tablename, id)}; @record = db::getrecord(tablename); # Not for huge tables. @record = db::findrecord(tablename, fieldname, exact_value); @record = @{db::searchrecord(tablename, fieldname, value_substring)};

      This is *MUCH* better than my previous method of embedding SQL all over the place. (I can still use a db handle directly for custom SQL in the few rare cases where the above functions aren't enough, such as when the code needs to create tables on the fly.) The functions are general, and in all cases the records being passed around are hashrefs, which works out rather nicely. If I had to do over again, though, I would use Class::DBI instead, mainly to avoid re-inventing the wheel but also because it's often easier to install a module from the CPAN than it is to pull a copy of my roll-my-own stuff from another system where it's installed. I'm pretty sure Class::DBI does everything my self-rolled stuff does (though not in quite the same way).

      The real key, though, is that it doesn't hardcode the list of (or even the number of) fields. Here's a sample excerpt from addrecord:

      my @clauses = map { "$_=?" } sort keys %r; my @values = map { $r{$_} } sort keys %r; my $q = $db->prepare("INSERT INTO $table SET ". (join ", ", @clauses +)); my $result = $q->execute(@values);

      This way, when new fields are added (as long as they're not set NOT NULL without a default value), I don't have to hunt through the existing code for places that have to be updated; the only parts that have to be updated are the parts that actually need to use the new fields. I believe Class::DBI also provides this benefit.


      ;$;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$;[-1]->();print
Re: Database field names in code
by cchampion (Curate) on Apr 10, 2004 at 06:34 UTC

    Something related to your question was debated in the Monastery a few years ago, and it ended up with a couple of articles:

    More recently, DBI Recipes addressed the same issue, offering some good advice.

    update Thanks to Tilly for clarifying. I had the sequence of facts upside down.

      The actual history a few years ago is that princepawn had an article published, it drew some criticism, and then chromatic has his reply published.

      For some of the discussion that appeared between those two events, see DBI vs DBIx::Recordset vs princepawn vs chromatic. For reasons that are clear in my contribution to that discussion, I wouldn't recommend the original article as a useful learning resource.

Re: Database field names in code
by stvn (Monsignor) on Apr 10, 2004 at 03:06 UTC

    Ideally your data abstraction classes should be isolating you from your database implementation. Meaning, you should not have to change any code that uses them if you change the database structure, assuming of course your change isnt so major as to require a rethinking/recoding of said data abstraction classes. Of course this is idealistic and in reality it may not be this black and white.

    Personally the way I avoid database structural changes rippling throughout my code, is by using a set of Object Relational Mapping classes. Usually comprised of an Entity class and matching Data Access class, although sometimes they are combined into a single class. I then make sure from that point on all my other code only uses the Entity classes interface. As long as the metaphor my entity is based on is solid, then I find any underlying database changes have minimal effect. More often than not, additions are the only noticable changes.

    -stvn
Re: Database field names in code
by gsiems (Deacon) on Apr 10, 2004 at 04:14 UTC

    I would really hate to be the one maintaining code like that.

    What happens if someone adds a column, or rearranges the columns? I know some folks that will change their column order whenever they add a column to a table so that their auditing columns are always the last columns in the table. Guaranteed breakage.

    I would think that:

    my $q=$dbh->query("INSERT INTO foo (col1, col2, col3, col4, col5) V +ALUES (?,?,?,?,?); $q->execute($bar, $baz, $qx, $qux, $quux);

    would be more robust as it doesn't make any assumptions about either the column order or the number of columns (assuming any columns not listed are nullable). Also, wouldn't naming the columns make the code more readable and maintainable (by virtue of being able search on the column names)?

      Another thing I like to avoid is the dreaded and error-prone counting of placeholders. To accomplish this goal I build an array of fields I want, and use it multiple times. Example:

      my @keys = qw(time_value hour_24 minute second hour_12 am_pm); my %time = ( time_value => "2:30:12pm", hour_24 => 14, hour_12 => 2, minute => 30, second => 12, am_pm => "pm" ); my $sql = "INSERT INTO" . " time (" . join(",", @keys) . ")" . " VALUES (" . join(",", ('?') x @keys) . ")"; my $sth = $dbh->prepare($sql); $sth->execute(@time{@keys});

      Note I keep the values I'm executing in a hash with keys matching the field names. This allows me to use a hash slice so even the execute() call is guaranteed to match.

Re: Database field names in code
by Jenda (Abbot) on Apr 10, 2004 at 17:04 UTC

    1. It doesn't need to get worse because of stored procedures. It may get better. Depending on the nature of the change you may end up having to change just the procedures without having to leak the change to the upper levels. That is if your stored procedures are more than just SELECT All, Columns FROM TableName WHERE ID = @ID.

    2. Data Abstraction layer is one of the best places to use code generation. The functions/methods look very similar so there is no need to maintain them by hand.

    Jenda
    Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
       -- Rick Osborne

    Edit by castaway: Closed small tag in signature

Re: Database field names in code
by dragonchild (Archbishop) on Apr 10, 2004 at 14:56 UTC
    I have a different take on it. Classes and database tables aren't a 1-1 mapping. I have my own database accessor classes, which have a HAS-A relationship with DBI. They provide business-level functions to the other classes, with the ability to drop into SQL, if desired.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

Re: Database field names in code
by jeffa (Bishop) on Apr 10, 2004 at 19:50 UTC

    For me, it's generally not a matter of hard coding the field names, but limiting the amount i do. When retrieving data for display, i strive to only "hard code" my database field names in 2 places ... the SQL query and the template (usually HTML). I discuss more of that in a moment, but for now, know that DBI allows you to deal with the query results in a variety of useful ways. You really should become familar with the documentation and then have a look into Class::DBI. For example, try this code on one of your SELECT queries:

    use DBI; use Data::Dumper; my $dbh = DBI->connect( ... ); # Example 1: List of Lists print Dumper $dbh->selectall_arrayref('select id,name from status'); # Example 2: List of Hashes print Dumper $dbh->selectall_arrayref( 'select id,name from status', { Slice=>{} }, );
    And pay close attention to the results. The first is really just a two dimensional table, like a spreadsheet without headers. Here is what my results yield:
    $VAR1 = [
              [
                '0',
                'enrolled'
              ],
              [
                '1',
                'dropped'
              ]
            ];
    
    With this, i can now build a useful hash with which i can lookup values if need be ... but notice that i only specify the field names in the SQL query (and that i reversed them from the last example):
    my $rslt = $dbh->selectall_arrayref('select name,id from status'); my %hash = map @$_, @$rslt; print Dumper \%hash;

    But what about the second example, the one with the strange and cryptic { Slice => {} }? For a web developer who has to build reports and such, that is one of the best things since sliced bread. With this, i need only specify which fields i want in the SQL query, and then once more in the template. Here is an example that uses an easy to install CPAN module, HTML::Template. Just run the code first, you can ask more questions later. :)

    use DBI; use HTML::Template; my $dbh = DBI->connect( qw(DBI:vendor:database:host user pass), {RaiseError=>1}, ); my $things = $dbh->selectall_arrayref(' SELECT id,name FROM status ',{Slice=>{}}); # optionally do more things to some of the fields # yes, we do have to say their names again $_->{id} = sprintf("%03d",$_->{id}) for @$things; my $tmpl = HTML::Template->new(filehandle => \*DATA); $tmpl->param(things => $things); print $tmpl->output; __DATA__ <tmpl_loop things> <tmpl_var id>: <tmpl_var name> </tmpl_loop>

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://344068]
Front-paged by diotalevi
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (1)
As of 2024-04-25 01:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found