Table schemas and enum types
This section contains subsets of the table schemas and enumerated types (enums) that are relevant to the SQL Editor.spans
| Column | Type | Example value |
|---|---|---|
span_id | UUID | "00000000-0000-0000-1234-426614174000" |
status | String | "error" |
name | String | "openai.chat" |
path | String | "workflow.process.step1.openai.chat" |
trace_id | UUID | "12345678-90ab-cdef-1234-426614174000" |
parent_span_id | UUID | "00000000-0000-0000-a456-abcd5667ef09" |
span_type | UInt8 | 1 |
start_time | DateTime64 | "2021-01-01 00:00:00+00" |
end_time | DateTime64 | "2021-01-01 00:00:00+00" |
input | String | "[{\"role\": \"user\", \"content\": \"Hello, world!\"}]" |
output | String | "[{\"role\": \"assistant\", \"content\": \"Hi! How can I help you today?\"}]" |
attributes | String | "{\"gen_ai.system\": \"openai\", \"gen_ai.model\": \"gpt-4o\"}" |
request_model | String | "gpt-4.1-mini" |
response_model | String | "gpt-4.1-mini-2025-04-14" |
model | String | "gpt-4.1-mini-2025-04-14" |
provider | String | "openai" |
input_tokens | UInt64 | 150 |
output_tokens | UInt64 | 100 |
total_tokens | UInt64 | 250 |
total_cost | Float64 | 0.6897 |
input_cost | Float64 | 0.5667 |
output_cost | Float64 | 0.123 |
Path
Laminar span path is stored as an array of span names in span attributes. However, in the SQL queries, it is stored as a string with items joined by a dot. For example, if the span path is["outer", "inner"], the path column will be "outer.inner".
If needed, you can still access the array value by accessing the attributes using simpleJSONExtractRaw(attributes, 'lmnr.span.path').
Parent span ID
If the current span is the top span of the trace, theparent_span_id will be a 0 UUID, i.e. "00000000-0000-0000-0000-000000000000".
Span type
Here are the values of thespan_type column and their meanings:
Input and output
Theinput and output columns are stored as either raw strings or stringified JSONs. The best way to parse them is to try
to parse them as JSON, and if it fails, use the raw string. You can also use isValidJSON function right in the query to test for this.
input and output columns are also indexed on content, so you can use them in WHERE conditions. Use ILIKE instead of LIKE, because the index is case-insensitive.
Attributes
Theattributes column is stored as a string in JSON format. That is, you can safely JSON.parse / json.loads them. In addition,
you can use JSON* and simpleJSON* functions on them right in the queries. Attributes are guaranteed to be a valid JSON object.
Model
Themodel column is set to the response model if present, otherwise it is set to the request model.
Total tokens and total cost
Usually, thetotal_tokens = input_tokens + output_tokens and total_cost = input_cost + output_cost.
However, you can manually report these values using the relevant attributes. In this case, totals may
not be equal to the sum of the input and output tokens and costs.
traces
| Column | Type | Example value |
|---|---|---|
trace_id | UUID | "01234567-1234-cdef-1234-426614174000" |
trace_type | UInt8 | 0 |
start_time | DateTime64 | "2021-01-01 00:00:00+00" |
end_time | DateTime64 | "2021-01-01 00:00:00+00" |
duration | Float64 | 1.23 |
input_tokens | UInt64 | 150 |
output_tokens | UInt64 | 100 |
total_tokens | UInt64 | 250 |
total_cost | Float64 | 0.6897 |
input_cost | Float64 | 0.5667 |
output_cost | Float64 | 0.123 |
status | String | "error" |
user_id | String | "user_123" |
session_id | String | "session_123" |
metadata | String | "{\"key\": \"value\"}" |
top_span_id | UUID | "00000000-0000-0000-1234-426614174000" |
Trace type
Here are the values of thetrace_type column and their meanings:
Duration
The duration is in seconds, and is calculated asend_time - start_time.
Status
Status is set to error if any of the spans in the trace have statuserror. Empty status means success.
Metadata
Metadata is stored as a string in JSON format. That is, you can safelyJSON.parse / json.loads it. In addition,
you can use JSON* and simpleJSON* functions on it right in the queries. Metadata is guaranteed to be a valid JSON object.
events
| Column | Type | Example value |
|---|---|---|
id | UUID | "01234567-89ab-4def-1234-426614174000" |
span_id | UUID | "00000000-0000-0000-1234-426614174000" |
name | String | "My custom event" |
timestamp | DateTime | "2021-01-01 00:00:00+00" |
attributes | String | "{\"key\": \"value\"}" |
user_id | String | "user_123" |
session_id | String | "session_123" |
Notes
Attributes Theattributes column is stored as a string in JSON format. That is, you can safely JSON.parse / json.loads it. In addition,
you can use JSON* and simpleJSON* functions on it right in the queries. Attributes are guaranteed to be a valid JSON object.
evaluation_datapoints
| Column | Type | Example value |
|---|---|---|
id | UUID | "01234567-89ab-4def-1234-426614174000" |
trace_id | UUID | "01234567-1234-cdef-1234-426614174000" |
evaluation_id | UUID | "98765432-1098-4654-3210-987654321098" |
created_at | DateTime64 | "2021-01-01 00:00:00+00" |
data | String | "{\"key\": \"value\"}" |
target | String | "{\"key\": \"value\"}" |
metadata | String | "{\"key\": \"value\"}" |
index | UInt64 | 0 |
evaluation_scores
Evaluation scores are stored in a separate table, linked to the evaluation datapoints. The scores are flattened into a single row per score. For example, if your evaluation has 3 scores,{ "score1": 0.85, "score2": 0.90, "score3": 0.95 } will be stored as 3 rows in the evaluation_scores table.
| Column | Type | Example value |
|---|---|---|
evaluation_id | UUID | "01234567-89ab-cdef-1234-426614174000" |
evaluation_datapoint_id | UUID | "98765432-1098-7654-3210-987654321098" |
timestamp | DateTime64 | "2021-01-01 00:00:00+00" |
name | String | "My custom score" |
value | Float64 | 0.85 |
metadata | String | "{\"key\": \"value\"}" |
trace_id | UUID | "01234567-89ab-cdef-1234-426614174000" |
Best practices
Avoid joins
ClickHouse is a columnar database, so, while JOINs are supported, they are not efficient. If you are joining tables with more than a few hundred rows, the query will likely timeout or fail.Solution
Query the data you need, and join the relevant data in your application.Add start_time filter
You almost certainly want to add astart_time filter to your query.
Spans table (and thus traces aggregation on it) is sorted by start_time and trace_id, so if you
apply a start_time WHERE condition, the query will run faster.
Advantages:
- Queries run faster
- Queries will never fail because of running out of memory
Searching in span input or output
You can search in theinput and output columns of the spans table.
The search is optimized to be case-insensitive, so use ILIKE instead of LIKE.
