举例:select * from files where kid in (1,2,3,4,5,6,10,12,14)
优化:select * from files where kid >=1 and kid <=6 or kid in (10,12,14)
第一步:将1,2,3,4,5,6,10,12,14优化为1-6,10,12,14
思路:将字符串分割为整数数组并排序,然后从头遍历,如果是连续3个以上的数,就描述为N1-N3,不连续的丢到一边暂存,直到最后得
到一堆连续数和一堆不连续数
第二步:将1-6,10,12,14描述生成sql语句
1.将连续部分生成 kid >= 1 and kid <= 6,不连续的10,12,14生成 kid in (10,12,14)
第三步:如果是Mysql之类数据库好像有In语句1000个项的限制(没测试过),Sqlite测过,10万条都可以,如果有限制可以分段IN,也可
以使用临时表的方式,将不连续的部分插入临时表,再使用join选出,union all上连续部分选出结果即可
1.create table temp_int([kid] integer not null primary key)
2.然后将不连续的数以insert into temp_int (kid) values (n1),(n2),(n3)的方法插入临时表
3.select a.* from files a join list b on a.kid = b.kid可以选出不连续内容
4.select a.* from files a where a.kid >=100 and a.kid <=106可以选出连续部分的内容
5.将以上两个sql语句结果用union all连接起来
API设计:
TSqlStrFunc = record
//将无序整型数组精简整描述形式
class function Simplify(const IntArrStr: string; Separator: Char): string; static;
//将精简后的描述构造成Sql语句
class function BuildSql(const IntArrDesc, TableName, KeyField: string): string; static;
end;
//需要引用 System.Generics.Collections单元(DelphiXE)
{ TSqlInString }
class function TSqlStrFunc.Simplify(const IntArrStr: string; Separator: Char): string;
var
dict: TDictionary;
sl: TStringDynArray;
il: TIntegerDynArray;
I, J,
C, M, N, K: Integer;
csl, dsl: TStrings;
begin
Result := '';
sl := SplitString(IntArrStr.Replace(#$A, '').Replace(#$D, ''), Separator);
K := Length(sl);
if K = 0 then Exit;
{$REGION '去重'}
dict := TDictionary.Create;
try
for I := 0 to K - 1 do
dict.AddOrSetValue(sl[I], StrToInt(sl[I]));
K := dict.Count;
setLength(il, K);
N := 0;
for M in dict.Values do
begin
il[N] := M;
Inc(N);
end;
finally
dict.Free;
end;
{$ENDREGION}
TArray.Sort(il);
csl := TStringList.Create;
dsl := TStringList.Create;
try
C := 0;//连续测试起始位置
N := 1;//连续次数
M := il[C];//上一个连续值
for I := 1 to K - 1 do
begin
Inc(M);
if (il[I] = M) and (I < K - 1) then
begin
Inc(N);
end
else
begin
if N >= 3 then
begin
//有3个以前的连续数,则之前的数据仍"连续"列表
csl.Add(Format('%d~%d', [il[C], il[I-Ord(I<>K-1)]]));
end
else
begin
//将之前认为是连续开始到I的数扔"不连续"列表
for J := C to I - Ord(I<>K-1) do
dsl.Add(IntToStr(il[J]));
end;
C := I;//重新设定连续开始位置
N := 1;
M := il[C];//上一个连续值
end;
end;
csl.AddStrings(dsl);
if csl.Count > 0 then
Result := csl.CommaText;
finally
csl.Free;
dsl.Free;
end;
end;
class function TSqlStrFunc.BuildSql(const IntArrDesc, TableName, KeyField: string): string;
var
csl, dsl: TStrings;
sl: TStringDynArray;
I, K: Integer;
begin
Result := '';
csl := TStringList.Create;
dsl := TStringList.Create;
try
csl.CommaText := IntArrDesc.Trim;
if csl.Count = 0 then Exit;
I := 0;
while I < csl.Count do
begin
if Pos('~', csl[I]) > 1 then
begin
sl := SplitString(csl[I], '~');
if Length(sl) <> 2 then
raise Exception.Create('Continuity item format error: ' + csl[I]);
dsl.Add(Format('%s >= %s and %s <= %s', [KeyField, sl[0], KeyField, sl[1]]));//连续部分
csl.Delete(I);
end
else Inc(I);
end;
if csl.Count > 0 then
dsl.Add(Format('%s in (%s)', [KeyField, csl.CommaText]));//非连续部分
Result := 'select * from ' + TableName;//SQL
dsl.Delimiter := '$';
if dsl.Count > 0 then
Result := Result + ' where ' + dsl.DelimitedText.Replace('"', '').Replace('$', ' or ');
finally
dsl.Free;
csl.Free;
end;
end;
#API调用示例:
var
s: string;
begin
s := TSqlStrFunc.Simplify('1,2,3,4,5,6,10,12,14', ',');//描述转换
mmo2.Text := TSqlStrFunc.BuildSql(s, 'files', 'kid');
end;