tags:

views:

272

answers:

8

Surely there is a framework method that given an array of integers, strings etc converts them into a list that can be used in a SQL "IN" clause?

e.g.

int[] values = {1,2,3};

would go to

"(1,2,3)"
+9  A: 
var inClause = "("
     + String.Join(",", values.Select(x => x.ToString()).ToArray()) 
     + ")";

Note: You no longer need to call .ToArray() in .NET Framework 4. A new String.Join<T>(string separator, IEnumerable<string> values) method is added.

Mehrdad Afshari
That will produce a List<int>, not a string that can be used in a SQL statement
thecoop
thecoop: I misunderstood your question. Updated.
Mehrdad Afshari
@thecoop: No, it won't. @Mehrdad: It won't compile; `String.Join` only takes an array.
SLaks
@Slaks: I think Mehrdad's using vs2010, which will fix that.
Joel Coehoorn
It does? Cool! :)
SLaks
As will using Array.ConvertAll(values, Convert.ToString)
thecoop
SLaks: Easy to fix. Add a ToArray.
Mehrdad Afshari
@thecoop: Exactly; see my answer. @Mehrdad: I know; I was reminding you.
SLaks
Also maybe use String.Format so temp strings arent created (depends how often this will be called, of course...)
thecoop
thecoop: I can't see how `String.Format` helps here. Do you know the number of elements in advance? Also, `String.Format` is usually the least efficient method as it requires parsing the format string.
Mehrdad Afshari
@Mehrdad: "(" + str + ")" creates temporary string objects - you may not want to do this if it's used lots of times
thecoop
thecoop: You should read one of the many blogs covering string concatenation in depth. `"(" + str + ")"` is resolved to a **single** call to `String.Concat` passing the three strings as arguments. It allocates a single buffer to hold the resulting string.
Mehrdad Afshari
@thecoop - "(" + str + ")" gets converted to System.String::Concat(string, string, string) which should not create any additional temporary strings beyond what the string.Format solution would also create with the same parameters. I just verified this with ILDASM.
Jeffrey L Whitledge
+4  A: 

If you don't have access to the .NET 3.5 extension methods, you can do this:

StringBuilder sb = new StringBuilder();
sb.Append('(');

foreach (int i in values) {
    sb.Append(i).Append(',');
}

// remove final ,
sb.Length -= 1;
sb.Append(')');

string inValue = sb.ToString();

Which'll work on .NET 2

thecoop
May I ask what the downvotes were for?
thecoop
+3  A: 

You can use the String.Join method, like this:

var str = "(" + string.Join(", ", Array.ConvertAll(values, v => v.ToString(CultureInfo.InvariantCulture)));

Assuming that values is an array, Array.ConvertAll should be more efficient than LINQ with ToArray.

SLaks
+2  A: 

This could be done in one line too

public string ToInStatement(this int[] values) {
    string[] stringValues = 
       Array.ConvertAll<int, string>(values, Convert.ToString);
    string result = "(" + String.Join(",", stringValues) + ")";
    return result;
 }
Bob
A: 

Hey, great suggestions, just a slight modification below

public static class IEnumerableExtensions
{
    // reasonable to assume you will use this everywhere, not just
    // Sql statements, but log statements, anywhere you need to 
    // dump a list into a readable format!
    // 
    // HINT: extra credit: you can generalize this, and provide
    // specialized short hands that invoke the general method
    public static string ToCommaSeparatedString<T>(this IEnumerable<T> values)
    {
         // SIGH: so apparently this does not generate minimal
         // assembler on every machine, please note the following
         // is written for clarity, please feel free to substitute
         // your own favourite ultra-performance high-octance
         // string appender algorithm
         StringBuilder commaSeparated = new StringBuilder ();
         foreach (T value in values)
         {
             // PERF: store format string as const
             commaSeparated.AppendFormat ("{0}, ", value);
         }
         // PERF: store trim chars as static readonly array
         return commaSeparated.Trim (", ".ToCharArray ());
    }
}

