DataTable
Min Max Values
Data Selection
Data Analysis
Programming

How to select min and max values of a column in a datatable?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Getting the minimum and maximum value from a DataTable column is straightforward when the column is clean and strongly typed. Real tables are usually messier: some rows contain DBNull, some imports store numbers as strings, and some reports need filtering first. A good solution should be short for the simple case and still behave predictably when the data is imperfect.

Use LINQ when the column type is already numeric

For most application code, LINQ is the clearest approach. Convert the DataTable into an enumerable sequence of rows, project the column, and call Min and Max.

csharp
1using System;
2using System.Data;
3using System.Linq;
4
5var table = new DataTable();
6table.Columns.Add("score", typeof(int));
7table.Rows.Add(12);
8table.Rows.Add(35);
9table.Rows.Add(7);
10
11var scores = table.AsEnumerable()
12    .Select(row => row.Field<int>("score"));
13
14Console.WriteLine($"Min: {scores.Min()}");
15Console.WriteLine($"Max: {scores.Max()}");

This is easy to read, works well with filters, and keeps the column access strongly typed. If you already use LINQ elsewhere in the same query, it is usually the best choice.

Filter out DBNull and empty results

Production data often contains missing values. Calling Min or Max on an empty sequence throws an exception, so it is worth handling that case explicitly.

csharp
1using System;
2using System.Data;
3using System.Linq;
4
5var table = new DataTable();
6table.Columns.Add("score", typeof(int));
7table.Rows.Add(12);
8table.Rows.Add(DBNull.Value);
9table.Rows.Add(30);
10
11var values = table.AsEnumerable()
12    .Where(row => !row.IsNull("score"))
13    .Select(row => row.Field<int>("score"))
14    .ToList();
15
16if (values.Count == 0)
17{
18    Console.WriteLine("No numeric scores found.");
19}
20else
21{
22    Console.WriteLine($"Min: {values.Min()}");
23    Console.WriteLine($"Max: {values.Max()}");
24}

Materializing into a list once is useful here because it avoids walking the same projection twice. That matters when the selection logic is more complex than a single column access.

DataTable.Compute is compact for simple reports

If you want a short expression-style answer, DataTable.Compute can calculate aggregates directly and accepts a filter expression. It is old, but still handy for report-like code.

csharp
1using System;
2using System.Data;
3
4var table = new DataTable();
5table.Columns.Add("team", typeof(string));
6table.Columns.Add("score", typeof(int));
7table.Rows.Add("A", 10);
8table.Rows.Add("A", 30);
9table.Rows.Add("B", 8);
10table.Rows.Add("B", 20);
11
12var minForA = table.Compute("MIN(score)", "team = 'A'");
13var maxForB = table.Compute("MAX(score)", "team = 'B'");
14
15Console.WriteLine($"Team A min: {minForA}");
16Console.WriteLine($"Team B max: {maxForB}");

This approach is concise, but the expression syntax is string-based, so it becomes harder to maintain as conditions grow. For business logic with several filters or type conversions, LINQ is usually easier to refactor and test.

Handle columns stored as text

Some imported tables keep numbers in string columns. In that case, parse the values explicitly instead of relying on loose conversions.

csharp
1using System;
2using System.Data;
3using System.Globalization;
4using System.Linq;
5
6var table = new DataTable();
7table.Columns.Add("score", typeof(string));
8table.Rows.Add("10");
9table.Rows.Add("25");
10table.Rows.Add("bad");
11
12var parsedValues = table.AsEnumerable()
13    .Select(row => row.Field<string>("score"))
14    .Select(value => int.TryParse(
15        value,
16        NumberStyles.Integer,
17        CultureInfo.InvariantCulture,
18        out var number)
19            ? (int?)number
20            : null)
21    .Where(number => number.HasValue)
22    .Select(number => number!.Value)
23    .ToList();
24
25Console.WriteLine($"Min: {parsedValues.Min()}");
26Console.WriteLine($"Max: {parsedValues.Max()}");

Explicit parsing makes bad input visible and avoids surprises from automatic conversion rules. If invalid values are significant, log or collect them instead of silently dropping them.

A reusable helper keeps behavior consistent

If several parts of your application need this logic, hide it behind a helper so every caller handles missing columns and empty results the same way.

csharp
1using System;
2using System.Data;
3using System.Linq;
4
5public static (int? Min, int? Max) GetMinMax(DataTable table, string columnName)
6{
7    if (!table.Columns.Contains(columnName))
8    {
9        throw new ArgumentException($"Column not found: {columnName}");
10    }
11
12    var values = table.AsEnumerable()
13        .Where(row => !row.IsNull(columnName))
14        .Select(row => Convert.ToInt32(row[columnName]))
15        .ToList();
16
17    return values.Count == 0
18        ? (null, null)
19        : (values.Min(), values.Max());
20}

That small wrapper gives you one place to define whether missing data should throw, return null, or be filtered away.

Common Pitfalls

The most common mistake is assuming every row contains a valid number. DBNull values and malformed text are normal in imported data, so guard those cases before aggregating.

Another issue is enumerating the same sequence repeatedly. If the projection is expensive or includes parsing, materialize once and compute both values from the list.

Developers also forget to validate the column name. A renamed or misspelled column then fails only at runtime.

Finally, DataTable.Compute becomes hard to maintain when the filter logic grows. It is convenient for simple expressions, but LINQ is usually the better long-term choice.

Summary

  • LINQ with AsEnumerable is the clearest approach for numeric columns.
  • Filter DBNull values and handle empty sequences explicitly.
  • 'DataTable.Compute is compact for simple aggregate expressions and filters.'
  • Parse string-based numbers yourself instead of relying on implicit conversion.
  • Put repeated min and max logic in a helper so every caller gets the same behavior.

Course illustration
Course illustration

All Rights Reserved.