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

I have a database table with 2 columns: name and addresses. The name column is the users name and the addresses column holds names and phone number entries ( ex.
--------------------------------------------------------- | username | addresses | --------------------------------------------------------- | | | despair | Des Despair <despair@blah.com>, B <B@B.com>| ---------------------------------------------------------
). Basically I have my script append new name and addresses pairs to this "addresses field". I need guidance on the best way to update and delete certain name and address pairs.

Replies are listed 'Best First'.
Re: Building an SQL query
by AgentM (Curate) on Oct 19, 2000 at 21:19 UTC
    Hmm. This is not the best way to organize a database. You may want to look into normalization. This will cut down on redundacy and maximize efficiency. Also, I can imagine that every time you want to use the data (for example in a CGI script), you are forced to use a split. If you retrieve the normalized data in the first place, you'll probably see a performance boost. That said, what's with the question? SQL has specific keywords that deal with insert and delete. You may look them up in the DBI tutorial.
    AgentM Systems nor Nasca Enterprises nor Bone::Easy is responsible for the comments made by AgentM.
Re: Building an SQL query
by runrig (Abbot) on Oct 19, 2000 at 21:19 UTC
    The 'best' way would be to have two tables, a 'user' table and an 'address' table, with a common column ('username' or perhaps a 'user_id' field), then your problem would be simple. Then you could easily insert, delete, and update addresses, and there would be no limit on the number of addresses you could store.

    That being said, with the way you describe the table, you'd need to select the 'addresses' of a user, split them up, add, delete, or update the appropriate one, paste the addresses back together, then update the user record. Sounds like a pain to me, but its what you'd have to do.

    Update: A simple split/join procedure:
    # Add/Update or delete an address my %hsh; my @arr=split /\s*,\s*/, $addresses; @hsh{@arr}=@arr; # Add/Update an address $hsh{'Joe Schmoe joe@schmoe.com'}='Joseph Schmoe joseph@schmoe.com'; # Delete an address delete $hsh{JoeBob Schmoe joebob@scmoe.com}; $addresses = join ", ", values %hsh;
Re: Building an SQL query
by swiftone (Curate) on Oct 19, 2000 at 21:16 UTC
    Are you asking for SQL statement advice, or the perl code to access them?

    SQL: "update {TABLENAME} set {FIELDNAME}={NEW VALUE} where {CONDITIONS}"

    Perl: read up on DBI and the DBD modules for your database.

    Update: Just noticed that you have more than one entry in your addresses field. Bad practice, it limits you. You should have multiple records:
    DESPAIRIamhere@place.org
    DESPAIRandheretoo@nother.place.org

Re: Building an SQL query
by cadfael (Friar) on Oct 19, 2000 at 22:05 UTC
    I think I would start off by deciding what data are 1:1 -- that is what should you put into the main table. For example:
    create table person_data ( id_no int NOT NULL, surname varchar(100) NOT NULL, given_name varchar(100) NOT NULL )

    And then tables consisting of 1:many data such as email addresses, phone numbers, etcetera. For example:

    create table person_email ( id_no int NOT NULL email_address varchar(100) NOT NULL )
    I recommend the use of a surrogate key (i.e. the id_no) because the person's name (the natural key) is subject to change. By using a stable surrogate key, you avoid having to change the key because it stays with the record until deleted.

    By using a 1:many table, you have the flexibility of dealing with multiple phone numbers or email addresses. By placing each in a separate row, you avoid the situation where you have multiple values in one field, which is a violation of First Normal Form, and of course by extension, Third Normal Form.

    Why worry about normality? Because the power of a relation database lies in the elimination of redundancy, thereby increasing data integrity, and reducing time spent in maintenance. And as a previous reply pointed out, when you get to the point where you are using DBI::DBD to extract the information, you have fewer steps you need to take with Perl.

    Good Luck

    -----
    "Computeri non cogitant, ergo non sunt"

Re: Building an SQL query
by arturo (Vicar) on Oct 19, 2000 at 21:50 UTC
    How can I pull this peice of data out of all the addresses If I select * from addresses?

    According to your description you can't select * from addresses, because "addresses" is a column, not a table. If what you want to do is search for an address that matches a pattern, then the SQL you want is something like this:

    SELECT addresses FROM table_name WHERE addresses LIKE '%pattern%'

    One worry here is that this SELECT is not guaranteed to return a unique value.

    An alternative is to grab *all* the addresses into (say) an array in Perl and use grep to grab the ones you want. This will have the same problem as above, however.

    my @addresses; my $sth= $db->prepare('SELECT addresses FROM table_name') or die "SQL +error: $DBI::errstr\n"; $sth->execute(); while (my $row = $sth->fetchrow) { push @addresses, $row; } my @matches = grep { /pattern/ } @addresses;

    Let me add my voice to the throng that says "NORMALIZE" though. It will make your life *soooooo* much easier.

    Philosophy can be made out of anything. Or less -- Jerry A. Fodor

Re: Building an SQL query
by swiftone (Curate) on Oct 19, 2000 at 22:22 UTC
    I would of course be remiss in my duties as a Perl Monk if I didn't mention that you can write a perl script to create a new, normalized database from this one. No effort beyond writing the script required. You can use some of the code above that accesses the data you have now to be the bulk of the script, and just add some SQL statements.
