• ベストアンサー

SQL構文でカラム名をハッシュのキーに

PerlをDBIでMySQLに接続しています。 MySQLの“country”テーブルに“name_en”カラムがあり、これをハッシュのキーにして、 下記構文で“name_jp”カラムに文字列を挿入しようとしたのですが、 ----------------------------------------- $sth = $dbh -> prepare ("update country set name_jp = \'$name{name_en}\' where id between 1379 and 1396"); ----------------------------------------- 結果は何も入りませんでした。 カラム名をハッシュのキーにするには、どう書けばいいのでしょうか? 宜しくお願いします。

質問者が選んだベストアンサー

  • ベストアンサー
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.3

そこの文字列は「Perl の文字列」として解釈されるんだから, カラム名として使いたい文字列をハッシュのキーに使っても無意味じゃん. ちゃんと「name_en が 'USA' で name_jp が 'アメリカ合衆国' というタプル」を作らないと.

gellan
質問者

お礼

補足説明ありがとうございます。 つまりは、裏技であれ、反則技であれ、 ハッシュのキーをカラム名として認識させる記述方法は存在しないということですね。 タプルが分からなかったので調べてみたら、 Pythonという言語はたくさんヒットしましたが、 Perlに関する説明は見つかりませんでした。 %nameに入っている要素と同じ数だけcase whenで書き並べれば、 とりあえずは出来なくはないのですが、世界には国が195ヶ国あるらしいので。

その他の回答 (8)

回答No.9

文字のエンコードが中途半端だったので手なおししました。 文字のエンコードは以下が参考になると思います。 http://d.hatena.ne.jp/tokuhirom/20080408/1207619640 http://blog.livedoor.jp/dankogai/archives/51031595.html # sqlite_unicode => 1で自動でフラグ付きUTF8に変換するようにした。 # 空白2文字を全角空白にしていることに注意 use strict; use warnings; use utf8; use feature 'say'; use DBI; binmode STDOUT, ':encoding(utf8)'; my $db_name  = 'sqlite_test.db'; my $table   = 'user_info'; my %jp_name_of = (   JPN => '日本',   USA => 'アメリカ合衆国',   SPN => 'スペイン' ); my $dbh = create_test_db($db_name); create_test_table( $dbh, $table ); say '-- before'; dump_table( $dbh, $table ); my $update_sql = 'UPDATE ' . $table . ' SET name_jp = ? where id = ?;'; my $update_sth = $dbh->prepare($update_sql); my $fetch_sql   = "SELECT id, name_en FROM $table where id between 1379 and 1396;"; my $fetch_sth = $dbh->prepare($fetch_sql); $fetch_sth->execute; while ( my $ref = $fetch_sth->fetch() ) {   my $id   = $ref->[0];   my $name_en = $ref->[1];   $update_sth->bind_param( 1, $jp_name_of{$name_en} );   $update_sth->bind_param( 2, $id );   $update_sth->execute;   $dbh->commit; } say '-- after'; dump_table( $dbh, $table ); $dbh->disconnect; unlink $db_name; sub create_test_db {   my $db_name = shift;   if ( -f $db_name ) {     unlink $db_name;   }   my $dbh = DBI->connect(     'dbi:SQLite:dbname=' . $db_name,     '', '',     {       AutoCommit   => 0,       RaiseError   => 1,       sqlite_unicode => 1,     }   ) || die "$db_name : $!";   return $dbh; } sub create_test_table {   my $dbh  = shift;   my $talbe = shift;   my $sql     = 'CREATE TABLE '     . $table     . '(id integer primary key, name_en, name_jp)';   $dbh->do($sql);   $dbh->commit;   my $sth = $dbh->prepare(     'INSERT INTO ' . $table . '(id, name_en, name_jp) VALUES (?, ?, ?)' );   while ( my $line = <DATA> ) {     $line =~ s/\x0D?\x0A?$//;     my ( $id, $name_en, $name_jp ) = ( '', '', '' );     if ( $line =~ m/^(\S+)\s+(\S+)$/ ) {       $id   = $1;       $name_en = $2;     }     elsif ( $line =~ m/^(\S+)\s+(\S+)\s+(\S+)$/ ) {       $id   = $1;       $name_en = $2;       $name_jp = $3;     }     else {       die 'Unknown format : ', $line;     }     $sth->bind_param( 1, $id );     $sth->bind_param( 2, $name_en );     $sth->bind_param( 3, $name_jp );     $sth->execute;     $dbh->commit;   } } sub dump_table {   my $dbh  = shift;   my $talbe = shift;   my $sql = "SELECT * FROM $table;";   my $sth = $dbh->prepare($sql);   $sth->execute;   while ( my $ref = $sth->fetch() ) {     say "@$ref";   } } __END__ 1377   RUS     ロシア 1378   GER     ドイツ 1379   USA 1380   SPN 1381   JPN 1395   USA 1396   JPN 1397   USA     アメリカ合衆国 1398   ITL      イタリア

