wtfutil/wtf · error

query returned %d tables, expected 1: %s

Error message

query returned %d tables, expected 1: %s

What it means

RunQuery expects exactly one table from the Log Analytics response and throws this when len(res.Tables) > 1. The rendering pipeline (TableRow loop) only processes res.Tables[0], so multiple tables would silently drop data. The query text is appended after the count.

Source

Thrown at modules/azurelogs/query.go:91

		},
		nil)
	if err != nil {
		return nil, fmt.Errorf("failed to execute query on workspace %s: %w", qf.WorkspaceID, err)
	}

	if res.Error != nil {
		return nil, res.Error
	}

	switch len(res.Tables) {
	case 0:
		return nil, fmt.Errorf("query returned no data tables: %s", qf.Query)
	case 1:
		if len(res.Tables[0].Columns) == 0 {
			return nil, fmt.Errorf("query returned table with no columns: %s", qf.Query)
		}
	default:
		return nil, fmt.Errorf("query returned %d tables, expected 1: %s", len(res.Tables), qf.Query)
	}

	// Process each row of data
	for _, row := range res.Tables[0].Rows {
		var r TableRow

		for _, field := range row {
			if field == nil {
				r = append(r, "")
				continue
			}

			// Convert all data types to string representation
			switch v := field.(type) {
			case string:
				r = append(r, v)
			case float64:
				r = append(r, fmt.Sprintf("%.0f", v))

View on GitHub (pinned to bb838c1ccb)

Solutions

  1. Reduce the query file to a single KQL statement that yields one table
  2. If batching is needed, split into separate query files and run each with its own Init/RunQuery
  3. Wrap multi-part logic into one statement using `union` or `materialize(...)` so a single table is returned
  4. Verify the API version in use — some responses include extra tables that older rendering code does not expect

Example fix

// before (two statements -> two tables)
AzureActivity | take 10
SigninLogs | take 10
// after (single table)
AzureActivity | take 10 | union (SigninLogs | take 10)  // or split into two query files
Defensive patterns

Strategy: validation

Validate before calling

// reject multi-statement KQL before running
if strings.Count(qf.Query, ";") > 0 && !strings.HasSuffix(strings.TrimSpace(qf.Query), ";") == false {
    // count standalone result statements
}
trimmed := strings.TrimSpace(qf.Query)
if strings.Count(trimmed, "\n") > 0 && strings.Count(strings.ReplaceAll(trimmed, ";", ""), "| ") == 0 {
    return errors.New("only a single KQL statement returning one table is supported")
}

Try / catch

tables, err := RunQuery(sess)
if err != nil {
    if strings.Contains(err.Error(), "tables, expected 1") {
        return fmt.Errorf("split the query into one statement per file: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: RunQuery or fetchDataAsync with KQL that legitimately produces multiple result tables — e.g. multiple batched statements separated by semicolons where more than one emits output, or `union`-style constructs returning several result sets in batch mode.

Common situations: Pasting multi-statement KQL (two SELECT-like statements) into the query file, or using features/API versions that return extra diagnostic tables alongside the main result.

Related errors


AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03). Data as JSON: /api/errors/d78a3a32753d27d5. Report an issue: GitHub.