Re: Building an SQL query
by Anonymous Monk on Oct 19, 2000 at 21:35 UTC
    My apologies for posting a somewhat ambiguous question. I guess you can say my confusion more or less lies within the realm of what to do after I fetch all the data in a particular addresses column. It would definitely be easier if there were two different tables, one for username, and one for all the corresponding name and address values, but for now, lets assume that this is the way it has to be. Okay, a quick example: Lets say a user, named "Blah" wants to edit a particular address ( Joe Schmoe joe@scmoe.com ). How can I pull this peice of data out of all the addresses If I select * from addresses?
      Okay, so your question is basically "How do I edit a string of comma delimited email addresses"?

      One way is to separate the data. Split the string on commas (assumes no email address have commas in them), then split each of those elements into outside <> and inside <>
      Edit the data, then rebuild your string.

      This seems to work:

      #!/usr/bin/perl -w use strict; my $test='A <a@bob.com>, B <B@b.org>, Calcutta <O@thehorror.net>'; my @names = split /\s?,\s?/, $test; #split apart $_ = [split /\s(?=<)/, $_] for (@names); #make array refs of each # Above happily learned from merlyn foreach my $name (@names) { foreach (@{$name}){ print "--$_--"; } print "\n"; }
      It outputs:
      --A----<a@bob.com>-- --B----<B@b.org>-- --Calcutta----<O@thehorror.net>--

      And of course, you can edit that as you wish. Of course, if you KNOW one element, it'd be better to use a hash

      #!/usr/bin/perl -w use strict; my $test='A <a@bob.com>, B <B@b.org>, Calcutta <O@thehorror.net>'; my %names = split /,\s|\s(?=<)/, $test; #split apart foreach my $name (keys (%names)) { print "$name==$names{$name}\n"; }
      This also works, and allows you to access (and modify) $names{Joe Schmoe}

      Rebuilding your original list is even easier after you modify it.

      my @names; while (my ($key, $value) = each %names){ push @names, "$key $value"; } print join ', ',@names;
      Note that all of the above code assumes that:
      1. No email addresses or names have commas
      2. No names or email address have < beyond the wrapper around email addresses
      3. Your list always has the entries separated with a comma then a space
      4. Your names are always separated from your email addresses by a space.
Re: Building an SQL query
by AgentM (Curate) on Oct 19, 2000 at 21:57 UTC
    Ew! Each row in your data set should contain a UNIQUE_ID (called various things in different DBengines). That's what you want to clean up first. using that, you might allow the user to be able to refine his search. next, you'll want to think about exactly what it is you want. If you say that you want a unique answer, then just grab the first one you want and ignore the others. of course, if you respond to the user with only one, it may be the wrong one. The current design of your database makes it very inefficient in a search for an address (dbload- going up?) I guess your solution is the best for the situation, though, either perl or the dbengine is going be doing lots of busywork since you have a poorly designed db!
    AgentM Systems nor Nasca Enterprises nor Bone::Easy is responsible for the comments made by AgentM.
Re: Building an SQL query
by despair (Initiate) on Oct 19, 2000 at 21:59 UTC
    Righteous. You guys are awesome. Thanks a mil.
Re: Building an SQL query
by despair (Initiate) on Oct 20, 2000 at 00:05 UTC
    Geez, I can't seem to match this pattern correctly:
    $somename <$someaddress>
      What "pattern" is that? Are you trying to build a regular expression to match each component there, so that you can pull out full names and e-mail addresses from text? Or are you trying to build a pattern out of $somename and $someaddress and match that against some known text to see if that combination exists? You'll need to be more descriptive with what you're trying to do.

      If you're trying to see if a string matches your (simple) definitions of a full name and an e-mail address, you're going to have to define (in regex terms) what $somename and $someaddress really are. Perhaps something like this:

      $somename = qr/[\w\.\s]+/; $someaddress = qr/[\w\.+]+@[\w\.]+/; if (/($somename) <($someaddress)>/) { print "Got a name of '$somename'\n"; print "Got address of '$someaddress'\n"; }
      Note that this is generally not the best way of looking for e-mail addresses in plain text. The relevant RFC's describe very complex rules for valid SMTP addresses, and it is going to be very difficult for you to build a quality set of regular expressions that will be able to pull these out of text. You may be interested in Email::Find to locate e-mail addresses, and Email::Valid to validate a sample string as an e-mail address (does not search for it, just validates an assumed address).

      On the other hand, if your intent is to build a pattern out of those two variables, to see if a particular line matches someone's name and e-mail address, you could easily do something like this:

      $somename = 'David Nesting'; $someaddress = 'david@example.com'; $_ = 'David Nesting <david@example.com>'; if (/^$somename <$someaddress>$/) { print "Yep, this one is $somename!\n"; }
      I hope this helps. Otherwise, please provide more information.
      The code I posted above split on a look-ahead for the first < (Which means it assumes your "name" fields don't have that. Note everything that Fastolfe said about Email addresses is true, they're quite difficult to validate/match for all possible cases.