oracle与infomix异同点

  之前是做oracle数据库应用开发的,现在工作用的是informix,特别不习惯。用了一段时间后才慢慢适应,最近做系统升级,把informix换成oracle数据库。顺便整理了一下informix与oracle区别,希望对各位有用。

差异点

oracle

infomix

update多列

update set c1 = 'c1', c2 = 'c2'

update set (c1, c2) = ('c1', 'c2')

命令行操作工具

sqlplus

dbaccess

插入字段内容超长

插入失败,报错

超长部分可能被截断

对正在修改的表进行查询

无影响

如果不走索引,会引起“Could not position within a table ”问题

需要在查询前设置

set lock mode to wait n;(n是等待秒数)

查看数据库用户下所有表名

user_tables

sysmaster.systables

导出库表数据

自己写程序或者脚本

load

导入库表数据

sqlldr

unload

对象名长度限制

32个字符

没查过,但远超过32

空字符串

没有空字符串,只有NULL,''与NULL等价

''与NULL是两个不同的东西,所以字符设置not null也可以插入''

rowid

全局不同

每个表都是从1开始

rownum

无,但支持select [skip] first n

日期date类型

需要比较显示地使用to_date和to_char函数互转。如果没写系统会自动进行隐式转换。

直接使用类似日期的字符串

保留字

resource、union、level、mode、

start 这些都是oracle保留字,不能用于库表或字段名

这些不是保留字(应该还有其它的保留字区别,这里只整理我们项目碰到的)

yestorday current、year、today、day、month、weekday

不支持,需要使用sysdate-1,sysdate,add_year(sysdate, 1)等写法

支持

varchar

最长4000,叫varchar2

最长256

char

自动补全空格

自动补全空格,但查询时会自动删除这些空格,所以对于用户来说感觉跟不补空格一样

字符串截取

只支持substr

支持substr或者字段名[first, n]的写法,如res[1, 2]

调用存储过程

直接调用存储过程名称

call 或者是execute procedure

call 调用如果有错误只是会提示笼统错误。而execute则是提示具体错误

number类型

decimal

dual

没有,但是可以自己创建一张

保存查询结果

支持create table as select

insert into select

只支持insert into select

统计更新

dbms_stat .gather_table_stats

update statistcs

大事务(如一个语句更新几万条数据)

对数据库性能影响比较小

对数据库性能影响比较大

连接字符串

一样,可以使用||

一样,可以使用||

库表连接

一样,支持=写法

一样,支持=写法

truncate

支持

支持

merge

支持

支持

另外,在项目数据迁移过程中,我总结了unload导出数据特点:

 

1 char类型导出时,会自动去除尾部空格

2 如果varchar类型字段的值是null,则导出结果是空字符串,但如果值是'',导出结果是斜杠空格("\ ")

3 char类型字段如果值是'',导出结果是空格(" ")

4 unload导出时可以指定字段分隔符delimiter,如果字段值本身包含delimiter,则会导出成\delimiter

5 如果字段值含有'\',则会导出成双斜杠("\\")

 

我写了一个将informix用unload导出来的文本装载回oracle的程序,大家有需要可以看看(操作oracle是用的otl,所以理论上也支持导入数据到informix,不过没测试过)

load_from_file.cc

 

//文件装载程序
#ifndef WIN32
//程序里面使用了get_rpc,用以统计成功入库的记录
//根据otl文档描述,在入库出现异常时,get_rpc在odbc和oci模式下返回结果有较大差异
//odbc模式下入库异常时get_rpc返回0,oci下则可以正确返回
#define OTL_ORA11G_R2
//#define OTL_ODBC_UNIX
//#define OTL_INFORMIX_CLI
#else
//#define OTL_ODBC  
#define OTL_ORA11G_R2
#ifndef snprintf
#define snprintf _snprintf
#endif
#endif

#define OTL_STREAM_READ_ITERATOR_ON //在没有返回数据的情况下抛出异常
#define OTL_PARANOID_EOF
#define OTL_STL


#include <string>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include "otlv4.h"

using namespace std;
#define MAX_COLUMN_LEN 2048
#define MAX_LINE_LEN 8092

