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 -----------------------------------------+