gellan
質問者

お礼

エンコードのために、わざわざ手直しをして頂き、ありがとうございました。 さて、自分にはちょっと理解の難しい記述内容なのですが、せっかく頂いたので、実行させない手はないと思い、注釈にある全角スペース部分を処理していると・・・??? この内容はMySQLで使えるのでしょうか? -------------------------------------------------------------------- my $dbh = DBI -> connect('dbi:SQLite:dbname= ・・・ -------------------------------------------------------------------- 接続の部分がこうなっていますが、もしやSQLite用? もう一ヶ所気になった点が、create_test_table サブルーチン内の -------------------------------------------- while ( my $line = <DATA> ) { -------------------------------------------- ですが、<DATA>の元データは一体どこにあるんでしょうか? 私の方も、前回示した内容に関して、デコード部の記述ミスを修正し、エンコード処理を加え、さらにUPDATEパターンを二種類作ってみました。ちょっとエラーが出たりで、試行錯誤はありましたが、最終的には下記内容で、どちらのパターンも上手く機能しました。 あっ、それからデコードなんですが、selectでSQLから読み込んだ id や name_en を、処理のどこかでデコードすると、最後に挿入されるエンコード済みの日本語国名($jp_name)がなぜか文字化けしてます。半角英数はむしろデコードしない方がいいのかな・・・??? 前回こちらの評価はして頂けなかったので、一応ご報告だけしておきます。ただ、もし、どこどこに問題点があるから、この内容は使わない方がいい、ということでしたら、その旨ご指摘下さい。 use strict; use warnings; use utf8; use Encode; use DBI; #---CSVファイルから国名データ読み込み my %jp_name_of = (); open (IN, './country_name.csv') or die "$!"; while (my $line = <IN>){ chomp $line; my ($name_e, $name_j) = split /\,/, (decode 'cp932', $line); $jp_name_of{$name_e} = $name_j; } close (IN); #---MySQL内の該当データをUPDATE my $dbh = DBI -> connect ('DBI:mysql:logs:localhost', [ユーザID], [パスワード]); my $select_sth = $dbh -> prepare ("select id, name_en from country where id between 1379 and 1396"); $select_sth -> execute(); ###---パターン1ここから--->>> while (my $ary_ref = $select_sth -> fetchrow_arrayref){ my ($id, $name_en) = @$ary_ref; my $jp_name = encode 'utf-8', $jp_name_of{$name_en}; my $update_sth = $dbh -> prepare ("update country set name_jp = \'$jp_name\' where id = \'$id\'"); $update_sth -> execute(); $update_sth -> finish(); } $select_sth -> finish(); ###---パターン1ここまで---/// =pipe ###---パターン2ここから--->>> my @array = (); while (my $ary_ref = $select_sth -> fetchrow_arrayref){ my ($id, $name_en) = @$ary_ref; push @array, [$name_en, $id]; } $select_sth -> finish(); foreach my $ref (@array){ my $id = $jp_name_of{$$ref[1]}; my $jp_name = encode 'utf-8', $jp_name_of{$$ref[0]}; my $update_sth = $dbh -> prepare ("update country set name_jp = \'$jp_name\' where id = \'$id\'"); $update_sth -> execute(); $update_sth -> finish(); } ###---パターン2ここまで---/// =cut $dbh -> disconnect(); exit();

回答No.8

- %nameだとわかりづらいので、%jp_name_ofにした。 - 表示がくずれるので、空白2文字を全角空白にしているていることに注意。 use strict; use warnings; use utf8; use feature 'say'; use DBI; use Encode; binmode STDOUT, ':encoding(utf8)'; my $db_name  = 'sqlite_test.db'; my $table   = 'user_info'; my %jp_name_of = (   JPN => '日本',   USA => 'アメリカ合衆国',   SPN => 'スペイン' ); my $dbh = create_test_db($db_name); create_test_table( $dbh, $table ); say '-- before'; dump_table( $dbh, $table ); my $update_sql = 'UPDATE ' . $table . ' SET name_jp = ? where id = ?;'; my $update_sth = $dbh->prepare($update_sql); my $fetch_sql   = "SELECT id, name_en FROM $table where id between 1379 and 1396;"; my $fetch_sth = $dbh->prepare($fetch_sql); $fetch_sth->execute; while ( my $ref = $fetch_sth->fetch() ) {   my $id   = $ref->[0];   my $name_en = $ref->[1];   $update_sth->bind_param( 1, $jp_name_of{$name_en} );   $update_sth->bind_param( 2, $id );   $update_sth->execute;   $dbh->commit; } say '-- after'; dump_table( $dbh, $table ); $dbh->disconnect; unlink $db_name; sub create_test_db {   my $db_name = shift;   if ( -f $db_name ) {     unlink $db_name;   }   my $dbh = DBI->connect(     'dbi:SQLite:dbname=' . $db_name,     '', '',     {       AutoCommit => 0,       RaiseError => 1     }   ) || die "$db_name : $!";   return $dbh; } sub create_test_table {   my $dbh  = shift;   my $talbe = shift;   my $sql     = 'CREATE TABLE '     . $table     . '(id integer primary key, name_en, name_jp)';   $dbh->do($sql);   $dbh->commit;   my $sth = $dbh->prepare(     'INSERT INTO ' . $table . '(id, name_en, name_jp) VALUES (?, ?, ?)' );   while ( my $line = <DATA> ) {     $line =~ s/\x0D?\x0A?$//;     my ( $id, $name_en, $name_jp ) = ( '', '', '' );     if ( $line =~ m/^(\S+)\s+(\S+)$/ ) {       $id   = $1;       $name_en = $2;     }     elsif ( $line =~ m/^(\S+)\s+(\S+)\s+(\S+)$/ ) {       $id   = $1;       $name_en = $2;       $name_jp = encode( 'utf8', $3 );     }     else {       die 'Unknown format : ', $line;     }     $sth->bind_param( 1, $id );     $sth->bind_param( 2, $name_en );     $sth->bind_param( 3, $name_jp );     $sth->execute;     $dbh->commit;   } } sub dump_table {   my $dbh  = shift;   my $talbe = shift;   my $sql = "SELECT * FROM $table;";   my $sth = $dbh->prepare($sql);   $sth->execute;   while ( my $ref = $sth->fetch() ) {     say decode( 'utf8', "@$ref" );   } } __DATA__ 1377   RUS     ロシア 1378   GER     ドイツ 1379   USA 1380   SPN 1381   JPN 1395   USA 1396   JPN 1397   USA     アメリカ合衆国 1398   ITL      イタリア $ perl -w data.pl -- before 1377 RUS ロシア 1378 GER ドイツ 1379 USA 1380 SPN 1381 JPN 1395 USA 1396 JPN 1397 USA アメリカ合衆国 1398 ITL イタリア -- after 1377 RUS ロシア 1378 GER ドイツ 1379 USA アメリカ合衆国 1380 SPN スペイン 1381 JPN 日本 1395 USA アメリカ合衆国 1396 JPN 日本 1397 USA アメリカ合衆国 1398 ITL イタリア

gellan
質問者

お礼

ループ処理の一例、ありがとうございます。 内容を一生懸命、いや、必死に読んでおりました。 見たこともないような記述がずらずらと(^^; 空白に関する注釈があったので、実行はしていませんが。 今も何をしているのか分からない部分が多々あります。 ただ、UPDATE以外のプロセスも含まれているようなので、UPDATEの部分はこういうことなのかなと、きちんと理解するためにも、その流れを私の書ける方法で書き出してみました。 #---CSVファイルから国名データ読み込み my %jp_name_of; open (IN, './country_name.csv') or die "$!"; while (my $line = <IN>){ chomp $line; my ($name_e, $name_j) = decode 'cp932', (split /\,/, $line); $jp_name_of{$name_e} = $name_j; } close (IN); 頂いたプロセスの中に上記は含まれていませんが、このプロセスがないと、次のUPDATEができないので。 #---SQL内の該当データをUPDATE my $select_sth = $dbh -> prepare ("select id, name_en from country where id between 1379 and 1396"); $select_sth -> execute(); while (my $ary_ref = $select_sth -> fetchrow_arrayref()){ my ($id, $name_en) = @$ary_ref; my $update_sth = $dbh -> prepare ("update country set name_jp = \'$jp_name_of{$name_en}\' where id = \'$id\'"); $update_sth -> execute(); $update_sth -> finish(); } $select_sth -> finish(); SELECTで該当レコードを引っ張って来て、1レコードずつ読み込んではUPDATEしていく。 \'$jp_name_of{$name_en}\'なら$name_enはハッシュのキーとして認識されたと思います。 理解としては、これでいいのでしょうか? あっ、それから余談になりますが・・・ use utf8; use Encode; の環境で、テキストからの読み込み/テキストへの書き出し時は、その都度decode/encodeしていますが、SQLの場合、select/insert、update時に、decode/encodeはしなくていいんだろうかと。 ネット上でdecode/encodeしている例を見たことがないので、私もしていないのですが、これはいつも気になってます。

回答No.7

ループではなくて、UPDATE一発でできないかということでしょうかね。 もしDB上にテーブルを作っていいなら、 dummy_table: name_en name_jp ---------- JPN 日本 SPN スペイン USA アメリカ合衆国 ... UPDATE target_table A   set A.name_jp = (select B.name_jp from dummy_table B                    where A.name_en = B.name_en)   where id between 1379 and 1396; でいけるかなぁ? UPDATE target_table 一時名 って書き方ができないDBもあったと思うけど

gellan
質問者

お礼

ご回答ありがとうございます。 ちゃんと理解して頂けたようで良かったです。 この処理は一時的にであれ、変換参照用テーブルを作る必要があるようですね。 特に作ってはいけない理由はないので、頂いた構文を実行するために作ってみました。 そして実行したら、ちゃんとname_jpにそれぞれの日本語国名が挿入されました。 ありがとうございました。 ループの件は、その前のお礼の中で、私が case when での処理に言及したからでしょう。 もしこの処理に case when を使うなら、条件部分を約150ヶ国分書き連ねなければなりません。 恐らく「その部分をループ処理で」とおっしゃったのだと思います。 そして、もしループ処理できるなら、条件部分に今度は%nameが使えそうな気がするので、 この case when のループ構文、一体どうやって書くのか非常に知りたかったのですが・・・ 当初の質問自体には既に「出来ない」という結論が出てしまっているので、 そろそろこの質問を締め切ろうと思うのですが、 上記 case when のループ構文だけが心残りで。 というわけで、あと2、3日待ってみます(^^;;;

回答No.6

質問の内容が理解できません。 1. %nameには何が入っているのか? 2. どういう文字列を期待しているのか? print "update country set name_jp = \'$name{name_en}\' where id between 1379 and 1396", "\n"; -> update country set name_jp = "USA" where id between 1379 and 1396 ??

gellan
質問者

補足

確かにただでさえ分かりづらい内容が、更に分かりづらくなってるかもしれません。 出来るだけ分かりやすく説明してみようと思います。 1) %name = ('USA' => 'アメリカ合衆国', 'SPN' => 'スペイン', 'JPN' => '日本', ・・・・); 上記のように、%nameには国名の要素が('英語3文字略称' => '日本語')の形式で150ヶ国分くらい格納されています。 データ自体はCSVファイルとしてあり、そこから読み込んだものです。 2) countryテーブルにはid, name_en, name_jpというカラムがあり(他のカラムもありますが)、idはintのauto_increment、name_enには国名の英語3文字略称が、name_jpにはname_enに対応する日本語国名が、それぞれ自動で追加されていくようになっています。 id    name_en   name_jp -------------------------------------- 1377   RUS     ロシア 1378   GER     ドイツ 1379   USA 1380   SPN 1381   JPN (中略) 1395   USA 1396   JPN 1397   USA     アメリカ合衆国 1398   ITL      イタリア ある日、このテーブルをチェックしてみると、上記のようにidが1379から1396まで、name_enにはデータが入っているものの、name_jpには何も入っていないことが判明しました(原因は不明です) そこで、この空欄部分を埋めるべく思い付いた方法がハッシュ%nameでした。 CSVファイルから国名データを読み込んで%nameに格納し、 --------------------------------------- "update country set name_jp = \'$name{name_en}\' where id between 1379 and 1396" --------------------------------------- を実行すれば、下のように各name_enの値がハッシュのキーとして代入され、 id    name_en              name_jp -------------------------------------------------------- 1379   USA   $name{'USA'} →  アメリカ合衆国 1380   SPN   $name{'SPN'} →  スペイン 1381   JPN   $name{'JPN'} →  日本 (中略) 1395   USA   $name{'USA'} →  アメリカ合衆国 1396   JPN   $name{'JPN'} →  日本 対応する日本語国名がname_jpに挿入されるだろうと考えたわけです。 しかし、実際にこれを実行してみると、\'$name{name_en}\'のname_en部分はカラム名ではなく、単なる'name_en'という文字列と認識されるようで、結果としてどのレコードもname_jpには何も挿入されませんでした。 ここで、この方法は無理なんだ、と諦めればよかったのでしょうが、もしかしたら自分が知らないだけで、裏技、神技、反則技のどれかを使えば、name_enをカラム名と認識させる記述方法が実はあるのかもしれない、と変なこだわりが出て、最終的にこうして質問を出してみたわけです。 というわけで、最終目標は、\'$name{name_en}\'のname_enを文字列ではなく、カラム名と認識させるSQL構文の記述方法だったのですが、No.3~No.4でTacosan氏に、そんな記述方法はない、という結論を頂いてしまいました。 すでに結論が出てしまっているので、本来なら質問を締め切るのが筋なのでしょうが、No.4でTacosan氏が書き残された最後の2行が気になったので、締め切らずに現在に至っているというわけです。 どうでしょうか? これで理解して頂けたでしょうか? もしまだご質問等ありましたら、遠慮なく言ってください。 そして、もし万一、裏技、神技、反則技のどれかをご存知なら、ぜひとも教えてください。

  • hirotn
  • ベストアンサー率59% (147/246)
回答No.5

name_en     name_jp = $name{name_en} --------------------------------------- USA        アメリカ合衆国 SPN        スペイン JPN        日本 これを格納したテーブルはあるのですよね? ならば、Perl上ではハッシュを用いたデータを「別に」用意しなければならないです。 SELECTをexecuteして、 while (my $ary_ref = $sth->fetchrow_arrayref) { my ($name_en, sname_jp) = @$ary_ref; $name{$name_en}=$name_jp; } これでname_enをキーにname_jpを値に取ることのできるハッシュ変数%nameが作れると思います。

gellan
質問者

補足

ご回答頂き、ありがとうございます。 先ずテーブルの件ですが、「国名一覧」のようなテーブルは、今のところ特に作ってはいません。 CSVファイルでなら一覧はありますが、195ヶ国すべてを網羅しているわけではありません(^^; さて、本題なのですが・・・ なぜそのハッシュ%nameで私のやりたかった事ができるのか、 ちょっと内容が掴めない状態で戸惑っています>< そのハッシュを使って「set name_jp = $name{name_en}」としても name_enは単なる文字列と認識され、name_jpに値は入らないと思うのですが。 もしかして、私は何かとんでもない勘違いをしているのでしょうか? それとも、何か他の内容を説明されているのでしょうか? 鈍いヤツで申し訳ありませんが、もう少し詳しくご説明お願い致します。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.4

あれ? 「タプル」自体は SQL 方面でも特に珍しい言葉じゃないと思ったんだけど.... いずれにしても ・SQLサーバに渡すコマンド (文字列) を作るまでは Perl の世界 ・SQLサーバがコマンドを受け取ってからは SQL の世界 という 2つの世界があることを確認しておいた方がいいんじゃないかな. 念の為ですが, この 2つの世界は魔法でも使わない限り交わることはありません. 国の数がどうであれ, データ構造さえ作ってしまえばループするだけのことでは? まあ, しばしば「データ構造を作る」ところが問題になったりするんだが.

gellan
質問者

お礼

タブルは多分私が知らないだけなのだと思います。 もしやテーブルの事かな?などと思ったりもしたのですが・・・ あっ、あまり書くと無知の上塗りになってしまいますね(^^; %nameの要素を元に変換参照用テーブルを作って、joinしてupdateすれば、 確かにこんな面倒なことを考えなくても解決はします。 ただ、もしハッシュのキーをカラム名として認識させる記述方法があるなら、 今回だけでなく、今後も何かの時に使えそうな気がしたので、 あえてその処理方法にこだわって質問させて頂きました。 SQL構文の一部もしくは全体をループさせるような処理は書いたことがないので、 後学のために、今回の処理で一例を挙げてもらえませんか?

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

ハッシュのキーは (およそ) \w+ にマッチすれば自動的に文字列とみなされます. だから $name{name_en} でも $name{"name_en"} でも同じことだし, あるいは %name = ('USA' => 'アメリカ合衆国') と %name = (USA => 'アメリカ合衆国') も同じ意味だったりする. とはいえ, そもそもハッシュ %name に name_en ってキーで値は入れてあるの?

gellan
質問者

補足

ご回答ありがとうございます。 SQLのカラム名に、果たしてどれだけ半角英数以外の文字が使われているのかは知りませんが、 半角英数やハイフン、アンダーバーを用いるのが一般的という認識で、 いつもそれらのみを使用して名前を付けています。 ですから、カラム名はすべて\w+にマッチする名前になっています。 name_enはテーブルのカラム名であって、%nameの要素ではないので、 name_enというキーも、それに対応する値も、%nameには入っていません。 %name = ('USA' => 'アメリカ合衆国', 'SPN' => 'スペイン', 'JPN' => '日本', ・・・・)となっています。 確認のために、もう一度やりたいことを説明しますと・・・ 例えば、"hinichi"というカラムに日付(4桁年-2桁月-2桁日)が入っていれば、 year関数を使ってyear(hinichi)とすると、その日付の年が取得できます。 hinichi        year(hinichi) -------------------------------------- 2013-09-10     2013 2012-10-05     2012 2010-03-06     2010 これと同じように、%nameを関数のように考えて、 name_en     name_jp = $name{name_en} --------------------------------------- USA        アメリカ合衆国 SPN        スペイン JPN        日本 という構想で、name_jpに日本語の国名を挿入したかったのです。 ですから、name_enはカラム名として認識される必要があり、 単なる文字列と認識されては困るというわけです。 name_enをカラム名として認識させる記述方法はないでしょうか?

  • hirotn
  • ベストアンサー率59% (147/246)
回答No.1

例えば以下でしょうか…。 ("update country set name_jp = \'" . $name{"name_en"} . "\' where id between 1379 and 1396"); . は文字列結合演算子です。あるいは、 ("update country set name_jp = \'$name{'name_en'}\' where id between 1379 and 1396");

gellan
質問者

補足

素早いご回答にびっくりしました。 どうもありがとうございます。 さて早速、頂いた構文を両方とも試してみました。 しかし残念ながら、どちらの構文でもname_jpカラムには何も挿入されませんでした。 %nameには('USA' => 'アメリカ合衆国')も格納されているので、 試しに下記二つを実行してみたら、 ------------------- ("update country set name_jp = \'".$name{"USA"}."\' where id between 1379 and 1396"); ------------------- ("update country set name_jp = \'$name{'USA'}\' where id between 1379 and 1396"); ------------------- どちらの場合も、nama_jpカラムに'アメリカ合衆国'と挿入されました。 どちらの構文でも、ハッシュのキーは、カラム名ではなく、単なる文字列として認識されているようです。