//是否检查列里面有空行记录 如果不定义这个宏则不主动检查
#define CHECK_NULL_COLUMN 0

extern "C" {
    //获取数据库登录信息
    int GetODBCInfo(char *dbname,char *dbusername,char *dbpasswd);
}

//函数出口比较多是,释放资源偷懒类
template<typename T, typename FUN>
class CSimpleRelease
{
public:
    CSimpleRelease(T *pResourse, FUN releaseFunc):m_pResourse(pResourse), m_releaseFunc(releaseFunc)
    {

    }

    ~CSimpleRelease()
    {
        m_releaseFunc(m_pResourse);
    }

public:
    T *m_pResourse;
    FUN m_releaseFunc;
};

class ColumnInfo
{
public:
    ColumnInfo()
    {
        columnTypes = NULL;
        columnCnt = 0;
    }

    ~ColumnInfo()
    {
        if (columnTypes != NULL)
        {
            delete []columnTypes;
            columnTypes = NULL;
        }
    }

    int columnCnt;
    int *columnTypes; //0字符串类型,1时间类型

#ifdef CHECK_NULL_COLUMN
    vector<string> vecName;
    vector<int> vecNullable;
#endif

private:
    ColumnInfo(const ColumnInfo& other)
    {
        //不让用户拷贝赋值
    }


    ColumnInfo& operator =(const ColumnInfo& other)
    {
        //不让用户拷贝赋值
    }
};

//使用select * from table_name返回的列信息拼接成insert sql
bool getTabInfo(const char *tableName, otl_connect& db, string& insertSql, ColumnInfo &columnInfo)
{
    insertSql = string("insert into ") + tableName;
    string valuesSql = " values(";

    char column_type_format_str[64];

    snprintf(column_type_format_str, sizeof(column_type_format_str), ":c%%d<char[%d]>, ", MAX_COLUMN_LEN);
    char column_type_format_date[] = ":c%d<timestamp>, ";

    bool bRet = true;
    otl_stream cur;
    int nColumnCnt = 0;
    otl_column_desc *column_des = NULL;
    try
    {
        string selectSql = string("select * from ") + tableName;
        cur.open(1, selectSql.c_str(), db);

        column_des = cur.describe_select(nColumnCnt);

    }
    catch(otl_exception& p)
    {
        cerr << "database err:" << endl;
        cerr << "stm_text = " << p.stm_text << endl;
        cerr << "msg = " << p.msg << endl;
        cerr << "var_info = " << p.var_info << endl;
        bRet = false;
    }

    if (!bRet || nColumnCnt <= 0 || column_des == NULL)
    {
        cerr << "errmsg=获取库表列信息出错" << endl;
        return false;
    }

    columnInfo.columnCnt = nColumnCnt;
    columnInfo.columnTypes = new int[nColumnCnt];
    assert(columnInfo.columnTypes != NULL);

    //对于大多数数据库来说,数据类型和字符串类型在输入时没区别
    //举例insert into demo(num_int, num_double, str) values('1', '1.1', '1')
    for (int i = 0; i < nColumnCnt; i++)
    {
#ifdef CHECK_NULL_COLUMN
        columnInfo.vecName.push_back(column_des[i].name);
        columnInfo.vecNullable.push_back(column_des[i].nullok);
#endif
        const char *pColumnTypeFormat = NULL;
        //这里只需要区分是否时间字段即可
        switch (column_des[i].otl_var_dbtype) {
        case otl_var_timestamp:
        case otl_var_db2time:
        case otl_var_db2date:
            columnInfo.columnTypes[i] = 1;
            pColumnTypeFormat = column_type_format_date;
            break;

        default:
            columnInfo.columnTypes[i] = 0;
            pColumnTypeFormat = column_type_format_str;
            break;
        }

        char buffer[256];
        snprintf(buffer, sizeof(buffer), pColumnTypeFormat, i);
        valuesSql += buffer;
    }
    column_des = NULL;
    cur.close();

    //去掉末尾的", "
    valuesSql.resize(valuesSql.length() - 2);

    insertSql += valuesSql + ")";
    return true;
}

