关于SQL中IN语句中项精简描述及组装方法

举例: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;

你可能感兴趣的:(Delphi,数据库,算法,SQL)