LimeSurvey API - Master branch

ResponseAggregateBatch
in package

FinalYes

Collects every aggregate needed for survey statistics and executes them as conditional aggregates over a single scan of the responses table.

Usage is two-phased:

  1. Registration: countValue()/countBlank()/countNonEmpty()/countTotal() each return an alias for the requested aggregate (deduplicated).
  2. execute() runs the merged SELECT (chunked only when the expression list is very large), after which value($alias) returns the count.

Table of Contents

Constants

MAX_EXPRESSIONS_PER_QUERY  = 1500
Hard cap of SELECT expressions per query to bound per-row CASE cost
FIELD_SEPARATOR  = "\x1e"
Separator joining the columns of a multi-field aggregate into one field key
KIND_ANY_NON_EMPTY  = 'anyNonEmpty'
KIND_BLANK  = 'blank'
KIND_JSON_ELEMENT  = 'jsonElement'
KIND_MAX  = 'max'
KIND_MIN  = 'min'
KIND_NON_EMPTY  = 'nonEmpty'
KIND_NUMERIC  = 'numeric'
KIND_SUM  = 'sum'
KIND_SUM_SQUARES  = 'sumSquares'
KIND_TOTAL  = 'total'
KIND_VALUE  = 'value'
MAX_MEDIANS_PER_QUERY  = 100
NUMERIC_PATTERN  = '^-?[0-9]*\\.?[0-9]+$'
Plain decimal test for numeric answers stored in text columns

Properties

$aliasIndex  : array<string, string>
$executed  : bool
$filters  : StatisticsResponseFilters|null
$medianRequests  : array<string, array{field: string, countAlias: string}>
Median cannot be expressed as a one-scan conditional aggregate, so these run as one ordered LIMIT/OFFSET query per field after the main pass.
$requests  : array<string, array{kind: string, field: string, value: string}>
$results  : array<string, int|float>
$surveyId  : int

Methods