bool connectDataBase(otl_connect &db)
{
    char  P1[128] = {0}, P2[128] = {0}, P3[128] = {0};
    int iRet = GetODBCInfo(P1,P2,P3);
    if(iRet != 0) 
    {
        cout << "GetODBCInfo err" << endl;        
    }

    /*
    cout << "P1 = " << P1 << endl;
    cout << "P2 = " << P2 << endl;
    cout << "P3 = " << P3 << endl;
    */

    bool bRet = true;
    try
    {
        otl_connect::otl_initialize(1); // initialize OCI environment 多线程设置1
        char connect_str[128];
        snprintf(connect_str, sizeof(connect_str), "%s/%s@%s", P2, P3, P1); 
        db.rlogon(connect_str, 0);
    }
    catch(otl_exception& p)
    {
        cerr << "database err:" << endl;
        cerr << "stm_text = " << p.stm_text << endl;
        cerr << "msg = " << p.msg << endl;
        cerr << "var_info = " << p.var_info << endl;
        bRet = false;
    }

    return bRet;
}

//在字符串中查找一个字节,忽略中文(有些特殊中文编码正好有竖线)
char *strchrEngOnly(char *str, char ch)
{
    while (*str)
    {
        //中文字符占两个字节,第一个字节第一位是1
        if (0x80 & *str)
        {
            str++;
            //一般不会有这样的情况 防异常
            if (*str == '\0')
            {
                break;
            }
        }
        else
        {
            if (*str == ch)
            {
                return str;
            }
        }

        str++;
    }

    return NULL;
}


//出错还原文件行
void concatOneFileLine(char *lineBuf, vector<char *>& vecColumn)
{
    for (size_t i = 1; i < vecColumn.size(); i++)
    {
        char *p = vecColumn[i] - 1;
        *p = '|';
    }
}


//用竖线把字符串拆分成多个字段
void splitOneFileLine(char *lineBuf, vector<char *>& vecColumn)
{
    char *pStart = lineBuf;
    char *pEnd = NULL;
    while ((pEnd = strchrEngOnly(pStart, '|')) != NULL)
    {
        *pEnd = '\0';
        vecColumn.push_back(pStart);
        pStart = pEnd + 1;
    }

    //去掉行尾回车换行符
    pEnd = strchr(pStart, '\n');
    assert(pEnd != NULL);
    *pEnd = '\0';
    // 如果行末是换行符则不认为是新字段
    if (pStart != pEnd) vecColumn.push_back(pStart);
}

bool commit(otl_stream& cur, otl_connect& db, int &effectRows)
{
    try
    {
        effectRows = 0;
        cur.flush();
        effectRows = cur.get_rpc();
        db.commit();
    }
    catch(otl_exception& p)
    {
        cerr << "database commit err:" << endl;
        //oci的get_rpc才准
        effectRows = cur.get_rpc();
        cerr << "stm_text = " << p.stm_text << endl;
        cerr << "msg = " << p.msg << endl;
        cerr << "var_info = " << p.var_info << endl;
        return false;
    }

    return true;
}


//informix unload文件特点:1 char类型末尾空格自动删除 2 字符串里面的'\'用双斜杠"\\"代替 
// 3 char类型的空字符串用" "表示 4 vchar类型的空字符串用"\ "表示
// 5 字符串中的分隔符(默认是竖线'|')用"\分隔符"表示,如"\|" 
//为此做以下处理 1 "\\"替换成"\";  2 "\|" 替换成 "0x01"; 3 "\ "删除 4 "| |" 替换成 "||"
void informix_unload_dual_pre(char *pBuffer)
{
    char *pOutput = pBuffer;
    char *pCur = pBuffer;

    //第一域是空串特殊处理
    if (*pCur == ' ' && *(pCur + 1) == '|')
    {
        pCur++;
    }

    while (*pCur)
    {
        if (*pCur & 0x80)
        {
            //中文字符直接复制
            *pOutput++ = *pCur++;
            *pOutput++ = *pCur++;
        }
        else
        {
            switch(*pCur)
            {
            case '\\':
                switch (*(++pCur))
                {
                case '|':
                    *pOutput++ = (char)0x01;
                    pCur++;
                    break;
                case '\\':
                    *pOutput++ = *pCur++;
                    break;
                case ' ': //"\ "忽略
                    pCur++;
                    break;
                default:
                    *pOutput++ = *pCur++;
                    pCur++;
                    break;
                }
                break;
            case ' ':
                if (*(pCur - 1) == '|' && *(pCur + 1) == '|')
                {
                    pCur++;
                }
                else
                {
                    *pOutput++ = *pCur++;
                }
                break;
            default:
                *pOutput++ = *pCur++;
            }
        }
    }

    *pOutput = '\0';
}

