一.場景描述
數據庫為ASCII編碼單字節存儲,在查詢中文時可能會出現錯誤結果。例如查詢like“房”字,會查詢出不含“房”,含“朔科”的結果。
select * from Tablename01 where name like '%房%';
二.原因分析
一個漢字為2個字符,“朔”的第二個字節與“科”的第一個字節,正好與“房”的兩個字節相同。查看十六進制,如下:
select char2hexint('房'); --00B7 00BF select char2hexint('朔科'); --00CB 00B7 00BF 00C6
三.解決方案
1.安裝部署自定義函數chs_instr
chs_instr(參數1,參數2)是一個C編寫的自定義函數,它的功能是在參數1中查找參數2,按照參數2的字節數去查找,如果查詢不到則返回0,查詢到則返回大於0的值。
經查在Teradata庫中,使用此UDF函數與like比較,IO數基本不變,CPUTime突增近100倍。所以,遇到此類問題時,優先考慮從設計層面規避like中文。
2.語句【name like ‘%房%’】替換成【chs_instr(name,'房')>0】
四.UDF安裝部署
Teradata支持C語言的自定義函數。具體部署方法如下:
1、如下UDF存儲到文件中,文件名稱為chs_instr.udf
/* database syslib; replace FUNCTION chs_instr(srcStr VARCHAR(1024), childStr VARCHAR(64)) RETURNS INTEGER LANGUAGE C NO SQL PARAMETER STYLE TD_GENERAL EXTERNAL NAME chs_instr; sel chs_instr('弢1234|', '|'); sel index('弢1234|', '|'); */
2、如下為UDF定義中引用的C函數,文件名稱為chs_instr.c
#define SQL_TEXT Latin_Text #include <sqltypes_td.h> #include <string.h> /* Result is 0, if search_str does not apper in source_string */ /* index, a pos(start at 1) to the firt occurrence of search_str of source_string */ void chs_instr(VARCHAR_LATIN *source_string,VARCHAR_LATIN *search_str, int *result, char sqlstate[6]) { unsigned char *src = source_string, *sub = search_str, c; int sublen = strlen(sub); int slen = strlen(src); int spos = 0; *result = 0; while ( spos <= slen-sublen ) { if (memcmp(src+spos, sub, sublen) == 0) { *result = spos+1; break; } c = src[spos++]; if (c > 128) spos++; } return; }
3、使用bteq登錄數據庫(dbc用戶),指定UDF默認存儲數據庫為syslib。
bteq "logon citic/dbc,dbc" < chs_instr.udf
附錄:memcmp函數是按字節比較的。
s1,s2為字符串時候memcmp(s1,s2,1)就是比較s1和s2的第一個字節的ascII碼值;
memcmp(s1,s2,n)就是比較s1和s2的前n個字節的ascII碼值;
如:char *s1="abc";
char *s2="acd";
int r=memcmp(s1,s2,3);
就是比較s1和s2的前3個字節,第一個字節相等,第二個字節比較中大小已經確定,不必繼續比較第三字節了。所以r=-1.