...
// elsewhere in code
List<int> myIdentifiers = new List<int> { 1, 2, 3, 4, 5, };
string mySqlIdentifierList = myIdentifiers.ToCommaSeparatedList ();
string mySqlStatementFormat = "SELECT * FROM [SomeTable] WHERE [Id] IN ({0})";
string mySqlStatement = 
    string.format (mySqlStatementFormat, mySqlIdentifierList);
...
johnny g
It would be faster to call append twice and not parse the format string every time. It would also be faster to check whether you're doing the first item, and, if you are, don't add the comma.
SLaks
A: 

You can do this more efficiently using the following extension method:

 ///<summary>Appends a list of strings to a StringBuilder, separated by a separator string.</summary>
 ///<param name="builder">The StringBuilder to append to.</param>
 ///<param name="strings">The strings to append.</param>
 ///<param name="separator">A string to append between the strings.</param>
 public static StringBuilder AppendJoin(this StringBuilder builder, IEnumerable<string> strings, string separator) {
  if (builder == null) throw new ArgumentNullException("builder");
  if (strings == null) throw new ArgumentNullException("strings");
  if (separator == null) throw new ArgumentNullException("separator");

  bool first = true;

  foreach (var str in strings) {
   if (first)
    first = false;
   else
    builder.Append(separator);

   builder.Append(str);
  }

  return builder;
 }

 ///<summary>Combines a collection of strings into a single string.</summary>
 public static string Join<T>(this IEnumerable<T> strings, string separator, Func<T, string> selector) { return strings.Select(selector).Join(separator); }
 ///<summary>Combines a collection of strings into a single string.</summary>
 public static string Join(this IEnumerable<string> strings, string separator) { return new StringBuilder().AppendJoin(strings, separator).ToString(); }
SLaks
A: 
/// <summary>
/// Converts an array of integers into a string that may be used in a SQL IN expression.
/// </summary>
/// <param name="values">The array to convert.</param>
/// <returns>A string representing the array as a parenthetical comma-delemited list. If the array
/// is empty or missing, then "(null)" is returned.</returns>
public static string ToSqlInList(int[] values)
{
    if (values == null || values.Length == 0)
        return "(null)";  // In SQL the expression "IN (NULL)" is always false.

    return string.Concat("(", string.Join(",", Array.ConvertAll<int, string>(values,x=>x.ToString())), ")");
}
Jeffrey L Whitledge
A: 

If your list of integers is large, you may end up generating a string that is too long for your database to accept. E.g. I think the maximum length of a VARCHAR in SQL2000 is around 8K.

So I have a set of helper method something like the sample below, which return an enumeration of strings, which can then be used as follows:

List<int> idList = ...;
using(SqlCommand command = ...)
{
    ...
    foreach(string idString in ConcatenateValues(ids,",", maxLength, false))
    {
       command.Parameters[...] = idString;
       // or command.CommandText = "SELECT ... IN (" + idString + ")...";
       ... execute command ...
    }
}

The concatenate method might look something like the following:

public static IEnumerable<string> ConcatenateValues(IEnumerable<int> values, string separator, int maxLength, bool skipDuplicates)
{
    IDictionary<int, string> valueDictionary = null;
    StringBuilder sb = new StringBuilder();
    if (skipDuplicates)
    {
        valueDictionary = new Dictionary<int, string>();
    }
    foreach (int value in values)
    {
        if (skipDuplicates)
        {
            if (valueDictionary.ContainsKey(value)) continue;
            valueDictionary.Add(value, "");
        }
        string s = value.ToString(CultureInfo.InvariantCulture);
        if ((sb.Length + separator.Length + s.Length) > maxLength)
        {
            // Max length reached, yield the result and start again
            if (sb.Length > 0) yield return sb.ToString();
            sb.Length = 0;
        }
        if (sb.Length > 0) sb.Append(separator);
        sb.Append(s);
    }
    // Yield whatever's left over
    if (sb.Length > 0) yield return sb.ToString();
}
Joe
He's not putting the string into a column; he's building an `IN` clause. Read the question.
SLaks
This technique can also be used for an IN clause.
Joe