Delphi Tstringlist, получавате низове като низ в кавички, разделен със запетая?

Имам Tstringlist, съдържащ списък с ключове, използвани в таблица на база данни. Бих искал прост начин за генериране на един низ, съдържащ всички ключове, като всеки е разделен със запетая и ограден в единични кавички. Това е така, за да може да се използва в SQL оператор „IN“, напр. WHERE FieldX IN („One“, „Two“, „Three“).

Опитах да използвам quotechar, но той се игнорира при четене на commatext. например следния код

procedure junk;
var
  SL : Tstringlist;
  s : string;
begin
 SL := Tstringlist.Create;
 SL.Delimiter :=','; //comma delimiter
 SL.QuoteChar := ''''; //single quote around strings
 SL.Add('One');
 SL.Add('Two');
 SL.Add('Three');
 try
   s :=  SL.commatext;
   showmessage(s);
 finally
   SL.Free;
 end; //finally
end; //junk

показва съобщението One,Two,Three - без никакви кавички.

Знам, че мога да го направя по дългия път, както в

procedure junk;
var
  SL : Tstringlist;
  s : string;
  i : integer;
begin
 SL := Tstringlist.Create;
 SL.Delimiter :=','; //comma delimiter
 SL.Add('One');
 SL.Add('Two');
 SL.Add('Three');
 try
 s := '';
 for I := 0 to SL.Count - 1 do
    begin
    s := s +  ',' + '''' + SL[i] + '''';
    end;
 delete(s,1,1);
 showmessage(s);
 finally
   SL.Free;
 end;//finally
end;

но има ли по-прост начин за използване на свойствата на самия Tstringlist?


person user3209752    schedule 18.11.2015    source източник
comment
Опитите ви да направите това са недостатъчни, защото не избягвате знаци в кавички   -  person David Heffernan    schedule 19.11.2015


Отговори (5)


Ако използвате D2006 или по-нова версия, можете да използвате CLASS HELPER:

USES Classes,StrUtils;

TYPE
  TStringListHelper = CLASS HELPER FOR TStrings
                        FUNCTION ToSQL : STRING;
                      END;

FUNCTION TStringListHelper.ToSQL : STRING;
  VAR
    S : STRING;

  FUNCTION QuotedStr(CONST S : STRING) : STRING;
    BEGIN
      Result:=''''+ReplaceStr(S,'''','''''')+''''
    END;

  BEGIN
    Result:='';
    FOR S IN Self DO BEGIN
      IF Result='' THEN Result:='(' ELSE Result:=Result+',';
      Result:=Result+QuotedStr(S)
    END;
    IF Result<>'' THEN Result:=Result+')'
  END;

Този код:

SL:=TStringList.Create;
SL.Add('One');
SL.Add('Two');
SL.Add('Number Three');
SL.Add('It''s number 4');
WRITELN('SELECT * FROM TABLE WHERE FIELD IN '+SL.ToSQL);

след това ще изведе:

SELECT * FROM TABLE WHERE FIELD IN ('One','Two','Number Three','It''s number 4')
person HeartWare    schedule 18.11.2015
comment
Полезен отговор. Вече написах помощни функции на стойност цяла единица, точно както предлагате, които правят неща като ToSQL.text, ToSQL.date и т.н., както и обратното, напр. FromDB.date, FromSQL.time и т.н., за да улесните генерирането на sql с помощта делфи променливи. По същество вие предлагате просто да постави моя код за „дълъг път“ по-горе в пакетирана форма вътре в тази единица. Добра идея за чистота, но всъщност не отговаря на действителния ми въпрос дали Tstringlist може да го направи сам - person user3209752; 18.11.2015
comment
Не може - но с помощта на CLASS HELPER можете да го направите да изглежда така, сякаш може :-) - person HeartWare; 18.11.2015
comment
Мисля, че ще приема отговора ви, отчасти защото вие всъщност отговорихте на въпроса - макар и като казахте, че не може да се направи ‹grin› и отчасти защото правя това, което предлагате и поставям моя код за „дълъг път“ в друга помощна функция, наистина го накарайте да изглежда така, сякаш Tstringlist върши работата. - person user3209752; 18.11.2015

Използвайте sl.DelimitedText вместо sl.CommaText, за да следва вашите настройки. CommaText временно ще промени Delimiter и QuoteChar на някои твърдо кодирани стойности.

person Uwe Raabe    schedule 18.11.2015
comment
Вече бях пробвал това, няма значение, все още излиза като One, Two, Three без единични кавички около стойностите. - person user3209752; 18.11.2015
comment
@user3209752: Възможно е да е, защото няма нужда да цитирате низовете, тъй като в тях няма интервали или запетаи. Опитайте със стойности, които съдържат запетаи и/или интервали... - person HeartWare; 18.11.2015
comment
Но имам нужда от тях, цитирани в крайния низ, разделен със запетая, в противен случай SQL, използващ този низ, ще ги интерпретира като имена на колони вместо литерали на низове. т.е. FieldX IN (едно, две, три) е много различно от FieldX IN ('Едно', 'Две', 'Три') - person user3209752; 18.11.2015
comment
Документите всъщност ви поддържат: When retrieving DelimitedText, the resulting value delimits individual strings in two ways: each string is surrounded (before and after) by the quotation marks character specified by the QuoteChar property. In addition, individual strings are separated by the character specified by the Delimiter property.. Той просто не прави това, което е посочено в първата част (поне не за низове с една дума) - person Tom Brunberg; 18.11.2015