__construct()  : mixed
countAnyNonEmpty()  : string
Count of rows where at least one of the given columns is neither NULL nor empty
countBlank()  : string
Count of rows where the column is NULL or empty.
countJsonArrayValue()  : string
Count of rows where the JSON-array column holds the given value at the given zero-based position (rankings store the whole answer as one JSON array of codes, the element index being the rank).
countNonEmpty()  : string
Count of rows where the column is neither NULL nor empty.
countNumeric()  : string
Count of rows whose cell holds a numeric value. For numeric columns this equals countNonEmpty(); for text columns non-numeric cells are excluded.
countTotal()  : string
Total row count (with the response filters applied).
countValue()  : string
Count of rows where the column equals the given value.
execute()  : void
Execute all registered aggregates over the responses table.
isExecuted()  : bool
maxValue()  : string
Largest numeric value in a column (empty cells ignored).
medianValue()  : string
minValue()  : string
Smallest numeric value in a column (empty cells ignored).
sumSquares()  : string
Sum of the squared numeric values in a column (non-numeric/empty cells count as 0). Combined with sumValues() and countNonEmpty() this yields the population variance/standard deviation without a second scan.
sumValues()  : string
Sum of the numeric values in a column (non-numeric/empty cells count as 0). Combined with countNonEmpty() this yields a column mean. The result preserves up to 4 decimal places, so fractional answers (e.g. 1.5) are not lost.
value()  : int|float
Resolved value for a previously registered alias (0 before execute()).
buildExpression()  : string
buildMedianSelect()  : string
buildSearchConditions()  : array<string|int, string>
One condition group per search term: the term must appear (case-insensitively) in at least one free-text answer column; the terms themselves combine with AND. A survey without any text answer column cannot match a term, so the whole filter becomes FALSE.
buildWhere()  : string
executeMedians()  : void
Median via an ordered LIMIT/OFFSET sub-select: the middle value, or the mean of the two middle values for even counts. There is no portable single-scan SQL median, but the per-field sub-selects are merged into chunked UNION ALL statements so resolving N fields (e.g. every cell of an array question) does not cost N round trips.
getDb()  : CDbConnection
getSearchableColumns()  : array<string|int, string>
isJsonColumn()  : bool
jsonArrayElementCheck()  : string
Predicate testing the element at a zero-based position of a JSON-array column against a value. Extracting in SQL keeps per-rank counts inside the shared single scan instead of pulling every row into PHP. The merged query must survive cells that are not valid JSON (encrypted rankings live in plain text columns): MySQL/MSSQL guard per row with JSON_VALID()/ISJSON(); Postgres only gets the ->> operator when the column really is json/jsonb, because on a text column the operator itself is a hard SQL error.
nonEmptyCheck()  : string
Answered-cell predicate for a column. Numeric response columns (e.g.
numericCellCheck()  : string
Castable-cell predicate guarding every CAST(... AS DECIMAL). Numeric answers of array questions live in text columns which may hold non-numeric junk (API-submitted or legacy data); casting that fails the whole query on Postgres and coerces to 0 (skewing sums) on MySQL, so text columns get a per-driver numeric test.
register()  : string

Constants

MAX_EXPRESSIONS_PER_QUERY

Hard cap of SELECT expressions per query to bound per-row CASE cost

public mixed MAX_EXPRESSIONS_PER_QUERY = 1500

FIELD_SEPARATOR

Separator joining the columns of a multi-field aggregate into one field key

private mixed FIELD_SEPARATOR = "\x1e"

NUMERIC_PATTERN

Plain decimal test for numeric answers stored in text columns

private mixed NUMERIC_PATTERN = '^-?[0-9]*\\.?[0-9]+$'

Properties

$medianRequests

Median cannot be expressed as a one-scan conditional aggregate, so these run as one ordered LIMIT/OFFSET query per field after the main pass.

private array<string, array{field: string, countAlias: string}> $medianRequests = []

alias => request

$requests

private array<string, array{kind: string, field: string, value: string}> $requests = []

alias => request

$results

private array<string, int|float> $results = []

alias => count, or fractional sum for KIND_SUM

Methods

countAnyNonEmpty()

Count of rows where at least one of the given columns is neither NULL nor empty

public countAnyNonEmpty(array<string|int, string> $fields) : string
Parameters
$fields : array<string|int, string>
Return values
string

countBlank()

Count of rows where the column is NULL or empty.

public countBlank(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

countJsonArrayValue()

Count of rows where the JSON-array column holds the given value at the given zero-based position (rankings store the whole answer as one JSON array of codes, the element index being the rank).

public countJsonArrayValue(string $field, int $position, string $value) : string
Parameters
$field : string
$position : int
$value : string
Return values
string

countNonEmpty()

Count of rows where the column is neither NULL nor empty.

public countNonEmpty(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

countNumeric()

Count of rows whose cell holds a numeric value. For numeric columns this equals countNonEmpty(); for text columns non-numeric cells are excluded.

public countNumeric(string $field[, bool $numericColumn = false ]) : string

Use as the denominator of mean/variance so junk cells cannot skew them.

Parameters
$field : string
$numericColumn : bool = false
Return values
string

countTotal()

Total row count (with the response filters applied).

public countTotal() : string
Return values
string

countValue()

Count of rows where the column equals the given value.

public countValue(string $field, string $value) : string
Parameters
$field : string
$value : string
Return values
string

maxValue()

Largest numeric value in a column (empty cells ignored).

public maxValue(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

medianValue()

public medianValue(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

minValue()

Smallest numeric value in a column (empty cells ignored).

public minValue(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

sumSquares()

Sum of the squared numeric values in a column (non-numeric/empty cells count as 0). Combined with sumValues() and countNonEmpty() this yields the population variance/standard deviation without a second scan.

public sumSquares(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

sumValues()

Sum of the numeric values in a column (non-numeric/empty cells count as 0). Combined with countNonEmpty() this yields a column mean. The result preserves up to 4 decimal places, so fractional answers (e.g. 1.5) are not lost.

public sumValues(string $field[, bool $numericColumn = false ]) : string
Parameters
$field : string
$numericColumn : bool = false
Return values
string

value()

Resolved value for a previously registered alias (0 before execute()).

public value(string $alias) : int|float

Counts are integers; KIND_SUM aggregates may be fractional.

Parameters
$alias : string
Return values
int|float

buildExpression()

private buildExpression(CDbConnection $db, array<string|int, mixed> $request) : string
Parameters
$db : CDbConnection
$request : array<string|int, mixed>
Return values
string

buildMedianSelect()

private buildMedianSelect(CDbConnection $db, string $table, array<string|int, mixed> $request, int $count, string $alias) : string
Parameters
$db : CDbConnection
$table : string
$request : array<string|int, mixed>
$count : int
$alias : string
Return values
string

buildSearchConditions()

One condition group per search term: the term must appear (case-insensitively) in at least one free-text answer column; the terms themselves combine with AND. A survey without any text answer column cannot match a term, so the whole filter becomes FALSE.

private buildSearchConditions(array<string|int, string> $terms) : array<string|int, string>
Parameters
$terms : array<string|int, string>
Return values
array<string|int, string>

executeMedians()

Median via an ordered LIMIT/OFFSET sub-select: the middle value, or the mean of the two middle values for even counts. There is no portable single-scan SQL median, but the per-field sub-selects are merged into chunked UNION ALL statements so resolving N fields (e.g. every cell of an array question) does not cost N round trips.

private executeMedians(CDbConnection $db, string $table) : void
Parameters
$db : CDbConnection
$table : string

getSearchableColumns()

private getSearchableColumns() : array<string|int, string>
Return values
array<string|int, string>

isJsonColumn()

private isJsonColumn(string $field) : bool
Parameters
$field : string
Return values
bool

jsonArrayElementCheck()

Predicate testing the element at a zero-based position of a JSON-array column against a value. Extracting in SQL keeps per-rank counts inside the shared single scan instead of pulling every row into PHP. The merged query must survive cells that are not valid JSON (encrypted rankings live in plain text columns): MySQL/MSSQL guard per row with JSON_VALID()/ISJSON(); Postgres only gets the ->> operator when the column really is json/jsonb, because on a text column the operator itself is a hard SQL error.

private jsonArrayElementCheck(CDbConnection $db, string $field, int $position, string $value) : string
Parameters
$db : CDbConnection
$field : string
$position : int
$value : string
Return values
string

nonEmptyCheck()

Answered-cell predicate for a column. Numeric response columns (e.g.

private nonEmptyCheck(CDbConnection $db, string $field, bool $numeric) : string

numerical input's DECIMAL column) must not be compared to '': Postgres rejects the comparison outright and MySQL coerces '' to 0, which would misclassify legitimate 0 answers as blank — for those, blank is NULL.

Parameters
$db : CDbConnection
$field : string
$numeric : bool
Return values
string

numericCellCheck()

Castable-cell predicate guarding every CAST(... AS DECIMAL). Numeric answers of array questions live in text columns which may hold non-numeric junk (API-submitted or legacy data); casting that fails the whole query on Postgres and coerces to 0 (skewing sums) on MySQL, so text columns get a per-driver numeric test.

private numericCellCheck(CDbConnection $db, string $field, bool $numeric) : string
Parameters
$db : CDbConnection
$field : string
$numeric : bool
Return values
string

register()

private register(string $kind, string $field, string $value[, bool $numeric = false ]) : string
Parameters
$kind : string
$field : string
$value : string
$numeric : bool = false
Return values
string

        
On this page

Search results