Try running requests on the grouping example data:
The Vespa grouping language is a list processing language which describes how the query hits should be grouped, aggregated and presented in result sets. A grouping statement takes the list of all matches to a query as input and groups/aggregates it, possibly in multiple nested and parallel ways to produce the output. This is a logical specification, and does not indicate how it is executed, as instantiating the list of all matches to the query somewhere would be too expensive, and execution is distributed instead.
Refer to the Query API reference for how to set the select parameter, and the Grouping reference for details. Fields used in grouping must be defined as attribute in the document schema. Grouping supports continuation objects for pagination.
The operations defining the structure of a grouping are:
all(statement)
: Execute the nested statement once on the input list as a whole.each(statement)
: Execute the nested statement on each element of the input list.group(specification)
:
Turn the input list into a list of list according to the grouping specification.output
: Output some value(s) at the current location in the structure.
The parallel and nested collection of these operations defines both the structure of the computation
and of the result it produces.
For example, all(group(customer) each(output(count())))
will take all matches, group them by customer id, and for each group output the count of hits in the group.
Vespa distributes and executes the grouping program on content nodes, and merges results on container nodes - in multiple phases, as needed. As realizing such programs over a distributed data set requires more network round-trips than a regular search query, these queries may be more expensive than regular queries - see defaultMaxGroups and the likes for how to control resource usage.
For the entirety of this document, assume an index of engine part purchases:
Date | Price | Tax | Item | Customer |
---|---|---|---|---|
2006-09-06 09:00:00 | $1 000 | 0.24 | Intake valve | Smith |
2006-09-07 10:00:00 | $1 000 | 0.12 | Rocker arm | Smith |
2006-09-07 11:00:00 | $2 000 | 0.24 | Spring | Smith |
2006-09-08 12:00:00 | $3 000 | 0.12 | Valve cover | Jones |
2006-09-08 10:00:00 | $5 000 | 0.24 | Intake port | Jones |
2006-09-08 11:00:00 | $8 000 | 0.12 | Head | Brown |
2006-09-09 12:00:00 | $1 300 | 0.24 | Coolant | Smith |
2006-09-09 10:00:00 | $2 100 | 0.12 | Engine block | Jones |
2006-09-09 11:00:00 | $3 400 | 0.24 | Oil pan | Brown |
2006-09-09 12:00:00 | $5 500 | 0.12 | Oil sump | Smith |
2006-09-10 10:00:00 | $8 900 | 0.24 | Camshaft | Jones |
2006-09-10 11:00:00 | $1 440 | 0.12 | Exhaust valve | Brown |
2006-09-10 12:00:00 | $2 330 | 0.24 | Rocker arm | Brown |
2006-09-10 10:00:00 | $3 770 | 0.12 | Spring | Brown |
2006-09-10 11:00:00 | $6 100 | 0.24 | Spark plug | Smith |
2006-09-11 12:00:00 | $9 870 | 0.12 | Exhaust port | Jones |
2006-09-11 10:00:00 | $1 597 | 0.24 | Piston | Brown |
2006-09-11 11:00:00 | $2 584 | 0.12 | Connection rod | Smith |
2006-09-11 12:00:00 | $4 181 | 0.24 | Rod bearing | Jones |
2006-09-11 13:00:00 | $6 765 | 0.12 | Crankshaft | Jones |
Example: Return the total sum of purchases per customer - steps:
/search/?yql=select * from sources * where true
all(...)
group(customer)
output(sum(price))
Final query, producing the sum of the price of all purchases for each customer:
select * from purchase where true limit 0 | all( group(customer) each(output(sum(price))) )
/search/?yql=select * from sources * where true limit 0 | all( group(customer) each(output(sum(price))) )
Here, limit is set to zero to get the grouping output only. URL encoded equivalent:
/search/?yql=select%20%2A%20from%20sources%20%2A%20where%20true%20limit%200%20%7C%20 all%28%20group%28customer%29%20each%28output%28sum%28price%29%29%29%20%29
Result:
GroupId | Sum(price) |
---|---|
Brown | $20 537 |
Jones | $39 816 |
Smith | $19 484 |
Example: Sum price of purchases per date:
select (…) | all(group(time.date(date)) each(output(sum(price))))
GroupId | Sum(price) |
---|---|
2006-09-06 | $1 000 |
2006-09-07 | $3 000 |
2006-09-08 | $16 000 |
2006-09-09 | $12 300 |
2006-09-10 | $22 540 |
2006-09-11 | $24 997 |
Note: in examples above, all documents are evaluated. Modify the query to add filters (and thus cut latency), like (remember to URL encode):
/search/?yql=select * from sources * where customer contains "smith"
In many scenarios, a large collection of groups is produced, possibly too large to display or process. This is handled by ordering groups, then limiting, the number of groups to return.
The order
clause accepts a list of one or more expressions.
Each of the arguments to order
is prefixed by either a plus/minus for ascending/descending order.
Limit the number of groups using max
and precision
-
the latter is the number of groups returned per content node to be merged to the global result.
Larger document distribution skews hence require a higher precision
for accurate results.
An implicit limit can be specified through the
grouping.defaultMaxGroups query parameter.
This value will always be overridden if max
explicitly specified in the query.
Use max(inf)
to retrieve all groups when the query parameter is set.
If precision
is not specified, it will default to a factor times max
.
This factor can be overridden through the
grouping.defaultPrecisionFactor
query parameter.
Example: To find the 2 globally best groups, make an educated guess on how
many samples are needed to fetch from each node in order to get the right groups.
This is the precision
.
An initial factor of 3 has proven to be quite good in most use cases.
If however the data for customer 'Jones' was spread on 3 different content nodes,
'Jones' might be among the 2 best on only one node.
But based on the distribution of the data,
we have concluded by earlier tests that if we fetch 5.67 as many groups as we need to,
we will have a correct answer with at least 99.999% confidence.
So then we just use 6 times as many groups when doing the merge.
However, there is one exception.
Without an order
constraint, precision
is not required.
Then local ordering will be the same as global ordering.
Ordering will not change after a merge operation.
Example: The two customers with most purchases, returning the sum for each:
select (…) | all(group(customer) max(2) precision(12) order(-count()) each(output(sum(price))))
GroupId | sum(price) |
---|---|
Jones | $39 816 |
Smith | $19 484 |
Use summary
to print the fields for a hit,
and max
to limit the number of hits per group.
An implicit limit can be specified through the
grouping.defaultMaxHits query parameter.
This value will always be overridden if max
explicitly specified in the query.
Use max(inf)
to retrieve all hits when the query parameter is set.
Example: Return the three most expensive parts per customer:
/search/?yql=select * from sources * where true | all(group(customer) each(max(3) each(output(summary())))) &ranking=pricerank
GroupId | |||||
---|---|---|---|---|---|
Brown | |||||
Date | Price | Tax | Item | Customer | |
2006-09-08 11:00 | $8 000 | 0.12 | Head | Brown | |
2006-09-10 10:00 | $3 770 | 0.12 | Spring | Brown | |
2006-09-09 11:00 | $3 400 | 0.24 | Oil pan | Brown | |
Jones | |||||
Date | Price | Tax | Item | Customer | |
2006-09-11 12:00 | $9 870 | 0.12 | Exhaust port | Jones | |
2006-09-10 10:00 | $8 900 | 0.24 | Camshaft | Jones | |
2006-09-11 13:00 | $6 765 | 0.12 | Crankshaft | Jones | |
Smith | |||||
Date | Price | Tax | Item | Customer | |
2006-09-10 11:00 | $6 100 | 0.24 | Spark plug | Smith | |
2006-09-09 12:00 | $5 500 | 0.12 | Oil sump | Smith | |
2006-09-11 11:00 | $2 584 | 0.12 | Connection rod | Smith |
Notes on ordering in the example above:
order
clause is a directive for group ordering, not hit ordering.
Here, there is no order clause on the groups, so default ordering -max(relevance())
is used. The -
denotes the sorting order, - means descending (higher score first).
In this case, the query is "all documents", so all groups are equally relevant and the group order is random.
ranking=pricerank
to the query
to use the pricerank rank profile to rank by price:
rank-profile pricerank inherits default { first-phase { expression: attribute(price) } }
The following query may return 5 groups and 0 hits.
It will be rejected when grouping.globalMaxGroups < 5
select (…) | all(group(a) max(5) each(output(count())))
The following query may return 5 groups and 35 hits.
It will be rejected when grouping.globalMaxGroups < 5+5*7
.
select (…) | all(group(a) max(5) each(output(count()) max(7) each(output(summary()))))
The following query may return 6 groups and 30 hits.
It will be rejected when grouping.globalMaxGroups < 2*(3+3*5)
.
select (…) | all( all(group(a) max(3) each(output(count()) max(5) each(output(summary())))) all(group(b) max(3) each(output(count()) max(5) each(output(summary())))))
The grouping.globalMaxGroups
restriction will utilize the
grouping.defaultMaxGroups/
grouping.defaultMaxHits
values for grouping statements without a max
. The two queries below are identical assuming
defaultMaxGroups=5
and defaultMaxHits=7
, and both will be rejected when
globalMaxGroups < 5+5*7
.
select (…) | all(group(a) max(5) each(output(count()) max(7) each(output(summary())))) select (…) | all(group(a) each(output(count()) each(output(summary()))))
A grouping without max
combined with defaultMaxGroups=-1
/defaultMaxHits=-1
will be rejected unless globalMaxGroups=-1
. This is because the query produces an unbounded result,
infinite number of groups if defaultMaxGroups=-1
or infinite number of summaries if
defaultMaxHits=-1
.
An unintentional DoS (Denial of Service) could be the utter consequence if a query returns thousands of groups and summaries.
This is why setting globalMaxGroups=-1
is risky.
The best practice is to always specify max
in groupings,
making it easy to reason about the worst-case cardinality of the query results. The performance will also benefit.
Set globalMaxGroups
to the overall worst-case result cardinality with some margin.
The defaultMaxGroups
/defaultMaxHits
should be overridden in a query profile if some groupings do not use max
and the default values are too low.
<query-profile id="default"> <field name="grouping.defaultMaxGroups">20</field> <field name="grouping.defaultMaxHits">100</field> <field name="grouping.globalMaxGroups">8000</field> </query-profile>
Grouping is by default tuned to favour performance over correctness.
Perfect correctness may not be achievable; result of queries using non-default ordering
can be approximate and correctness can only be partially achieved by a larger precision
value that sacrifices performance.
The grouping session cache is enabled by default.
Disabling it will improve correctness, especially for queries using order
and max
.
The cost of multi-level grouping expressions will though increase.
Consider increasing the precision value when using max
in combination with order
.
The default precision may not achieve the required correctness for your use-case.
Groups can be nested. This offers great drilling capabilities, as there are no limits to nesting depth or presented information on any level. Example: How much each customer has spent per day by grouping on customer, then date:
select (…) | all(group(customer) each(group(time.date(date)) each(output(sum(price)))))
GroupId | ||
---|---|---|
Brown | ||
GroupId | Sum(price) | |
2006-09-08 | $8 000 | |
2006-09-09 | $3 400 | |
2006-09-10 | $7 540 | |
2006-09-11 | $1 597 | |
Jones | ||
GroupId | Sum(price) | |
2006-09-08 | $8 000 | |
2006-09-09 | $2 100 | |
2006-09-10 | $8 900 | |
2006-09-11 | $20 816 | |
Smith | ||
GroupId | Sum(price) | |
2006-09-06 | $1 000 | |
2006-09-07 | $3 000 | |
2006-09-09 | $6 800 | |
2006-09-10 | $6 100 | |
2006-09-11 | $2 584 |
Use this to query for all items on a per-customer basis, displaying the most expensive hit for each customer, with subgroups of purchases on a per-date basis. Use the summary clause to show hits inside any group at any nesting level. Include the sum price for each customer, both as a grand total and broken down on a per-day basis:
/search/?yql=select * from sources * where true| all(group(customer) each(max(1) output(sum(price)) each(output(summary()))) each(group(time.date(date)) each(max(10) output(sum(price)) each(output(summary()))))) &ranking=pricerank
GroupId | sum(price) | |||||
---|---|---|---|---|---|---|
Brown | $20 537 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-08 11:00 | $8 000 | 0.12 | Head | Brown | ||
GroupId | Sum(price) | |||||
2006-09-08 | $8 000 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-08 11:00 | $8 000 | 0.12 | Head | Brown | ||
2006-09-09 | $3 400 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-09 11:00 | $3 400 | 0.12 | Oil pan | Brown | ||
2006-09-10 | $7 540 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-10 10:00 | $3 770 | 0.12 | Spring | Brown | ||
2006-09-10 12:00 | $2 330 | 0.24 | Rocker arm | Brown | ||
2006-09-10 11:00 | $1 440 | 0.12 | Exhaust valve | Brown | ||
2006-09-11 | $1 597 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-11 10:00 | $1 597 | 0.24 | Piston | Brown | ||
Jones | $39 816 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-11 12:00 | $9 870 | 0.12 | Exhaust port | Jones | ||
GroupId | Sum(price) | |||||
2006-09-08 | $8 000 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-08 10:00 | $5 000 | 0.24 | Intake port | Jones | ||
2006-09-08 12:00 | $3 000 | 0.12 | Valve cover | Jones | ||
2006-09-09 | $2 100 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-09 10:00 | $2 100 | 0,12 | Engine block | Jones | ||
2006-09-10 | $8 900 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-10 10:00 | $8 900 | 0.24 | Camshaft | Jones | ||
2006-09-11 | $20 816 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-11 12:00 | $9 870 | 0.12 | Exhaust port | Jones | ||
2006-09-11 13:00 | $6 765 | 0.12 | Crankshaft | Jones | ||
2006-09-11 12:00 | $4 181 | 0.24 | Rod bearing | Jones | ||
Smith | $19 484 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-10 11:00 | $6 100 | 0.24 | Spark plug | Smith | ||
GroupId | Sum(price) | |||||
2006-09-06 | $1 000 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-06 09:00 | $1 000 | 0.24 | Intake valve | Smith | ||
2006-09-07 | $3 000 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-07 11:00 | $2 000 | 0.24 | Spring | Smith | ||
2006-09-07 10:00 | $1 000 | 0.12 | Rocker arm | Smith | ||
2006-09-09 | $6 800 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-09 12:00 | $5 500 | 0.12 | Oil sump | Smith | ||
2006-09-09 12:00 | $1 300 | 0.24 | Coolant | Smith | ||
2006-09-10 | $6 100 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-10 11:00 | $6 100 | 0.24 | Spark plug | Smith | ||
2006-09-11 | $2 584 | |||||
Date | Price | Tax | Item | Customer | ||
2006-09-11 11:00 | $2 584 | 0.12 | Connection rod | Smith |
Structured grouping is nested grouping over array of struct or maps.
In examples above, results are grouped on distinct values, like customer or date. To group on price:
select (…) | all(group(price) each(each(output(summary()))))
This gives one group per price. To group on price ranges, one could compress the price range. This gives prices in $0 - $999 in bucket 0, $1 000 - $2 000 in bucket 1 and so on:
select (…) | all(group(price/1000) each(each(output(summary()))))
An alternative is using bucket expressions - think of a bucket as the range per group. Group on price, make groups have a width of 1000:
select (…) | all(group(fixedwidth(price,1000)) each(each(output(summary()))))
Use predefined
to configure group sizes individually (the two below are equivalent):
select (…) | all(group(predefined(price, bucket(0,1000), bucket(1000,2000), bucket(2000,5000), bucket(5000,inf))) each(each(output(summary())))) select (…) | all(group(predefined(price, bucket[0,1000>, bucket[1000,2000>, bucket[2000,5000>, bucket[5000,inf>)) each(each(output(summary()))))
This works with strings as well - put Jones and Smith in the second group:
select (…) | all(group(predefined(customer, bucket(-inf,"Jones"), bucket("Jones", inf))) each(each(output(summary()))))
... or have Jones in his own group:
select (…) | all(group(predefined(customer, bucket<-inf,"Jones">, bucket["Jones"], bucket<"Jones", inf>)) each(each(output(summary()))))
Use decimal numbers in bucket definitions if the expression evaluates to a double or float:
select (…) | all(group(predefined(tax, bucket[0.0,0.2>, bucket[0.2,0.5>, bucket[0.5,inf>)) each(each(output(summary()))))
Grouping supports continuation objects
that are passed as annotations to the grouping statement.
The continuations
annotation is a list of zero or more continuation strings,
returned in the grouping result.
For example, given the result:
reproduce the same result by passing the this-continuation along the original select:
select (…) | { 'continuations':['BGAAABEBCA'] }all(…)
To display the next page of customers, pass the this-continuation of the root group, and the next continuation of the customer list:
select (…) | { 'continuations':['BGAAABEBCA', 'BGAAABEBEBC'] }all(…)
To display the previous page of customers, pass the this-continuation of the root group, and the prev continuation of the customer list:
select (…) | { 'continuations':['BGAAABEBCA', 'BGAAABEABC'] }all(…)
The continuations
annotation is an ordered list of continuation strings.
These are combined by replacement,
so that a continuation given later will replace any shared state with a continuation given before.
Also, when using the continuations
annotation,
always pass the this-continuation as its first element.
Instead of just grouping on some attribute value,
the group
clause may contain arbitrarily complex expressions -
see group
in the
grouping reference for an exhaustive list.
Examples:
Sum the prices of purchases on per-hour-of-day basis:
select (…) | all(group(mod(div(date,mul(60,60)),24)) each(output(sum(price))))
GroupId | sum(price) |
---|---|
09:00 | $1 000 |
10:00 | $22 367 |
11:00 | $23 524 |
12:00 | $26 181 |
13:00 | $6 765 |
These types of expressions may also be used inside output
operations,
so instead of simply calculating the sum price of the grouped purchases,
calculate the sum income after taxes per customer:
select (…) | all(group(customer) each(output(sum(mul(price,sub(1,tax))))))
GroupId | sum(mul(price,sub(1,tax))) |
---|---|
Brown | $17 193 |
Jones | $32 868 |
Smith | $15 897 |
Note that the validity of an expression depends on the current nesting level.
E.g. while sum(price)
would be a valid expression for a group of hits, price
would not.
As a general rule, each operator within an expression either applies to a single hit or aggregates values across a group.
As an alternative to a textual representation,
one can use the programmatic API to execute grouping requests.
This allows multiple grouping requests to run in parallel,
and does not collide with the yql
parameter - example:
Refer to the API documentation for the complete reference.
Simple grouping, count the number of documents in each group:
all( group(a) each(output(count())) )
Two parallel groupings:
all( all(group(a) each(output(count()))) all(group(b) each(output(count()))) )
Only the 1000 best hits will be grouped at each content node. Lower accuracy, but higher speed:
all( max(1000) all(group(a) each(output(count()))) )
Do a modulo 5 operation before selecting the group:
all( group(a % 5) each(output(count())) )
Do a + b * c
before selecting the group:
all( group(a + b * c) each(output(count())) )
Do a modulo 5 operation before selecting the group - the groups are then ordered by their aggregated sum of attribute "b":
all( group(a % 5) order(sum(b)) each(output(count())) )
Do a + b * c
before selecting the group.
Ordering is given by the maximum value of attribute "d" in each group:
all( group(a + b * c) order(max(d)) each(output(count())) )
Take the average relevance of the groups and multiply it with the number of groups to get a cumulative count:
all( group(a) order(avg(relevance()) * count()) each(output(count())) )
One can not directly reference an attribute in the order clause, as this:
all(group(a) order(attr * count()) each(output(count())) )
However, one can do this:
all(group(a) order(max(attr) * count()) each(output(count())) )
Simple grouping to count number of documents in each group and return the best hit in each group:
all( group(a) each(max(1) each(output(summary()))) )
Also return the sum of attribute "b":
all( group(a) each(max(1) output(count(), sum(b)) each(output(summary()))) )
Also return an XOR of the 64 most significant bits of an MD5 over the concatenation of attributes "a", "b" and "c":
all(group(a) each(max(1) output(count(), sum(b), xor(md5(cat(a, b, c), 64))) each(output(summary()))))
Single level grouping on "a" attribute, returning at most 5 groups with full hit count as well as the 69 best hits.
all( group(a) max(5) each(max(69) output(count()) each(output(summary()))) )
Two level grouping on "a" and "b" attribute:
all( group(a) max(5) each(output(count()) all(group(b) max(5) each(max(69) output(count()) each(output(summary()))))) )
Three level grouping on "a", "b" and "c" attribute:
all( group(a) max(5) each(output(count()) all(group(b) max(5) each(output(count()) all(group(c) max(5) each(max(69) output(count()) each(output(summary()))))) )
As above, but also collect best hit in level 2:
all( group(a) max(5) each(output(count()) all(group(b) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(c) max(5) each(max(69) output(count()) each(output(summary()))))) )
As above, but also collect best hit in level 1:
all( group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(c) max(5) each(max(69) output(count()) each(output(summary()))))) )
As above, but using different document summaries on each level:
all( group(a) max(5) each(output(count()) all(max(1) each(output(summary(complexsummary)))) all(group(b) max(5) each(output(count()) all(max(1) each(output(summary(simplesummary)))) all(group(c) max(5) each(max(69) output(count()) each(output(summary(fastsummary)))))) )
Deep grouping with counting and hit collection on all levels:
all( group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))) )
The field (a
below, but can have any name)
must be a long, with second resolution (unix timestamp/epoch).
See the reference for all time-functions.
Group by year:
all( group(time.year(a)) each(output(count())) )
Group by year, then by month:
all( group(time.year(a)) each(output(count()) all(group(time.month(a)) each(output(count())))) )
Group by year, then by month, then day, then by hour:
all( group(time.year(a)) each(output(count()) all(group(time.monthofyear(a)) each(output(count()) all(group(time.dayofmonth(a)) each(output(count()) all(group(time.hourofday(a)) each(output(count())))))))) )
Groups today, yesterday, lastweek, and lastmonth
using predefined
aggregator, and groups each day within each of these separately:
all( group(predefined((now() - a) / (60 * 60 * 24), bucket(0,1), bucket(1,2), bucket(3,7), bucket(8,31))) each(output(count()) all(max(2) each(output(summary()))) all(group((now() - a) / (60 * 60 * 24)) each(output(count()) all(max(2) each(output(summary())))))) )
The count
aggregator can be applied on list of groups to determine the number of unique groups
without having to explicitly retrieve all groups.
Note that this count is an estimate using HyperLogLog++ which is an algorithm for the count-distinct problem.
To get an accurate count one needs to explicitly retrieve all groups
and count them in a custom component or in the middle tier calling out to Vespa.
This is network intensive and might not be feasible in cases with many unique groups.
Another use case for this aggregator is counting the number of unique instances matching a given expression.
Output an estimate of the number of groups, which is equivalent to the number of unique values for attribute "a":
all( group(a) output(count()) )
Output an estimate of the number of unique string lengths for the attribute "name":
all( group(strlen(name)) output(count()) )
Output the sum of the "b" attribute for each group in addition to the accurate count of the overall number of unique groups as the inner each causes all groups to be returned.
all( group(a) output(count()) each(output(sum(b))) )
The max
clause is used to restrict the number of groups returned.
The query outputs the sum for the 3 best groups.
The count
clause outputs the estimated number of groups (potentially >3).
The count
becomes an estimate here as the number of groups is limited by max,
while in the above example it's not limited by max:
all( group(a) max(3) output(count()) each(output(sum(b))) )
Output the number of top level groups, and for the 10 best groups, output the number of unique values for attribute "b":
all( group(a) max(10) output(count()) each(group(b) output(count())) )
Using impression logs for a given user, one can make a function that maps from rank score to the number of impressions an advertisement would get - example:
Score Integer (# impressions for this user) 0.200 0 0.210 1 0.220 2 0.240 3 0.320 4 0.420 5 0.560 6 0.700 7 0.800 8 0.880 9 0.920 10 0.940 11 0.950 12
Storing just the first column (the rank scores, including a rank score for 0 impressions) in an array attribute named impressions, the grouping operation interpolatedlookup(impressions, relevance()) can be used to figure out how many times a given advertisement would have been shown to this particular user.
So if the rank score is 0.420 for a specific user/ad/bid combination,
then interpolatedlookup(impressions,relevance())
would return 5.0.
If the bid is increased so the rank score gets to 0.490,
it would get 5.5 as the return value instead.
In this context a count of 5.5 isn't meaningful for the past of a single user, but it gives more information that may be used as a forecast. Summing this across more, different users may then be used to forecast the total of future impressions for the advertisement.
Grouping is useful to analyse data. To aggregate over the full document set, create one group (which will have all documents) by using a constant (here 1) - example:
$ vespa query 'select rating from restaurant where true | all(group(1) each(output(avg(rating))))' \ hits=0 \ ranking=unranked
Make sure all documents have a value for the given field, if not, NaN is used and the final result is also NaN:
Count number of documents missing a value for an attribute field (actually, in this example, unset or less than 0, see the bucket expression below). Set a higher query timeout just in case. Example, analyzing a field called rating:
$ vespa query \ 'select rating from restaurant where true | all( group(predefined(rating, bucket[-inf, 0>, bucket[0, inf>)) each(output(count())) )' \ hits=0 \ ranking=unranked \ timeout=60s
Example output, counting 2 documents with -inf
in rating:
See analyzing field values for how to export ids of documents meeting given criteria from the full corpus.
This is similar to the counting of NaN above, but instead of aggregating the count, for each hit, print a document summary:
$ vespa query \ 'select rating from restaurant where true | all( group(predefined(rating, bucket[-inf, 0>, bucket[0, inf>)) order(max(rating)) max(1) each( max(100) each(output(summary(name_only)))) )' \ hits=0 \ ranking=unranked \ timeout=60s
Notes:
max(rating)
and use max(1)
to get only the first
max(100)
in order to limit result set sizes.
Read more about grouping.defaultmaxhits.
document-summary name_only { summary name {} }In the query, use the class name in
each(output(summary(name_only)))
.