Не може да се разчита на CommaText (и очевидно също DelimitedText) за добавяне на кавички, тъй като те третират по различен начин низовете от една дума и няколко думи.

При извличане на CommaText всеки низ в списъка, който включва интервали, запетаи или кавички, ще се съдържа в двойни кавички и всички двойни кавички в низ ще се повтарят.

Изглежда, че няма никаква комбинация само с TStringList, така че предлагам да добавите низовете, като използвате QuotedStr. Следните настройки работят както желаете, независимо дали низовете са единични думи или много думи:

 SL := Tstringlist.Create;
 SL.Delimiter :=','; //comma delimiter
 SL.StrictDelimiter := True;
// SL.QuoteChar := ''''; //single quote around strings
// SL.Add('One');
// SL.Add('Two words');
// SL.Add('Three');
 SL.Add(QuotedStr('One'));
 SL.Add(QuotedStr('Two words'));
 SL.Add(QuotedStr('Three'));
 s := SL.CommaText; // or SL.DelimitedText;
 showmessage(s);

Изходът е:

„Едно“, „Две думи“, „Три“

person Tom Brunberg    schedule 18.11.2015
comment
StrictDelimiter не прави това, което може да се чудите - person fantaghirocco came to Rome; 18.11.2015
comment
@fantaghirocco Ооо? Какво може да е това? - person Tom Brunberg; 18.11.2015
comment
беше един от моите тестове: пробвахте ли вашия код? не отпечатва кавичките според очакванията. Изглежда, че няма лесен начин... - person fantaghirocco came to Rome; 18.11.2015
comment
@fantaghirocco Както виждате на последния ред. Но препрочитайки, първото ми изказване за CommaText не се получи по предназначение. Благодаря - person Tom Brunberg; 18.11.2015
comment
в последния си коментар не забелязах използването на QuotedStr: очевидно отпечатва това, което се очаква. Моите извинения O:-) - person fantaghirocco came to Rome; 18.11.2015
comment
Но поставянето на кавичките в списъка с низове на първо място не е много по-различно от моя „дълъг път“, когато поставям кавичките след това. Все още не мога да намеря списък с низове, за да ги сложа вместо мен. Опитах StrictDelimiter = true, но, както разбрахте, няма разлика в OUTPUT, само когато първо се зарежда списъкът с низове. В моя случай действителният списък с низове се зарежда чрез друг програмен код, а не по опростения начин, който показах в моя пример, така че не мога да поставя кавичките в източника - person user3209752; 18.11.2015
comment
@user3209752 Крайният резултат в сравнение с вашия „дълъг път“ е по същество същият, но е по-приятен за кодиране. Изглежда има грешка с TStringList, проучвам. - person Tom Brunberg; 18.11.2015

Методите TStrings.DelimitedText цитират само низовете, когато е необходимо: когато има връзка към нещо O'ne, това ще се отпечата като 'O''ne' - т.е. удвоява кавичките.

Можете да постигнете това, което искате, като зададете удобен разделител и свойството QuoteChar на стойност #0 и след това замените разделителите с кавички.

procedure junk;
var
  sl : Tstringlist;
  s: string;
begin
  sl := Tstringlist.Create;
  try
    sl.Delimiter := '|';
    sl.QuoteChar := #0;//no quote
    sl.Add('On''e');
    sl.Add('Two');
    sl.Add('Three');

    //escape the single quotes
    s := StringReplace(sl.DelimitedText, '''', '''''', [rfReplaceAll]);

    //replace the delimiters and quote the text
    s := '''' +  StringReplace(s, sl.Delimiter, ''',''', [rfReplaceAll]) + '''';

    WriteLn(s);

  finally
    sl.Free;
  end;
end;
person fantaghirocco came to Rome    schedule 18.11.2015
comment
Това е още по-сложно от моя „дълъг път“! Вие казвате на списъка с низове какво да очаква чрез разделители във входния низ, което не мога да направя, тъй като входът се генерира другаде без разделители, плюс изрично поставяте кавички около всеки низ, което по същата причина не мога да направя. Не мога да променя какво съдържа списъкът с низове на първо място. Изглежда, че вариант на моя „дълъг път“ ще бъде единственият начин - person user3209752; 18.11.2015
comment
низовете на обекта sl никога не се променят. Можете да присвоите Delimiter и QuoteChar дори след като списъкът е попълнен със стойностите. Това влияе само на изхода DelimitedText - person fantaghirocco came to Rome; 18.11.2015

Разреших подобен проблем с нещо подобно:

  s := ''' + StringReplace(sl.CommaText, ',', ''',''', [rfReplaceAll]) + '''
person Moore    schedule 03.12.2019
comment
Ще се провали, ако един от записите в списъка с низове съдържа запетая... Или ако един от записите съдържа единични кавички... - person HeartWare; 03.12.2019