ThinkPHP框架MySQL转PostgreSQL

thinkphp项目需进行国产化,将原有mysql数据库转为基于postgresql的国产数据库。

以下内容需进一步测试完善

步骤(待完善,仅参考)

  1. 导出线上数据库到本地(含数据+结构、结构两种sql)

    • 修改char类型为varchar

      同时需导出一份数据表结构sql,修改char为varchar后导入ss_pg1;
    • 清理不必要的日志文件等数据
    • 修改部分日期字段(lawyer/_apply中的birthday等left(birthday, 1) <>1 and left(birthday, 1)<>2)
    • 修改lawyer_apply表birthday默认为Null
    • 使用navicat将“数据+结构”数据导入到ss_pg2库;
  1. 下载Mysql-to-PostgreSQL工具转换获取表结构

    https://www.convert-in.com/mysql-to-postgres.htm

    不推荐使用navicat,无法生成自增主键等信息

    选择ss_pg2进行转换,如使用ss_pg1则无法获取自增id值。

    删除insert相关语句,注意清理完整

    • 修改default ''default ''::character varying
    • 修改decimal字段长度恢复+1(lawfirm、report_month表等)
  2. 创建PostgreSQL数据库ss_pg2(最终数据库)并导入步骤2转换后的sql文件
  3. pg数据库导入tp自带的pgsql.sql文件(需修改:https://www.r1989.com/note-1347.html

    CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
    $BODY$
    DECLARE
         v_type varchar;
    BEGIN
         IF a_type='int8' THEN
              v_type:='bigint';
         ELSIF a_type='int4' THEN
              v_type:='integer';
         ELSIF a_type='int2' THEN
              v_type:='smallint';
         ELSIF a_type='bpchar' THEN
              v_type:='char';
         ELSE
              v_type:=a_type;
         END IF;
         RETURN v_type;
    END;
    $BODY$
    LANGUAGE PLPGSQL;
    
    CREATE TYPE "public"."tablestruct" AS (
      "fields_key_name" varchar(100),
      "fields_name" VARCHAR(200),
      "fields_type" VARCHAR(20),
      "fields_length" BIGINT,
      "fields_not_null" VARCHAR(10),
      "fields_default" VARCHAR(500),
      "fields_comment" VARCHAR(1000)
    );
    
    CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
    $body$
    DECLARE
         v_ret tablestruct;
         v_oid oid;
         v_sql varchar;
         v_rec RECORD;
         v_key varchar;
    BEGIN
         SELECT
               pg_class.oid  INTO v_oid
         FROM
               pg_class
               INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
         WHERE
               pg_class.relname=a_table_name;
         IF NOT FOUND THEN
             RETURN;
         END IF;
    
         v_sql='
         SELECT
               pg_attribute.attname AS fields_name,
               pg_attribute.attnum AS fields_index,
               pgsql_type(pg_type.typname::varchar) AS fields_type,
               pg_attribute.atttypmod-4 as fields_length,
               CASE WHEN pg_attribute.attnotnull  THEN ''not null''
               ELSE ''''
               END AS fields_not_null,
               pg_get_expr(pg_attrdef.adbin, pg_attribute.attrelid) AS fields_default,
               pg_description.description AS fields_comment
         FROM
               pg_attribute
               INNER JOIN pg_class  ON pg_attribute.attrelid = pg_class.oid
               INNER JOIN pg_type   ON pg_attribute.atttypid = pg_type.oid
               LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
               LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
         WHERE
               pg_attribute.attnum > 0
               AND attisdropped <> ''t''
               AND pg_class.oid = ' || v_oid || '
         ORDER BY pg_attribute.attnum' ;
    
         FOR v_rec IN EXECUTE v_sql LOOP
             v_ret.fields_name=v_rec.fields_name;
             v_ret.fields_type=v_rec.fields_type;
             IF v_rec.fields_length > 0 THEN
                v_ret.fields_length:=v_rec.fields_length;
             ELSE
                v_ret.fields_length:=NULL;
             END IF;
             v_ret.fields_not_null=v_rec.fields_not_null;
             v_ret.fields_default=v_rec.fields_default;
             v_ret.fields_comment=v_rec.fields_comment;
             SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
             IF FOUND THEN
                v_ret.fields_key_name=v_key;
             ELSE
                v_ret.fields_key_name='';
             END IF;
             RETURN NEXT v_ret;
         END LOOP;
         RETURN ;
    END;
    $body$
    LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
    
    COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
    IS '获得表信息';
    
    ---重载一个函数
    CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
    $body$
    DECLARE
        v_ret tablestruct;
    BEGIN
        FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
            RETURN NEXT v_ret;
        END LOOP;
        RETURN;
    END;
    $body$
    LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
    
    COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
    IS '获得表信息';
  1. 导入find_in_set函数支持

    ---创建函数
    CREATE OR replace function generate_subscripts(anyarray,int)
    
    RETURNS SETOF int AS $$
    
    begin
    
    SELECT generate_series(array_lower($1,$2), array_upper($1,$2));
    
    end
    
    $$ LANGUAGE plpgsql;
    
    
    CREATE OR REPLACE FUNCTION find_in_set(str text, strlist text)
    
    RETURNS bool AS $$
    
      SELECT i>0
    
      FROM generate_subscripts(string_to_array($2,','),1) g(i)
    
      WHERE (string_to_array($2, ','))[i] = $1
    
      UNION ALL
    
      SELECT false
    
      LIMIT 1
    
    $$ LANGUAGE sql STRICT;
    
  2. 新建pg数据库ss_pg1并导入ss_pg2备份数据。
  3. 使用navicat工具数据传输功能将mysql数据库ss_pg2中的数据同步至pg数据库ss_pg1中。

    通过“选项”设置仅同步数据,不含表结构,仅保留“使用完整插入语句”
  4. 使用navicat数据传输功能,将pg数据库ss_pg1导出到文件

  5. 使用navicat将步骤6导出的sql导入到pg数据库ss_pg2中

  6. 针对navicat导入错误信息完善数据

程序源码调整

  • thinkphp如存在user表,则模型需设置:protected $table = "\"user\"";(建议修改user表为users)

    替换Db::name('user') 、Db::name("user")

    User模型protected $table = 'users';

  • find_in_set:Db::name('lawfirm')->whereRaw("find_in_set('4987', \"partner_ids\")>0")->select()->toArray();

  • group查询处理

    使用聚合函数或group中添加所有字段,如:

    Model::field("count(*) as count,org_name,org_id")->group("org_id, org_name")->order('count','desc')->select();

    或Model::field("count(*) as count,min(org_name),org_id")->group("org_id")->order('count','desc')->select();

  • ifnull替换为COALESCE
  • 数据比较需确保类型一致,如:b.month = \''.$month.'\''

Tags: PostgreSQL

仅有一条评论

  1. 不建议使用以上转换方式,推荐使用瀚高数据库迁移工具。

添加新评论