Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > LINQ (Language-Integrated Query) > Запрос к таблице с n столбцов


Автор: swetik1981 8.2.2013, 06:57
Мною написана функция, которая выбирает уникальные значения в заданном пользователем столбце таблицы и подсчитывает количество соответствующих им уникальных значений во всех остальных столбцах.


    
Код

Function statArray(ByVal dTable As DataTable, ByVal numMainCol As Integer) As System.Array

        'Создаю массив имен столбцов в таблице, по которым будет произведен подсчёт статистики (все кроме основного столбца, по которому будет осуществляться группировка)
        Dim addCols(dTable.Columns.Count - 1) As String
        Dim numAddCol As Integer = 0 'Счётчик статистических столбцов
        For i = 0 To dTable.Columns.Count - 1
            If i <> numMainCol Then
                numAddCol = numAddCol + 1
                addCols(numAddCol) = dTable.Columns(i).ColumnName
            End If
        Next

        'LINQ-запросы на подсчет статики по каждому столбцу (можно, конечно, объединить всё в один запрос, но так будет более понятно)

        Dim q1 = From dt In dTable _
                        Group dt.Field(Of Object)(addCols(1)) By f_by = dt.Field(Of Object)(dTable.Columns(numMainCol).ColumnName) _
                        Into cnt = Count() _
                        Select ff = f_by, cnt = cnt

        Dim q2 = From dt In dTable _
                        Group dt.Field(Of Object)(addCols(2)) By f_by = dt.Field(Of Object)(dTable.Columns(numMainCol).ColumnName) _
                        Into cnt = Count() _
                        Select ff = f_by, cnt = cnt

        Dim q3 = From dt In dTable _
                        Group dt.Field(Of Object)(addCols(3)) By f_by = dt.Field(Of Object)(dTable.Columns(numMainCol).ColumnName) _
                        Into cnt = Count() _
                        Select ff = f_by, cnt = cnt

        'Теперь объединяющий запрос, собирающий статистику по всем столбцам в одну таблицу
        Dim query = From dt1 In q1 _
                  Join dt2 In q2 On dt2.ff Equals dt1.ff _
                  Join dt3 In q3 On dt3.ff Equals dt1.ff _
                Select main = dt1.ff, f1 = dt1.cnt, f2 = dt2.cnt, f3 = dt3.cnt

        'Возврат массива
        statTable = query.ToArray

    End Function




То есть, для таблицы:

Код

Table1

F     I    O        Year

Иванов     Сергей    Иванович    1980
Иванов     Олег    Петрович    1981
Иванов     Кирилл    Семенович    1980
Петров     Сергей    Семенович    1982
Петров     Кирилл    Семенович    1980
Сидоров Сергей    Иванович    1983
Сидоров Иван    Петрович    1980



при запуске функции:

Код

Dim Itog=statArray(Table1, 0)


Я получаю результат:

Код

Main      fl1     fl2     fl3    

Иванов    3       3       2 
Петров    2       1       2
Сидоров   2       2       2


из которого видно, что на лиц с фамилией Иванов приходится 3 уникальных имени, 3 уникальных отчества и 2 уникальных года рождения и т.д.


Код

Dim Itog=StatArray(Table1, 1)

выдаст:

Код

Main      fl1     fl2     fl3    

Сергей    3       2       3 
Олег      1       1       1
Кирилл    2       1       1
Иван      1       1       1


И так далее по всем четырём столбцам.

Функция работает, но только на таблицу с четырьмя столбцами. Для того, чтобы перестроить функцию под другое количество столбцов, необходимо менять код. 
ВОПРОС: Можно ли как-то посредством LINQ-запроса(ов) сделать аналогичную, но универсальную функцию, которая работала бы с таблицами с любым количеством столбцов и при этом выдавала бы статистику по всем столбцам аналогично, как в приведенном выше случае?

Автор: swetik1981 23.2.2013, 12:55
Неужели эта задача не разрешима?

Автор: KelTron 28.2.2013, 08:08
Вот, работает для любого количества столбцов:

Код

class Program
{
    static void Main(string[] args)
    {
        var initialData = CreateInitialData();

        Console.WriteLine("Initial:");
        PrintDataTable(initialData);

        Console.WriteLine();
        Console.WriteLine("Result:");
        PrintDataTable(CalculateStatistics(initialData, 3));

        Console.ReadLine();
    }

    private static DataTable CreateInitialData()
    {
        var table = new DataTable();
        table.Columns.Add(new DataColumn("F", typeof(string)));
        table.Columns.Add(new DataColumn("I", typeof(string)));
        table.Columns.Add(new DataColumn("O", typeof(string)));
        table.Columns.Add(new DataColumn("Year", typeof(int)));

        table.Rows.Add("Иванов", "Сергей", "Иванович", 1980);
        table.Rows.Add("Иванов", "Олег", "Петрович", 1981);
        table.Rows.Add("Иванов", "Кирилл", "Семенович", 1980);
        table.Rows.Add("Петров", "Сергей", "Семенович", 1982);
        table.Rows.Add("Петров", "Кирилл", "Семенович", 1980);
        table.Rows.Add("Сидоров", "Сергей", "Иванович", 1983);
        table.Rows.Add("Сидоров", "Иван", "Петрович", 1980);

        return table;
    }

    private static DataTable CalculateStatistics(DataTable table, int mainColInd)
    {
        var res = new DataTable();
        res.Columns.Add(new DataColumn("Main", typeof(string)));
        res.Columns.AddRange(Enumerable.Range(1, table.Columns.Count - 1)
            .Select(n => new DataColumn("fl" + n, typeof(int)))
            .ToArray());

        var checkColumns = table.Columns.Cast<DataColumn>().Where((col, ind) => ind != mainColInd).ToArray();
        var mainValues = table.AsEnumerable().Select(r => r[mainColInd]).Distinct();

        foreach (var value in mainValues)
        {
            var row = res.NewRow();

            int resColPos = 0;
            row[resColPos++] = value;

            foreach (var col in checkColumns)
            {
                row[resColPos++] = table.AsEnumerable()
                    .Where(r => r[mainColInd].Equals(value))
                    .Select(r => r[col])
                    .Distinct()
                    .Count();
            }

            res.Rows.Add(row);
        }

        return res;
    }

    private static void PrintDataTable(DataTable table)
    {
        const int colWidth = 15;

        Console.WriteLine(string.Join("", table.Columns.Cast<DataColumn>().Select(c => c.ColumnName.PadRight(colWidth))));

        Console.WriteLine(new string('-', colWidth * table.Columns.Count));

        foreach (var row in table.AsEnumerable())
        {
            Console.WriteLine(string.Join("", row.ItemArray.Select(c => c.ToString().PadRight(colWidth))));
        }
    }
}


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)