SigNoz/signoz · error

select columns cannot be empty for panelType

Error message

select columns cannot be empty for panelType %s

What it means

buildTracesQuery (v4) with a no-op aggregate and panelType=list constructs SELECT ... <selectColumns> from the span index; when SelectColumns is empty there is nothing to project besides the fixed columns, so the request is rejected.

Solutions

  1. Populate mq.SelectColumns with the attribute keys to display (e.g. service.name, name, duration_ns)
  2. Default to a sensible projection like [service.name, name, responseStatusCode] if the user selected none

Example fix

// before
mq.SelectColumns = []v3.AttributeKey{}

// after
mq.SelectColumns = []v3.AttributeKey{{Key:"service.name",DataType:"string",Type:"resource"}, {Key:"name",DataType:"string",Type:"tag"}}
Defensive patterns

Strategy: validation

Validate before calling

if panelType == v3.PanelTypeList && mq.AggregateOperator == v3.AggregateOperatorNoOp && len(mq.SelectColumns) == 0 {
    mq.SelectColumns = []v3.AttributeKey{{Key: "service.name", DataType: "string", Type: "resource"}}
}

Type guard

func hasSelectColumns(mq *v3.BuilderQuery) bool { return len(mq.SelectColumns) > 0 }

Prevention

When it happens

Trigger: Sending a PanelTypeList no-op traces query with an empty or missing mq.SelectColumns array.

Common situations: Programmatic list queries that only set filters and forget projections; UI bugs dropping select columns during query serialization.

Related errors


AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28). Data as JSON: /api/errors/dba31a6c6ccb3eca. Report an issue: GitHub.

Appendix: source

Thrown at pkg/query-service/app/traces/v4/query_builder.go:342

				afterSubQuery := tracesV3.AddLimitToQuery(constants.TracesExplorerViewSQLSelectAfterSubQuery, mq.Limit)
				if mq.Offset != 0 {
					afterSubQuery = tracesV3.AddOffsetToQuery(afterSubQuery, mq.Offset)
				}
				query = fmt.Sprintf(constants.TracesExplorerViewSQLSelectBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3, timeFilter, filterSubQuery) + withSubQuery + ")" + afterSubQuery
			} else {
				withSubQueryWithLimits := tracesV3.AddLimitToQuery(constants.TracesExplorerSpanCountWithSubQuery, mq.Limit)
				withSubQuery := fmt.Sprintf(withSubQueryWithLimits, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3_LOCAL_TABLENAME, timeFilter, filterSubQuery)
				afterSubQuery := tracesV3.AddLimitToQuery(constants.TraceExplorerSpanCountAfterSubQuery, mq.Limit)
				if mq.Offset != 0 {
					afterSubQuery = tracesV3.AddOffsetToQuery(afterSubQuery, mq.Offset)
				}
				query = fmt.Sprintf(constants.TraceExplorerSpanCountBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3) + withSubQuery + ") " + fmt.Sprintf(afterSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3, timeFilter)
			}
			// adding this to avoid the distributed product mode error which doesn't allow global in
			query += " settings distributed_product_mode='allow', max_memory_usage=10000000000"
		} else if panelType == v3.PanelTypeList {
			if len(mq.SelectColumns) == 0 {
				return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType)
			}
			selectLabels = getSelectLabels(mq.SelectColumns)
			// add it to the select labels
			queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, span_id as spanID, trace_id as traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s"
			query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy)
		} else {
			return "", fmt.Errorf("unsupported aggregate operator %s for panelType %s", mq.AggregateOperator, panelType)
		}
		return query, nil
		// ---- NOOP ends here ----
	}

	having := tracesV3.Having(mq.Having)
	if having != "" {
		having = " having " + having
	}

	groupBy := tracesV3.GroupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...)

View on GitHub (pinned to 5069bf80b0)