void informix_unload_dual_after(char *pBuffer)
{
    while (*pBuffer)
    {
        if (*pBuffer & 0x80)
        {
            pBuffer++;
        }
        else
        {
            if (*pBuffer == 0x01)
            {
                *pBuffer = '|';
            }
        }

        pBuffer++;
    }
}

//将类时间格式字符串转换为otl_datetime类型
//支持 ^([0-9][: /-\t]*){4,14}$
//如果转换成功,dtOutput保存字符串对应的时间,如果失败dt的值不变
bool getOtlDateTime(const char *inbuf, otl_datetime& dtOutput, bool& bNullFlag)
{
    char buffer[16] = {0};
    bool bErrFlag = false;
    int nInputPos = 0;
    int nOutputPos = 0;
    otl_datetime dt;

    //最多14个数字,有15个就是错的 所以这里到了第15个数字就退出循环
    while ((nOutputPos <= 15) || (inbuf[nInputPos] != '\0'))
    {
        if ((inbuf[nInputPos] >= '0' && inbuf[nInputPos] <= '9'))
        {
            buffer[nOutputPos++] = inbuf[nInputPos++];
        }
        else if ( inbuf[nInputPos] == ' ' || inbuf[nInputPos] == '\t' 
            || inbuf[nInputPos] == ':' || inbuf[nInputPos] == '-' || inbuf[nInputPos] == '/')
        {
            nInputPos++;
        }
        else
        {
            bErrFlag = false;
            break;
        }
    }

    if (bErrFlag)
    {
        return false;
    }

    size_t len = strlen(buffer);

    switch (len)
    {
    case 0:
        bNullFlag = true;
    case 4:
        sscanf(buffer, "%4d", &dt.year);
        break;
    case 6:
        sscanf(buffer, "%4d%2d", &dt.year, &dt.month);
        break;
    case 8:
        sscanf(buffer, "%4d%2d%2d", &dt.year, &dt.month, &dt.day);
        break;
    case 10:
        sscanf(buffer, "%4d%2d%2d%2d", &dt.year, &dt.month, &dt.day, &dt.hour);
        break;
    case 12:
        sscanf(buffer, "%4d%2d%2d%2d%2d", &dt.year, &dt.month, &dt.day,
            &dt.hour, &dt.minute);
        break;
    case 14:
        sscanf(buffer, "%4d%2d%2d%2d%2d%2d", 
            &dt.year, &dt.month, &dt.day,
            &dt.hour, &dt.minute, &dt.second);
        break;
    default:
        return false;
        break;
    }
    
    dtOutput = dt;
    bNullFlag = false;
    return true;
}

typedef struct {
    int nFileLine; //已读文件行数
    int nLoadIntoDb; //成功入库行数(只有用oci底层此数据才准)否则只是参考值
    int nDualCnt; //成功处理文件行数
    int nErrcode;
}LoadInfo;

