

現在以下のことをしようとしています。

  1. ユーザーの名前(Name)をsplitted_nameという変数に代入。
  2. splitted_name[0]をfirst_nameに保存(first_name = splitted_name[0])
  3. splitted_name[0]とsplitted_name[-1]が別の値であればlast_nameにsplitted_name[-1]を保存

以下がコードです。

[code]

    class ConvertNameIntoFirstAndLastName < ActiveRecord::Migration
      def up
        execute <<-SQL
          DO
          $do$
          DECLARE
            u record;
          BEGIN
          FOR u IN SELECT * FROM users LOOP
            DECLARE
              splitted_name text[];
            BEGIN
              splitted_name := CASE WHEN u.name IS NULL THEN '{''}'
                                    ELSE regexp_split_to_array(u.name, E'\\s+')
                               END;
              UPDATE users
                SET 
                  first_name = splitted_name[0],
                  last_name = CASE WHEN splitted_name[0] = splitted_name[-1] THEN ''
                                   ELSE splitted_name[-1]
                              END,
                  name = splitted_name[0] || ' ' || (CASE WHEN splitted_name[0] = splitted_name[-1] THEN '{''}'
                                                          ELSE splitted_name[-1]
                                                    END)
              WHERE id = u.id;
            END;
          END LOOP;
          END;
          $do$;
        SQL
      end
    
      def down
      end
    end
    
[/code]

しかし、漢字でnameが保存されている場合にこのSQL文のu.nameが常にnullを返します。(DBのエンコーディングはUnicodeに設定されています。)

下記がエラー内容です。

PG::NotNullViolation: ERROR: null value in column "name" violates not-null
constraint  
=> 実際にはnameはnullではなく漢字のstringが入っています。

どなたか同様の問題を解決された方はいるでしょうか？原因と解決法がわかると助かります。

よろしくお願いします。


CC BY-SA 3.0
2015-10-18T16:41:34.827
17785
2015-10-18T23:17:17.030
12835
question
ruby-on-rails postgresql unicode
Rails Migration内でのexecute >> SQLでUnicodeキャラクターが常にnullを返します


すみません。勉強不足でPostgresqlでArrayの最初の値がArray[0]Array[1]でないと取得出来ないことや、Array[-1]が無いことを知りませんでした。

以下のコードで動きました。

[code]

    class ConvertNameIntoFirstAndLastName < ActiveRecord::Migration
      def up
        execute <<-SQL
          DO
          $do$
          DECLARE
            u record;
          BEGIN
          FOR u IN SELECT * FROM users LOOP
            DECLARE
              splitted_name text[];
            BEGIN
              splitted_name := regexp_split_to_array(regexp_replace(u.name, '　', ' '), ' ');
              UPDATE users
                SET 
                  first_name = splitted_name[1],
                  last_name = CASE WHEN splitted_name[1] = splitted_name[array_upper(splitted_name, 1)] THEN ''
                                   ELSE splitted_name[array_upper(splitted_name, 1)]
                              END,
                  name = splitted_name[1] || ' ' || (CASE WHEN splitted_name[1] = splitted_name[array_upper(splitted_name, 1)] THEN ''
                                                          ELSE splitted_name[array_upper(splitted_name, 1)]
                                                    END)
              WHERE id = u.id;
            END;
          END LOOP;
          END;
          $do$;
        SQL
      end
    
      def down
      end
    end
    
[/code]


CC BY-SA 3.0
2015-10-18T17:38:35.963
17786
2015-10-18T17:38:35.963
12835
17785
answer


「そもそも論」ですが、Railsのマイグレーションはスキーマの変更（テーブルやカラムの追加、インデックスの変更、etc）を目的としているので、既存データの変更に使うのは本来の用途から少し外れている気がします。（db:rollback
でデータを元に戻すのもほとんど無理でしょうし）

参考： [Active Record マイグレーション | Rails
ガイド](http://railsguides.jp/active_record_migrations.html)

あと、コードを見たところどうしてもDB上でやらなければいけない処理のようにも見えなかったので、Rails側で処理した方が簡単に書ける気がしました。

たとえば以下のような変換用のメソッドを用意して、それをサーバー上のrails consoleで実行する、というのも一つの方法です。

[code]

    class User < ActiveRecord::Base
      def self.convert_name_into_first_and_last_name
        self.transaction do
          self.all.each do |user|
            splitted_name = user.name.split(/[ 　]+/)
            user.first_name = splitted_name.first
            # splitted_nameが3つ以上に分割されている場合は別途考慮が必要
            user.last_name = splitted_name.size == 2 ? splitted_name.last : ''
            user.name = splitted_name.join(' ')
            user.save!
          end
        end
      end
    end
    
    # rails console
    User.convert_name_into_first_and_last_name
    
[/code]

こうしておくと、変更処理のテストコードも書きやすくなります。

よかったらこういう方法も検討してみてください。


CC BY-SA 3.0
2015-10-18T23:17:17.030
17788
2015-10-18T23:17:17.030
85
17785
answer