Just a couple of other points.
Your statement handle would benefit from placeholders, and forming a closure for the prepared handle would allow the prepare call to only be done once:
# after $dbh is obtained
{
my $sth = $dbh->prepare
"select
hall_name,
upload_date,
uploaded_by,
photo_id
from hall_details
where
hall_id=?
limit 1";
sub details_from_id {
my $id = shift;
$sth->execute $id;
$sth->fetchrow_hashref;
} # mod some error checking
}
With that, you get the the advantages of global variables without some of the headaches. The DBI handle could itself be encapsulated this way. Is hall_id the primary key? If so the limit clause can be omitted.
The other point is that your returned hash is a level too deep. The caller already knows $hall_id - it was passed as an argument - and will just need to extract the lone value from your return. With that, you can just call details_from_id directly.
my @hall_ids = 1..20;
my %details;
@details{@hall_ids} = map { details_from_id $_ } @hall_ids;
After Compline,
Zaxo