//返回值nErrcode等于0代表成功无异常,大于0,代表部分数据导入失败,小于0,代表导入完全失败(例如文件不可读)
LoadInfo loadFromFile(const char *loadSql, const char *strFileName, int nCurBuffLen, 
    const ColumnInfo &columnInfo, bool bInformixUnload/*待处理的文本是否informix unload导出格式*/,
    int nErrDualMode, otl_connect &db)
{
    LoadInfo retInfo;
    memset(&retInfo, 0, sizeof(retInfo));


    bool bRet = true;
    FILE *fp = fopen(strFileName, "r");
    if (fp == NULL)
    {
        cerr << "errmsg=open file " << strFileName << " error!";
        retInfo.nErrcode = -1;
        return retInfo;
    }
    //函数return的地方太多了,用资源托管类比较省代码
    CSimpleRelease<FILE, int(*)(FILE *)> resOfFp(fp, fclose);

    int& nFileLine = retInfo.nFileLine;  //已读取的文本行数
    int& nDualCnt = retInfo.nDualCnt; //已读取并且成功绑定到游标的行数
    int& nLoadIntoDb = retInfo.nLoadIntoDb; //已经成功提交到数据库的行数

    int effectRows = 0;
    otl_stream cur;
    otl_null otlNullValue;
    try
    {

        db.auto_commit_off();
        cur.open(nCurBuffLen, loadSql, db);
        cur.set_commit(0);

        char lineBuf[MAX_LINE_LEN];
        vector<char *> vecColumn;

        otl_datetime colDate;
        bool bColDateNullFlag = false;
        while (fgets(lineBuf, MAX_LINE_LEN, fp) != NULL)
        {
            nFileLine++;
            if ((nFileLine & 8191) == 8191) cout << "FileLine = " <<  nFileLine << endl;

            //informix unload 特殊处理
            if (bInformixUnload)
            {
                size_t len = 0;
                do 
                {
                    len += strlen(lineBuf + len);

                    if ((len < 2) || (lineBuf[len - 2] != '\\'))
                    {
                        break;
                    }

                    nFileLine++;
                    if ((nFileLine & 8191) == 8191) cout << "FileLine = " <<  nFileLine << endl;
                } while (fgets(lineBuf + len, MAX_LINE_LEN - len, fp));
            }

            if (bInformixUnload)
            {
                informix_unload_dual_pre(lineBuf);
            }

            //用竖线分割行记录
            vecColumn.clear();
            vecColumn.reserve(columnInfo.columnCnt);
            splitOneFileLine(lineBuf, vecColumn);
            if ((int) vecColumn.size() != columnInfo.columnCnt)
            {
                cerr << "errmsg=" << nFileLine << "行记录字段数与库表字段数不符" << endl;
                concatOneFileLine(lineBuf, vecColumn);
                vecColumn.clear();
                cerr << "lineBuf = " << lineBuf << endl;
                if ( (nErrDualMode == 0) && ((nFileLine - nDualCnt) <= nCurBuffLen) )
                {
                    cerr << "跳过该行" << endl;
                    continue;
                }

                cerr << "错误太多,停止导入" << endl;
                retInfo.nErrcode = 5;
                commit(cur, db, effectRows);
                retInfo.nLoadIntoDb += effectRows;
                return retInfo;
            }

            if (bInformixUnload)
            {
                for (size_t i = 0; i < vecColumn.size(); i++)
                {
                    informix_unload_dual_after(vecColumn[i]);
                }
            }

            for (int i = 0; i < columnInfo.columnCnt; i++)
            {

#ifdef CHECK_NULL_COLUMN
                //如果某列的值为空,但是该字段数据库不允许为空,则打印警告
                if (columnInfo.vecNullable[i] == 0 && vecColumn[i][0] == '\0')
                {
                    char msgBuf[1024];
                    snprintf(msgBuf, sizeof(msgBuf), "warning: %s line %d file %d is null can not insert into %s",
                        strFileName, nFileLine, i + 1, columnInfo.vecName[i].c_str());
                    cerr << "errmsg=" << msgBuf << endl;
                }
#endif
                if (columnInfo.columnTypes[i] == 0)
                {
                    cur << vecColumn[i];
                }
                else
                {
                    if (getOtlDateTime(vecColumn[i], colDate, bColDateNullFlag))
                    {
                        if (!bColDateNullFlag)
                        {
                            cur << colDate;
                        }
                        else
                        {
                            cur << otlNullValue;
                        }
                    }
                    else
                    {
                        cerr << "errmsg=" << nFileLine << "" << i << "列时间格式非法" << endl;
                        retInfo.nErrcode = 10;
                        commit(cur, db, effectRows);
                        retInfo.nLoadIntoDb += effectRows;
                        return retInfo;
                    }
                }
            }

            //每次清空缓存后获取影响记录数
            if (cur.get_dirty_buf_len() == 0)
            {
                nLoadIntoDb += cur.get_rpc();
            }
            nDualCnt++;
        }

        //处理完文件提交数据库
        cout << "文件处理完毕,准备提交事务" << endl;
        if ( commit(cur, db, effectRows) )
        {
            cout << "事务提交成功" << endl;
            retInfo.nLoadIntoDb += effectRows;
        }
        else
        {
            cerr << "提交数据到数据库出错" << endl;
            retInfo.nLoadIntoDb += effectRows;
            retInfo.nErrcode = 15;
            return retInfo;
        }
        cur.close();
    }
    catch(otl_exception& p)
    {
        cerr << "database err:" << endl;
        if (cur.get_connected() && commit(cur, db, effectRows) )
        {
            //提交数据库出错处理
            retInfo.nLoadIntoDb += effectRows;
        }

        //oci这些函数才有用,具体见otl文档
        //cerr << "cur.get_number_of_errors_in_batch() = " <<  cur.get_number_of_errors_in_batch() << endl;
        //cerr << "cur.get_error() = " << cur.get_error() << endl;
        cerr << "stm_text = " << p.stm_text << endl;
        cerr << "msg = " << p.msg << endl;
        cerr << "var_info = " << p.var_info << endl;
        retInfo.nErrcode = 20;
        return retInfo;
    }

    cout << nDualCnt << " lines load." << endl;
    return retInfo;
}

