# 如何用 C# 中的本地 LLM 分析大型 CSV 文件

<!--category-- AI, LLM, Data Analysis, DuckDB, C#, Ollama -->
<datetime class="hidden">2025-12-18T10:00</datetime>

**系列:用于数据的地方LMLM(当地LLM) -- -- 第2部分第1部分**

这是每个人犯的错误: 他们试图把他们的CSV 输入一个LLM。不要。 **LLMs应提出查询,而不是消耗数据。**

您有一个 500MB CSV 文件, 想要问“ 区域的平均订单值是多少? ” 工具类似 。 [使用Excel的副驾驶员](https://support.microsoft.com/en-gb/copilot-excel) 如果数据对云服务太敏感了呢?如果需要自己建造呢?

这篇文章向您展示了当地、私人、 C# 中的方式。

> 使用 DuckDB 直接查询 CSV 文件。 使用本地 LLM 生成 SQL。 LLM 从未看到您的数据 - 只看到 schema 。 结果: 百万行文件的子 100 m 查询, 完全脱机 。

关于补充性、更注重国家牵头倡议的处理方法,即扩大利用统计概况作为LLM接口(并展示一个完整工具来落实这些想法----特征分析、安全SQL模式、合成克隆和漂流探测),见配套文章: **[DataSummarizer: 快速本地数据分析](/blog/datasummarizer-how-it-works)** - 特别是“关键升级:统计作为接口”一节。这两篇文章构成关于当地实用LLM+查询模式的简短系列。

[TOC]

## 核心洞见

将LLM 用作数据存储器是错误的抽象。 LLM 根本上无法扫描数百万行来计算平均值 — — 这并不是它们的目的。即使是200K 象征性的上下文窗口,也可能是5万行。 您的500MB CSV 有数百万。

正确的模式 : **LLM原因,数据库计算。**

```mermaid
flowchart LR
    A[User Question] --> B[LLM]
    B --> C[SQL Query]
    C --> D[DuckDB]
    D --> E[Results]
    
    style B stroke:#333,stroke-width:4px
    style D stroke:#333,stroke-width:4px
```

注意正在发生的情况: LLM 根据您的问题和模式生成 SQL 查询。 DuckDB 根据实际数据执行它。 LLM 从未碰过您的数据 - 它只看到列名和类型。 这就是为什么它快速、私有和准确。

更多关于将剖面作为LLM接口(以及执行剖面第一个解析、安全 SQL支持的 SQL A、登记册支持的会话和合成克隆的具体的CLI),见 **[DataSummarizer: 快速本地数据分析](/blog/datasummarizer-how-it-works#the-key-upgrade-statistics-as-the-interface)**.

## 为什么不把它装进记忆里呢?

显而易见的方法都有着相同的致命缺陷:

**[Csv 帮助器](https://joshclose.github.io/CsvHelper/) / 数据框架**: 将整个文件装入 RAM 。 A 500MB CSV 变成 2-4GB 对象。 A 5GB 文件? OOM 崩溃 。

**SQLite / PostgreSQL**: 需要缓慢的输入步骤(大文件的分钟),前期计划定义,以及数据库管理间接费用。

**[潘达斯国际](https://github.com/Sinaptik-AI/pandas-ai)**: 仍然将所有东西加载到记忆中。 此外, 执行LLM 产生的任意代码是一种安全恶梦 SQL 是宣示性的, 沙箱可以; Python 不是 。

## 为什么 duckDB

[鸭鸭DDB](https://duckdb.org/) 查询 CSV 文件 *直接直接直接* - 没有输入步骤, 没有装入内存 :

```csharp
using var connection = new DuckDBConnection("DataSource=:memory:");
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT Region, SUM(Amount) FROM 'sales.csv' GROUP BY Region";
// Executes directly against the file - no import, no memory explosion
```

杀手的特征是: **它将文件作为表格处理**。指向 CSV、 Parquet 或 JSON 文件并立即查询。没有 CREATE Table, 没有大容量插入, 没有等待 。

* 因素 * * CsvHelper * SQLite * DuckDB * * 因素 * CsvHelper * SQLite * DuckDB * * 因素 * CsvHelper * SQLite * DuckDB * * 因素 * CsvHelfer * SQLite * DuckDB * * 因素 * CsvHelfer * SQLite * DuckDB *
|--------|-----------|--------|--------|
| **内存** 在导入可以在
| **设置设置设置设置设置设置设置** 无
| **500500MB 文件** *~2GB RAM * 导入分钟 * 即时 *
| **5GB 文件** * OOM 坠机 * * 非常慢 * * 工作顺利 *
| **拼格** 没有 没有 是 (10-100x更快) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

DuckDB 是 Python 中的数据工程师对这个使用大小写所使用的数据 。 [. NET 约束](https://github.com/Giorgi/DuckDB.NET) 给予您完整的 ADO. NET 支持 - 它感觉像任何其他数据库, 除了您正在查询文件 。

## 堆叠

* 元件 * * 为何如此 * * * 为何如此 * * * 为何如此 * * * * * * * 部分为何如此 * * * * * * * * * * * 部分为何如此 * * * * Why this one * * * * * why this one * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|-----------|--------------|
| [鸭鸭DDB](https://duckdb.org/) 直接询问CSV,没有进口步骤
| [Duck DD. 网](https://github.com/Giorgi/DuckDB.NET) ADO.
| [奥拉马](https://ollama.ai/) 本地推论,无API键,无云
| [错数](https://github.com/bchavez/Bogus) 任何尺度的现实测试数据
| [`qwen2.5-coder:7b`](https://ollama.ai/library/qwen2.5-coder) 最大 SQL 精度为 7B 大小

> **关于安全问题的说明**我们正在执行 LLM 生成 SQL。 这比任意代码更安全, 但仍然需要验证。 见 [安保科](#security-considerations) 关于将概况转换成LLM接口和实施这些模式的CLI的更广泛讨论,请参看随附部分。 **[DataSummarizer: 快速本地数据分析](/blog/datasummarizer-how-it-works)**.

## 项目设置

让我们创建一个样本工程。 安装 NuGet 软件包 :

```bash
dotnet add package DuckDB.NET.Data.Full
dotnet add package OllamaSharp
dotnet add package Bogus
```

调出一个在SQL上很好的以编码为重点的模型:

```bash
ollama pull qwen2.5-coder:7b
```

## 建筑结构

以下是这些碎片是如何结合在一起的:

```mermaid
flowchart TB
    subgraph Input
        Q[User Question]
        CSV[CSV File]
    end
    
    subgraph Processing
        Schema[Extract Schema]
        Sample[Get Sample Rows]
        Context[Build LLM Context]
        LLM[Generate SQL]
        Validate[Validate SQL]
        Execute[Execute Query]
    end
    
    subgraph Output
        Results[Query Results]
    end
    
    CSV --> Schema
    CSV --> Sample
    Schema --> Context
    Sample --> Context
    Q --> Context
    Context --> LLM
    LLM --> Validate
    Validate -->|Error| LLM
    Validate -->|OK| Execute
    CSV --> Execute
    Execute --> Results
    
    style LLM stroke:#333,stroke-width:4px
    style Execute stroke:#333,stroke-width:4px
```

关键见解:我们给LLM **模型和样本数据**而不是实际数据。这保持了背景小和反应快。

**为什么这重要**:LLM 生成意图(SQL) 。 DuckDDB 执行。验证步骤在执行前会捕捉语法错误。重试环会处理偶发错误。这种分离使系统既安全又准确。更详细、以 CLI 为中心的引用(包括配置文件第一解析和安全 SQL 执行限制)见 **[DataSummarizer: 快速本地数据分析](/blog/datasummarizer-how-it-works#the-key-upgrade-statistics-as-the-interface)**.

## 第1步:用bogus生成测试数据

> **有CSV数据了吗?** 跳跳到 [步骤2:构建方案表背景](#step-2-build-the-schema-context).

在测试我们的LLM驱动的CSV分析器之前,我们需要分析数据。为了开发和测试,合成数据比真实数据要快:

1. **规模测试** - 生成100K、1M或1 000M行,以不同大小校验性能
2. **隐私隐私** - 没有在演示或截图中暴露真实客户/商业数据的风险
3. **可复制** - 同一种子=同一数据,使虫虫可复制
4. **边缘病例** - 控制分配(例如,5%的军力返回,具体日期范围)

### 什么是博格斯?

[错数](https://github.com/bchavez/Bogus) 是一个流行的假冒js 库的.NET 端口。 它生成真实的假数据 - 姓名、 地址、 电子邮件、 日期、 编号 - 在适当的本地支持下。 博格斯没有手工艺测试 CSV 文件或使用随机的垃圾数据, 而是提供您的数据 。 *外观* 实数 :

- `f.Name.FullName()` "约翰史密斯"(不是"Asdf1234")
- `f.Internet.Email()` “john.smith@gmail.com”(正确格式化)
- `f.Date.Between(start, end)` 现实日期分布
- `f.Commerce.ProductName()` "手工艺的葛兰地奶酪" (有趣,但可识别)

这很重要,因为现实的数据能帮助你发现随机字符串隐藏的问题 -- -- 怪异的格式、意想不到的聚合、日期处理中的边缘案例。

### 定义数据模型

```csharp
internal class SaleRecord
{
    public string OrderId { get; set; } = "";
    public DateTime OrderDate { get; set; }
    public string CustomerId { get; set; } = "";
    public string CustomerName { get; set; } = "";
    public string Region { get; set; } = "";
    public string Category { get; set; } = "";
    public string ProductName { get; set; } = "";
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Discount { get; set; }
    public bool IsReturned { get; set; }
}
```

### 配置 Faker

Bogus使用流利的 API 来定义生成规则:

```csharp
var categories = new[] { "Electronics", "Clothing", "Home & Garden", "Sports", "Books" };
var regions = new[] { "North", "South", "East", "West", "Central" };

var faker = new Faker<SaleRecord>()
    .RuleFor(s => s.OrderId, f => f.Random.Guid().ToString()[..8].ToUpper())
    .RuleFor(s => s.OrderDate, f => f.Date.Between(
        new DateTime(2022, 1, 1), 
        new DateTime(2024, 12, 31)))
    .RuleFor(s => s.CustomerId, f => $"CUST-{f.Random.Number(10000, 99999)}")
    .RuleFor(s => s.CustomerName, f => f.Name.FullName())
    .RuleFor(s => s.Region, f => f.PickRandom(regions))
    .RuleFor(s => s.Category, f => f.PickRandom(categories))
    .RuleFor(s => s.ProductName, (f, s) => GenerateProductName(f, s.Category))
    .RuleFor(s => s.Quantity, f => f.Random.Number(1, 20))
    .RuleFor(s => s.UnitPrice, f => f.Random.Decimal(9.99m, 299.99m))
    .RuleFor(s => s.Discount, f => f.Random.Bool(0.3f) ? f.Random.Decimal(0.05m, 0.25m) : 0m)
    .RuleFor(s => s.IsReturned, f => f.Random.Bool(0.05f));
```

让我们把发生的事情细分为:

- **`f` (方 价)** - 能够访问所有数据模块(名称、日期、随机等)的生成器实例
- **`f.Random.Guid().ToString()[..8]`** - 生成一个 GUID, 但仅先取前 8 个字符作为可读顺序 ID
- **`f.Date.Between()`** - 实际范围内的随机日期(不是9999年)
- **`f.PickRandom(array)`** - 从预定义选项中随机选择( 确保有效类别)
- **`f.Random.Bool(0.3f)`** - 30%的正确机会(30%的订单得到折扣)
- **`(f, s)` 语法** - 访问假冒和部分制作的记录。 `ProductName` 取决于 `Category`

缩略 `(f, s)` 这种一致性使得生成的数据更符合现实性, 用于测试“按类别分类的收入”等聚合物。

### 生成并写入 CSV

```csharp
var records = faker.Generate(100_000); // Adjust for your testing needs

await using var writer = new StreamWriter(csvPath, false, Encoding.UTF8);
await writer.WriteLineAsync("OrderId,OrderDate,CustomerId,CustomerName,Region,Category,...");

foreach (var record in records)
{
    var total = record.Quantity * record.UnitPrice * (1 - record.Discount);
    await writer.WriteLineAsync($"{record.OrderId},{record.OrderDate:yyyy-MM-dd},...");
}
```

100K 列生成大约 15MB CSV - 足以测试, 但您可以很容易的缩放到 百万 。 生成速度很快( 100K 列为 ~ 2 秒 ) , 因为 Bogus 被优化成批生成 。

> **提示提示**: 设置 `Randomizer.Seed = new Random(12345)` 在生成数据前获取可复制的数据。相同的种子=每次相同的“随机”记录,这对调试非常宝贵。

## 步骤2:构建方案表背景

LLM 生成 SQL 之前, 它需要理解数据结构 。 我们从 DuckDB 提取此数据 :

### 上下文模式

```csharp
public class DataContext
{
    public string CsvPath { get; set; } = "";
    public List<ColumnInfo> Columns { get; set; } = new();
    public List<Dictionary<string, string>> SampleRows { get; set; } = new();
    public long RowCount { get; set; }
}

public class ColumnInfo
{
    public string Name { get; set; } = "";
    public string Type { get; set; } = "";  // VARCHAR, DOUBLE, TIMESTAMP, etc.
}
```

它记录了LLM所需要的一切:列名、类型和几个样本行,以了解数据格式。

### 抽取气管

DuckDB 可以描述任何 CSV, 不装入全部 :

```csharp
private DataContext BuildContext(DuckDBConnection connection, string csvPath)
{
    var context = new DataContext { CsvPath = csvPath };

    // Get schema - DuckDB infers types from the CSV
    using var cmd = connection.CreateCommand();
    cmd.CommandText = $"DESCRIBE SELECT * FROM '{csvPath}'";
    using var reader = cmd.ExecuteReader();

    while (reader.Read())
    {
        context.Columns.Add(new ColumnInfo
        {
            Name = reader.GetString(0),  // Column name
            Type = reader.GetString(1)   // Inferred type
        });
    }

    return context;
}
```

缩略 `DESCRIBE` 命令只读取文件页眉加几行以进行类型推断 - 即使在巨大的文件上也是瞬时的。

### 获取抽样数据

样本行有助于LLM理解数据格式(日期、身份等):

```csharp
using var cmd = connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM '{csvPath}' LIMIT 3";
using var reader = cmd.ExecuteReader();

while (reader.Read())
{
    var row = new Dictionary<string, string>();
    for (int i = 0; i < reader.FieldCount; i++)
    {
        var value = reader.IsDBNull(i) ? "NULL" : reader.GetValue(i)?.ToString() ?? "";
        row[reader.GetName(i)] = value;
    }
    context.SampleRows.Add(row);
}
```

三行通常就足够了, 它向LLM展示了在不浪费象征物的情况下可以期待的格式。

## 步骤3:与LLM生成 SQL

**这是困难的部分。** 这里的迅速工程是不可谈判的-没有严格的规则,当地LLMs将产生创造性但破碎的SQL。 目标是确定性,而不是创造性。

### 快速建设

```csharp
private string BuildPrompt(DataContext context, string question, string? previousError)
{
    var sb = new StringBuilder();

    sb.AppendLine("You are a SQL expert. Generate a DuckDB SQL query to answer the user's question.");
    sb.AppendLine();
    sb.AppendLine("IMPORTANT RULES:");
    sb.AppendLine("1. The table is accessed directly from the CSV file path");
    sb.AppendLine("2. Use single quotes around the file path in FROM clause");
    sb.AppendLine("3. DuckDB syntax - use LIMIT not TOP, use || for string concat");
    sb.AppendLine("4. Return ONLY the SQL query, no explanation, no markdown");
    sb.AppendLine();
    
    sb.AppendLine($"CSV File: '{context.CsvPath}'");
    sb.AppendLine($"Row Count: {context.RowCount:N0}");
    sb.AppendLine();
    
    // Schema
    sb.AppendLine("Schema:");
    foreach (var col in context.Columns)
    {
        sb.AppendLine($"  - {col.Name}: {col.Type}");
    }
```

规则部分至关重要 - 它告诉LLM 如何对 DuckDB 查询进行格式化 。 明确使用语法( LIMIT 相对于 TOP, 字符串连接) 防止常见错误 。

### 添加样本数据

```csharp
    if (context.SampleRows.Count > 0)
    {
        sb.AppendLine();
        sb.AppendLine("Sample data (first 3 rows):");
        foreach (var row in context.SampleRows)
        {
            var values = row.Select(kv => $"{kv.Key}='{kv.Value}'");
            sb.AppendLine($"  {{{string.Join(", ", values)}}}");
        }
    }
```

### 错误回收

如果上次尝试失败, 请包含错误 :

```csharp
    if (previousError != null)
    {
        sb.AppendLine();
        sb.AppendLine("YOUR PREVIOUS QUERY HAD AN ERROR:");
        sb.AppendLine(previousError);
        sb.AppendLine("Please fix the query based on this error.");
    }

    sb.AppendLine();
    sb.AppendLine($"Question: {question}");
    sb.AppendLine();
    sb.AppendLine("SQL Query (no markdown, no explanation):");

    return sb.ToString();
}
```

这个重试机制很重要, 本地LLMs有时会犯语法错误,

### 致电LLM

```csharp
var request = new GenerateRequest { Model = _model, Prompt = prompt };
var response = await _ollama.GenerateAsync(request).StreamToEndAsync();
var sql = CleanSqlResponse(response?.Response ?? "");
```

缩略 `StreamToEndAsync()` 等待完整回复。为了更好的 UX,您可以在标志到达时流传它们。

### 清理反应

LLMs经常用标记代码块包装SQL, 尽管他们被告知不要:

```csharp
private string CleanSqlResponse(string response)
{
    var sql = response.Trim();

    // Remove markdown code blocks if present
    if (sql.StartsWith("```"))
    {
        var lines = sql.Split('\n').ToList();
        lines.RemoveAt(0);  // Remove opening ```sql
        if (lines.Count > 0 && lines[^1].Trim().StartsWith("```"))
        {
            lines.RemoveAt(lines.Count - 1);  // Remove closing ```
        }
        sql = string.Join('\n', lines);
    }

    return sql.Trim('`', ' ', '\n', '\r');
}
```

## 步骤4:执行前验证

鸭式 DDB 的 `EXPLAIN` 让我们检查 SQL 语法而不运行查询 :

```csharp
private string? ValidateSql(DuckDBConnection connection, string sql)
{
    try
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = $"EXPLAIN {sql}";
        cmd.ExecuteNonQuery();
        return null; // Valid
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}
```

如果验证失败, 我们将把错误反馈到 LLM 并重试( 以限制为限 ) 。

## 步骤5:执行和格式结果

最后,运行查询并格式化输出:

```csharp
private QueryResult ExecuteQuery(DuckDBConnection connection, string sql)
{
    var result = new QueryResult { Sql = sql };

    try
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        using var reader = cmd.ExecuteReader();

        // Capture column names
        for (int i = 0; i < reader.FieldCount; i++)
        {
            result.Columns.Add(reader.GetName(i));
        }

        // Capture rows
        while (reader.Read())
        {
            var row = new List<object?>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                row.Add(reader.IsDBNull(i) ? null : reader.GetValue(i));
            }
            result.Rows.Add(row);
        }

        result.Success = true;
    }
    catch (Exception ex)
    {
        result.Success = false;
        result.Error = ex.Message;
    }

    return result;
}
```

缩略 `QueryResult` 类(在抽样项目中全面显示)包括a `ToString()` 方法,格式结果作为可读表格。

## 添加对话上下文

为了进行交互式分析,用户往往想问后续问题:

```
"What's the total revenue?"
→ "Break that down by region"
→ "Show the top 5 regions"
```

第二和第三个问题仅与第一个问题的背景有关,才有意义。

### 跟踪对话史

```csharp
public class ConversationTurn
{
    public string Question { get; set; } = "";
    public string Sql { get; set; } = "";
    public bool Success { get; set; }
    public int RowCount { get; set; }
    public string Summary { get; set; } = "";  // "Single value: 1234567.89"
}
```

### 将历史包含在提示中

```csharp
if (_history.Count > 0)
{
    sb.AppendLine();
    sb.AppendLine("CONVERSATION HISTORY (for context):");
    
    foreach (var turn in _history.TakeLast(5))  // Last 5 turns
    {
        sb.AppendLine($"Q: {turn.Question}");
        sb.AppendLine($"SQL: {turn.Sql}");
        if (turn.Success)
        {
            sb.AppendLine($"Result: {turn.Summary}");
        }
        sb.AppendLine();
    }
}
```

历史给了LLM上下文 来理解"那个"、"那些结果"或"进一步打破它"的提法

## 使用哪一种模型?

对于 " SQL " 一代来说,以编码为重点的模式最有效。 [Ollama的示范图书馆](https://ollama.ai/library):


|-------|------|-------|---------|------|
| `qwen2.5-coder:7b` 4.7GB [奥拉马](https://ollama.ai/library/qwen2.5-coder) |
| `deepseek-coder-v2:16b` 9GB 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 9GB 9GB 中 中 中 中 中 中 中 中 中 中 中 中 9GB 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 [奥拉马](https://ollama.ai/library/deepseek-coder-v2) |
| `codellama:7b` 4GB 快速 ~好快 [奥拉马](https://ollama.ai/library/codellama) |
| `llama3.2:3b` * 2GB * 非常快 * * 接受 * * * * * 接受 * * * * 接受 * * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * 接受 * * * 接受 * * 接受 * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * * * 接受 * * * * * 接受 * * * * * * 接受 * * * * * * * 2GB * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * [奥拉马](https://ollama.ai/library/llama3.2) |

对于大多数使用的案件, **`qwen2.5-coder:7b`** 精确的 SQL, 速度良好, 用微小的硬件运行 (8GB+ RAM) 。

## 业绩 业绩业绩 业绩业绩

### 现实世界基准

在标准德夫机器上测试100K行 CSV 文件(14MB) (Ryzen 5, NVME SSD, 32GB RAM):

* 查询类型 * 时间 *
|------------|------|
简单 COUNT $ $ 65 ms $ $ 65 ms $
与58-68ms集团
复杂集成与FILTER  63ms
由Having + 71ms组成的多表格组

用于100K行分析查询的子100米 - 没有任何导入步骤。 查询的复杂性比行数要重要得多; DuckDB 的列引擎能有效处理聚合, 不论文件大小 。

### 将大文件转换为 Parquet

对于超过1GB的文件, [拼格格式](https://parquet.apache.org/) 速度为 10- 100x :

```csharp
using var cmd = connection.CreateCommand();
cmd.CommandText = $"COPY (SELECT * FROM '{csvPath}') TO '{parquetPath}' (FORMAT PARQUET)";
cmd.ExecuteNonQuery();
```

压缩的 Parquet 文件也小得多。

### 安全考虑

执行 LLM 生成 SQL 时:

```csharp
private bool IsSafeQuery(string sql)
{
    var dangerous = new[] { "DROP", "DELETE", "TRUNCATE", "UPDATE", "INSERT", "ALTER", "CREATE" };
    var upperSql = sql.ToUpperInvariant();
    return !dangerous.Any(d => upperSql.Contains(d));
}
```

DuckDB 的模拟模式也提供自然隔离, 它不会影响您的生产数据库 。

## 完整示例

下面是所有事情的共同点:

```csharp
// Generate test data
await GenerateSalesCsvAsync("sales.csv", 100_000);

// Simple query
using var service = new CsvQueryService("qwen2.5-coder:7b", verbose: true);
var result = await service.QueryAsync("sales.csv", "What are total sales by region?");
Console.WriteLine(result);

// Conversational analysis
using var analyser = new ConversationalCsvAnalyser("sales.csv", "qwen2.5-coder:7b");

Console.WriteLine(await analyser.AskAsync("What's the total revenue?"));
Console.WriteLine(await analyser.AskAsync("Break that down by category"));
Console.WriteLine(await analyser.AskAsync("Which category has the most returns?"));
```

## 摘要摘要摘要

保持精神模式: **LLMs 理由;数据库计算。**

不要将数据输入LLM。 喂它 schema, 让它生成 SQL, 用合适的查询引擎执行 SQL 。 分离就是这个方法在规模上起作用的原因 。

执行:

1. **鸭鸭DDB** 直接查询 CSV - 无导入, 磁盘流
2. **Schema+样本** 给予 LLM 足够的上下文而不曝光数据
3. **严格迅速规则** 确定性 SQL, 不是创造性的流言
4. **通过 EXPLAIN 验证** 执行前的捕获错误
5. **使用错误反馈重试** 处理偶发语法滑块

结果:每100米子分析查询百万行文件,完全脱机,数据从不离开机器。

全部抽样项目可于下列时间提供: [最精密的 CsvLllm](https://github.com/scottgal/mostlylucidweb) - 包括 `CsvQueryService`, `ConversationalCsvAnalyser`和基于 bogus 的数据生成 。

## 资源资源资源 资源资源资源 资源资源 资源资源

### 鸭鸭DDB

- [DuckDB 文档文档](https://duckdb.org/docs/) - 全面参考
- [Duck DDB CSV 导入导入](https://duckdb.org/docs/data/csv/overview.html) - CSV的具体特点
- [Duck DB SQL 引用](https://duckdb.org/docs/sql/introduction) - SQL与其他数据库的语法差异
- [DuckDB.NET 吉特Hub](https://github.com/Giorgi/DuckDB.NET) - C# 装订
- [DuckDB. NET Nuget 数据交换器](https://www.nuget.org/packages/DuckDB.NET.Data.Full) - 带有本地二进制的完整包包

### Ollama和LLMs

- [奥拉马](https://ollama.ai/) - 当地LLM运行时间
- [奥利亚马模拟图书馆](https://ollama.ai/library) - 现有模型
- [奥利亚马沙尔普](https://github.com/awaescher/OllamaSharp) - C# 客户图书馆
- [OllamaSharp Nuget 超音速](https://www.nuget.org/packages/OllamaSharp/)

### 测试数据生成

- [博格斯吉特Hub](https://github.com/bchavez/Bogus) - 假数据生成器
- [Bogus API 参考](https://github.com/bchavez/Bogus#bogus-api-support) - 现有数据类型

### 提及的替代措施

- [Csv 帮助器](https://joshclose.github.io/CsvHelper/) - CSV 解析库
- [微软数据分析](https://www.nuget.org/packages/Microsoft.Data.Analysis) - .NET的数据框架
- [潘达斯国际](https://github.com/Sinaptik-AI/pandas-ai) - Python LLM+熊猫融合

### 相关条款

- [DataSummarizer: 快速本地数据分析](/blog/datasummarizer-how-it-works) - 包含基于CLI的数据特征分析的配套文章