Question

I have this idea that i want to make a sub which can take a select query and then copy the results into the same table, but with some important changes. My problem is that some of the tables that i run my sub on has large text fields that contains many different characters, some of which will break my insert statement. I then changed my insert to use parameter binding, but when I did this my query will not run because of some foreign key constraint on my "profile" field:

DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails (retriever.result, CONSTRAINT result_ibfk_2 FOREIGN KEY (profile) REFERENCES profile (id) ON DELETE CASCADE ON UPDATE CASCADE) at ./create_query.pl line 62. Cannot add or update a child row: a foreign key constraint fails (retriever.result, CONSTRAINT result_ibfk_2 FOREIGN KEY (profile) REFERENCES profile (id) ON DELETE CASCADE ON UPDATE CASCADE)

Some information about the table:

CREATE TABLE `result` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `profile` mediumint(8) unsigned NOT NULL DEFAULT '0',
    CONSTRAINT `result_ibfk_2` FOREIGN KEY (`profile`) REFERENCES `profile` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
) ENGINE=InnoDB AUTO_INCREMENT=1037028383 DEFAULT CHARSET=utf8

Here is my code:


#Define which rows to change
my $rows_to_change = {
    'profile'               => 621420,
};

#Define a select query, the results will then be copyed over
my $sql = "select * from result where profile = 639253";

#Define which table to work with
my $table = "result";
my @inserted_ids = create_insert_query($sql, $table, $rows_to_change);

for my $id (@inserted_ids){
    print $id."\n";
}

$dbh->rollback();
$dbh->disconnect();

sub create_insert_query{
    my $select          = shift;
    my $table           = shift;
    my $rows_to_change  = shift;    

    my $result = $dbh->prepare($select);
    $result->execute() or die $dbh->errstr;

    my @inserted_ids;
    while(my $row = $result->fetchrow_hashref){
        delete $row->{'id'};
        foreach my $key (keys %{$rows_to_change}){
            $row->{$key} = $rows_to_change->{$key};
        }
        my @fields;
        my @values;
        for my $key (keys %{$row}){
            push(@fields, $key);

            if(defined $row->{$key}){
                push(@values, "'$row->{$key}'");
            }else{
                push(@values, "null");
            }
        }
        my $fields_string = join(", ", @fields);
        my $values_string = join(", ", @values);
        my $questionmarks = join( ',', map { '?' } @values );
        my $query = qq[insert into $table ($fields_string) values ($questionmarks)];
        my $sth = $dbh->prepare($query);
        $sth->execute(@values) or die $dbh->errstr;

        push(@inserted_ids, $sth->{mysql_insertid});
    }
    return @inserted_ids;
}
Was it helpful?

Solution

What to do

You want instead something like:

$fieldnames   = join ', ' => map { $dbh->quote_identifier($_) } keys %row;
$placeholders = join ', ' => map { '?' } values %row;
...
$sth = $dbh->prepare('INSERT INTO t ($fieldnames) VALUES ($placeholders)');
$sth->execute(values %row);

(The quote_identifier call is there for safety in case your column names conflict with keywords or need special encoding or quoting.)

Why?

Your instant problem is that you are quoting values you then bind to placeholders (which binds the value with the quotes) and that you bind the string "null" to placeholders (which binds the string "null"). This fails some foreign key constraint, since your FKEY is probably not the literal string "null" nor is it a string representing quoted digits (e.g., it's probably the number 123 and not the string with embedded quotes "'123'").

For reference, binding strings, strings containing quotes, numeric literals, and undef under the DBI typically works like so:

 my $sth = $dbh->prepare('INSERT INTO t (a,b,c,d,e) VALUES (?,?,?,?,?)');
 $sth->execute("'quoted'", 'null', undef, 123, "a string");
 # Approximately the same as:
 #  INSERT INTO t (a,b,c,d,e) VALUES ('''quoted''', 'null', NULL, 123, 'a string');
 #                                        ^           ^      ^     ^     ^
 #               a string with quotes ----+           |      |     |     |
 #                                                    |      |     |     |
 #               a string (not the NULL value) -------+      |     |     |
 #                                                           |     |     |
 #               NULL (not the string 'null')----------------+     |     |
 #                                                                 |     |
 #               numeric literal ----------------------------------+     |
 #                                                                       |
 #               another string -----------------------------------------+
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top