int main(int argc, char *argv[])
{
    // 参数1设置文本列数与库表不匹配时跳过该记录还是退出
    // 参数5设置输入的格式是否informix unload导出文件
    if (argc < 5) 
    {
        cerr << "errmsg=error param!" << endl;
        cerr << "usage: load_from_file continue|break buffer_cnt table_name file_name [1]" << endl;
        exit(1);
    }

    int nRet = 0;

    int nErrDualMode = 0;
    if (strcmp(argv[1], "continue") == 0)
    {
        nErrDualMode = 0;
    }
    else if (strcmp(argv[1], "break") == 0)
    {
        nErrDualMode = 1;
    }
    else
    {
        cerr << "errmsg=error param!" << endl;
        exit(1);
    }

    int nCurBuffLen = atoi(argv[2]);
    const char *strTableName = argv[3];
    const char *strFileName = argv[4];
    bool bInformixUnload = false;
    if (argc > 5)
    {
        if (atoi(argv[5]) == 1)
        {
            bInformixUnload = true;
        }
    }

    otl_connect db;
    cout << "开始连接数据库" << endl;
    if (!connectDataBase(db))
    {
        cerr << "数据库连接失败" << endl;
        nRet = 1;
    }

    ColumnInfo columnInfo;
    string insertSql;
    cout << "开始获取表结构:" << strTableName << endl;
    if (nRet == 0)
    {
        if (!getTabInfo(strTableName, db, insertSql, columnInfo))
        {
            cout << "表结构信息获取失败" << endl;
            nRet = 2;
        }
    }

    if (nRet == 0)
    {
        cout << "开始导入库表" << endl;
        LoadInfo retInfo;
        try
        {
            retInfo = loadFromFile(insertSql.c_str(), strFileName, nCurBuffLen, 
                columnInfo, bInformixUnload, nErrDualMode, db);
        }
        catch(...)
        {
            cerr << "errmsg=导入发生意外" << endl;
        }

        nRet = retInfo.nErrcode;
        if (nRet > 0)
        {
            cout << "导入部分失败" << endl;
            char errBuf[1024];
            snprintf(errBuf, sizeof(errBuf), "已读记录数%d,已处理记录数%d,确定入库记录数%d",
                retInfo.nFileLine, retInfo.nDualCnt, retInfo.nLoadIntoDb);
            cout << errBuf << endl;
        }
        else if (nRet == 0)
        {
            cout << "导入成功" << endl;
        }
        else
        {
            cout << "导入失败" << endl;
            nRet = 3;
        }
    }

    try
    {
        db.logoff();
    }
    catch(...)
    {
        cerr << "与数据库断开连接时发生意外" << endl;
    }

    if (nRet != 0)
    {
        cout << "发生异常,请检查日志" << endl;
    }
    return nRet;
}

 

posted @ 2018-12-22 10:43  皇家救星  阅读(1250)  评论(0编辑  收藏  举报