From 5d66d4dae05451032988a043c65d2f261fce4652 Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 19 May 2023 10:29:41 +0200 Subject: [PATCH 001/116] Adapt terminology to the multi-parent case --- .../odata-data-aggregation-ext.html | 38 ++++++++++--------- .../odata-data-aggregation-ext.md | 8 ++-- .../5 Vocabulary for Data Aggregation.md | 8 ++-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 8a093770b..68f1d1eda 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -3925,9 +3925,9 @@

2 segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy.
  • The ParentNavigationProperty allows navigation to the -instance representing the parent node. It MUST be a nullable single- or -collection-valued navigation property path that addresses the entity -type annotated with this term.
  • +instance or instances representing the parent nodes. It MUST be a +nullable single- or collection-valued navigation property path that +addresses the entity type annotated with this term.

    The term RecursiveHierarchy can only be applied to entity types, and MUST be applied with a qualifier, which is used to @@ -3936,22 +3936,24 @@

    2 rolluprecursive, and in hierarchy functions.

    A node is an instance of an entity type annotated with -RecursiveHierarchy. It may have a parent node -that is the target instance reached via the -ParentNavigationProperty. A -recursive hierarchy is a collection of such nodes with +RecursiveHierarchy and a given qualifier. The same entity +can be different nodes, given different qualifiers. A node may have one +or more parent nodes that are the target instances reached via +the ParentNavigationProperty. A recursive +hierarchy for a given qualifier is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links.

    -

    A node without parent node is a root node, a node is a -child node of its parent node, a node without child nodes is a -leaf node. Nodes with the same parent node are sibling -nodes and so are root nodes. The descendants of a node are -its child nodes, their child nodes, and so on, up to and including all -leaf nodes that can be reached. A node together with its descendants -forms a sub-hierarchy of the hierarchy. The ancestors -of a node are its parent node, the parent of its parent node, and so on, -up to and including a root node that can be reached. A recursive -hierarchy can have one or more root nodes.

    +

    A node without parent node or with null as parent node is a root +node, a node is a child node of its parent nodes, a node +without child nodes is a leaf node. Nodes with a common parent +node are sibling nodes and so are root nodes. The +descendants of a node are its child nodes, their child nodes, +and so on, up to and including all leaf nodes that can be reached. A +node together with its descendants forms a sub-hierarchy of the +hierarchy. The ancestors of a node are its parent nodes, the +parents of its parent nodes, and so on, up to and including root nodes +that can be reached. A recursive hierarchy can have one or more root +nodes.

    The term UpNode can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with UpNode and so on until a path to the root is @@ -3991,7 +3993,7 @@

    2 IncludeSelf is true
  • issibling tests if the given entity and another entity (whose node identifier is given in a parameter Other) have -the same parent node or both are roots, but are not the same
  • +a common parent node or both are roots, but are not the same
  • isleaf tests if the given entity is without descendants.
  • diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 5e87a74ff..5218ba0bb 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2364,13 +2364,13 @@ The term `LeveledHierarchy` MUST be applied with a qualifier that can be used to A recursive hierarchy organizes entities of a collection as nodes of one or more tree structures. This structure does not need to be as uniform as a leveled hierarchy. It is described by a complex term `RecursiveHierarchy` with these properties: - The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. -- The `ParentNavigationProperty` allows navigation to the instance representing the parent node. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). -A _node_ is an instance of an entity type annotated with `RecursiveHierarchy`. It may have a `parent node` that is the target instance reached via the `ParentNavigationProperty`. A `recursive hierarchy` is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links. +A _node_ is an instance of an entity type annotated with `RecursiveHierarchy` and a given qualifier. The same entity can be different nodes, given different qualifiers. A node may have one or more _parent nodes_ that are the target instances reached via the `ParentNavigationProperty`. A _recursive hierarchy_ for a given qualifier is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links. -A node without parent node is a _root node_, a node is a _child node_ of its parent node, a node without child nodes is a _leaf node_. Nodes with the same parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent node, the parent of its parent node, and so on, up to and including a root node that can be reached. A recursive hierarchy can have one or more root nodes. +A node without parent node or with null as parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Nodes with a common parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to the root is constructed. @@ -2386,7 +2386,7 @@ The following functions are defined: - `isroot` tests if the given entity is a root of the hierarchy - `isdescendant` tests if the given entity is a descendant of an ancestor node (whose node identifier is given in a parameter `Ancestor`) with a maximum distance `MaxDistance`, or equals the ancestor if `IncludeSelf` is true - `isancestor` tests if the given entity is an ancestor of a descendant node (whose node identifier is given in a parameter `Descendant`) with a maximum distance `MaxDistance`, or equals the descendant if `IncludeSelf` is true -- `issibling` tests if the given entity and another entity (whose node identifier is given in a parameter `Other`) have the same parent node or both are roots, but are not the same +- `issibling` tests if the given entity and another entity (whose node identifier is given in a parameter `Other`) have a common parent node or both are roots, but are not the same - `isleaf` tests if the given entity is without descendants. ### 5.5.3 Hierarchy Examples diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index 2ff8865c1..8653625cc 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -167,13 +167,13 @@ The term `LeveledHierarchy` MUST be applied with a qualifier that can be used to A recursive hierarchy organizes entities of a collection as nodes of one or more tree structures. This structure does not need to be as uniform as a leveled hierarchy. It is described by a complex term `RecursiveHierarchy` with these properties: - The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. -- The `ParentNavigationProperty` allows navigation to the instance representing the parent node. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). -A _node_ is an instance of an entity type annotated with `RecursiveHierarchy`. It may have a `parent node` that is the target instance reached via the `ParentNavigationProperty`. A `recursive hierarchy` is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links. +A _node_ is an instance of an entity type annotated with `RecursiveHierarchy` and a given qualifier. The same entity can be different nodes, given different qualifiers. A node may have one or more _parent nodes_ that are the target instances reached via the `ParentNavigationProperty`. A _recursive hierarchy_ for a given qualifier is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links. -A node without parent node is a _root node_, a node is a _child node_ of its parent node, a node without child nodes is a _leaf node_. Nodes with the same parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent node, the parent of its parent node, and so on, up to and including a root node that can be reached. A recursive hierarchy can have one or more root nodes. +A node without parent node or with null as parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Nodes with a common parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to the root is constructed. @@ -189,7 +189,7 @@ The following functions are defined: - `isroot` tests if the given entity is a root of the hierarchy - `isdescendant` tests if the given entity is a descendant of an ancestor node (whose node identifier is given in a parameter `Ancestor`) with a maximum distance `MaxDistance`, or equals the ancestor if `IncludeSelf` is true - `isancestor` tests if the given entity is an ancestor of a descendant node (whose node identifier is given in a parameter `Descendant`) with a maximum distance `MaxDistance`, or equals the descendant if `IncludeSelf` is true -- `issibling` tests if the given entity and another entity (whose node identifier is given in a parameter `Other`) have the same parent node or both are roots, but are not the same +- `issibling` tests if the given entity and another entity (whose node identifier is given in a parameter `Other`) have a common parent node or both are roots, but are not the same - `isleaf` tests if the given entity is without descendants. ### ##subsubsec Hierarchy Examples From 09e99292cb489c9e215d9488020377239dfc0d85 Mon Sep 17 00:00:00 2001 From: D024504 Date: Tue, 23 May 2023 12:37:44 +0200 Subject: [PATCH 002/116] Avoid the word tree --- .../odata-data-aggregation-ext.html | 30 ++++++++++--------- .../odata-data-aggregation-ext.md | 12 ++++---- .../5 Vocabulary for Data Aggregation.md | 12 ++++---- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 68f1d1eda..f55202d10 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -3916,10 +3916,17 @@

    2

    5.5.2 Recursive Hierarchy

    -

    A recursive hierarchy organizes entities of a collection as nodes of -one or more tree structures. This structure does not need to be as -uniform as a leveled hierarchy. It is described by a complex term -RecursiveHierarchy with these properties:

    +

    A recursive hierarchy is defined on a collection of entities by +associating with every entity zero or more other entities from the same +collection, called its parents. The entities are called +nodes and each node within the collection must be identifiable +through a single primitive property called the node +identifier.

    +

    A recursive hierarchy does not need to be as uniform as a leveled +hierarchy.

    +

    The recursive hierarchy is described in the model by an annotation of +the entity type with the complex term RecursiveHierarchy +with these properties:

    • The NodeProperty contains a path with single-valued segments ending in a primitive property. This path points to the @@ -3927,22 +3934,17 @@

      2
    • The ParentNavigationProperty allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that -addresses the entity type annotated with this term.
    • +addresses the entity type annotated with this term. There MUST NOT be +any cycles in the traversal of parent links.

    The term RecursiveHierarchy can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in grouping with rolluprecursive, and in hierarchy functions.

    -

    A node is an instance of an entity type annotated with -RecursiveHierarchy and a given qualifier. The same entity -can be different nodes, given different qualifiers. A node may have one -or more parent nodes that are the target instances reached via -the ParentNavigationProperty. A recursive -hierarchy for a given qualifier is a collection of such nodes with -unique node identifiers and no cycles in the traversal of parent -links.

    +href="#HierarchyFunctions">hierarchy functions. The same entity can +serve as different nodes in different recursive hierarchies, given +different qualifiers.

    A node without parent node or with null as parent node is a root node, a node is a child node of its parent nodes, a node without child nodes is a leaf node. Nodes with a common parent diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 5218ba0bb..ff845efda 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2362,13 +2362,15 @@ The term `LeveledHierarchy` MUST be applied with a qualifier that can be used to ### 5.5.2 Recursive Hierarchy -A recursive hierarchy organizes entities of a collection as nodes of one or more tree structures. This structure does not need to be as uniform as a leveled hierarchy. It is described by a complex term `RecursiveHierarchy` with these properties: -- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. -- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. +A recursive hierarchy is defined on a collection of entities by associating with every entity zero or more other entities from the same collection, called its _parents_. The entities are called _nodes_ and each node within the collection must be identifiable through a single primitive property called the _node identifier_. + +A recursive hierarchy does not need to be as uniform as a leveled hierarchy. -The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). +The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: +- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. -A _node_ is an instance of an entity type annotated with `RecursiveHierarchy` and a given qualifier. The same entity can be different nodes, given different qualifiers. A node may have one or more _parent nodes_ that are the target instances reached via the `ParentNavigationProperty`. A _recursive hierarchy_ for a given qualifier is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links. +The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. A node without parent node or with null as parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Nodes with a common parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index 8653625cc..43989082e 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -165,13 +165,15 @@ The term `LeveledHierarchy` MUST be applied with a qualifier that can be used to ### ##subsubsec Recursive Hierarchy -A recursive hierarchy organizes entities of a collection as nodes of one or more tree structures. This structure does not need to be as uniform as a leveled hierarchy. It is described by a complex term `RecursiveHierarchy` with these properties: -- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. -- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. +A recursive hierarchy is defined on a collection of entities by associating with every entity zero or more other entities from the same collection, called its _parents_. The entities are called _nodes_ and each node within the collection must be identifiable through a single primitive property called the _node identifier_. + +A recursive hierarchy does not need to be as uniform as a leveled hierarchy. -The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). +The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: +- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. -A _node_ is an instance of an entity type annotated with `RecursiveHierarchy` and a given qualifier. The same entity can be different nodes, given different qualifiers. A node may have one or more _parent nodes_ that are the target instances reached via the `ParentNavigationProperty`. A _recursive hierarchy_ for a given qualifier is a collection of such nodes with unique node identifiers and no cycles in the traversal of parent links. +The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. A node without parent node or with null as parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Nodes with a common parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. From 23d69c84b01e01aea27b3831563979032987ea60 Mon Sep 17 00:00:00 2001 From: D024504 Date: Tue, 23 May 2023 13:55:51 +0200 Subject: [PATCH 003/116] Root means zero parents --- .../odata-data-aggregation-ext.html | 27 +++++++++---------- .../odata-data-aggregation-ext.md | 4 +-- .../5 Vocabulary for Data Aggregation.md | 4 +-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index f55202d10..6867bc993 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -3933,9 +3933,9 @@

    2 property holding the node identifier of the node in the hierarchy.
  • The ParentNavigationProperty allows navigation to the instance or instances representing the parent nodes. It MUST be a -nullable single- or collection-valued navigation property path that -addresses the entity type annotated with this term. There MUST NOT be -any cycles in the traversal of parent links.
  • +collection-valued or nullable single-valued navigation property path +that addresses the entity type annotated with this term. There MUST NOT +be any cycles in the traversal of parent links.

    The term RecursiveHierarchy can only be applied to entity types, and MUST be applied with a qualifier, which is used to @@ -3945,17 +3945,16 @@

    2 href="#HierarchyFunctions">hierarchy functions. The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers.

    -

    A node without parent node or with null as parent node is a root -node, a node is a child node of its parent nodes, a node -without child nodes is a leaf node. Nodes with a common parent -node are sibling nodes and so are root nodes. The -descendants of a node are its child nodes, their child nodes, -and so on, up to and including all leaf nodes that can be reached. A -node together with its descendants forms a sub-hierarchy of the -hierarchy. The ancestors of a node are its parent nodes, the -parents of its parent nodes, and so on, up to and including root nodes -that can be reached. A recursive hierarchy can have one or more root -nodes.

    +

    A node without parent node is a root node, a node is a +child node of its parent nodes, a node without child nodes is a +leaf node. Two nodes with a common parent node are sibling +nodes and so are two root nodes. The descendants of a node +are its child nodes, their child nodes, and so on, up to and including +all leaf nodes that can be reached. A node together with its descendants +forms a sub-hierarchy of the hierarchy. The ancestors +of a node are its parent nodes, the parents of its parent nodes, and so +on, up to and including root nodes that can be reached. A recursive +hierarchy can have one or more root nodes.

    The term UpNode can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with UpNode and so on until a path to the root is diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index ff845efda..20b906c31 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2368,11 +2368,11 @@ A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: - The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. -- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. -A node without parent node or with null as parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Nodes with a common parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. +A node without parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to the root is constructed. diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index 43989082e..cd97fa110 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -171,11 +171,11 @@ A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: - The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. -- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a nullable single- or collection-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. -A node without parent node or with null as parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Nodes with a common parent node are _sibling nodes_ and so are root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. +A node without parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to the root is constructed. From 890b53b8f52d1ba59734ec7de0cbd7b827d02f3f Mon Sep 17 00:00:00 2001 From: D024504 Date: Tue, 23 May 2023 14:05:45 +0200 Subject: [PATCH 004/116] Subhierarchies have different roots. --- .../odata-data-aggregation-ext.html | 4 +++- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md | 2 +- odata-data-aggregation-ext/6 Hierarchical Transformations.md | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 6867bc993..d29032149 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -4236,7 +4236,9 @@

    2 href="#HierarchicalTransformationsPreservingtheInputSetStructure">Hierarchical Transformations Preserving the Input Set Structure or be service-defined bound functions whose output set is a subset of the -input set.

    +input set. The hierarchy \((H',Q)\) +can have different roots than the +hierarchy \((H,Q)\).

    6.2 Hierarchical Transformations Preserving the Input Set Structure

    diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 20b906c31..4049b9c97 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2586,7 +2586,7 @@ The recursive hierarchy is defined by a parameter pair $(H,Q)$, where $H$ and $Q The third parameter MUST be a data aggregation path $p$ with single- or collection-valued segments whose last segment MUST be a primitive property. The node identifier(s) of an instance $u$ in the input set are the primitive values in $γ(u,p)$ reached via $p$ starting from $u$. Let $p=p_1/…/p_k/s$ with $k≥0$ be the concatenation where each sub-path $p_1,…,p_k$ consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either $s$ consists of one or more single-valued segments or $k≥1$ and ${}/s$ is absent. -The recursive hierarchy to be processed can also be a subset $H'$ of $H$. For this case a non-empty sequence $S$ of transformations MAY be specified as an optional parameter whose position varies from transformation to transformation and is given below. In general, let $H'$ be the output set of the transformation sequence $S$ applied to $H$, or $H'=H$ if $S$ is not specified. The transformations in $S$ MUST be listed in the section on [Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) or in the section on [Hierarchical Transformations Preserving the Input Set Structure](#HierarchicalTransformationsPreservingtheInputSetStructure) or be service-defined bound functions whose output set is a subset of the input set. +The recursive hierarchy to be processed can also be a subset $H'$ of $H$. For this case a non-empty sequence $S$ of transformations MAY be specified as an optional parameter whose position varies from transformation to transformation and is given below. In general, let $H'$ be the output set of the transformation sequence $S$ applied to $H$, or $H'=H$ if $S$ is not specified. The transformations in $S$ MUST be listed in the section on [Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) or in the section on [Hierarchical Transformations Preserving the Input Set Structure](#HierarchicalTransformationsPreservingtheInputSetStructure) or be service-defined bound functions whose output set is a subset of the input set. The hierarchy $(H',Q)$ can have different [roots](#RecursiveHierarchy) than the hierarchy $(H,Q)$. ## 6.2 Hierarchical Transformations Preserving the Input Set Structure diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md index f296f3cc5..7e5e070cc 100644 --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md @@ -18,7 +18,7 @@ The recursive hierarchy is defined by a parameter pair $(H,Q)$, where $H$ and $Q The third parameter MUST be a data aggregation path $p$ with single- or collection-valued segments whose last segment MUST be a primitive property. The node identifier(s) of an instance $u$ in the input set are the primitive values in $γ(u,p)$ reached via $p$ starting from $u$. Let $p=p_1/…/p_k/s$ with $k≥0$ be the concatenation where each sub-path $p_1,…,p_k$ consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either $s$ consists of one or more single-valued segments or $k≥1$ and ${}/s$ is absent. -The recursive hierarchy to be processed can also be a subset $H'$ of $H$. For this case a non-empty sequence $S$ of transformations MAY be specified as an optional parameter whose position varies from transformation to transformation and is given below. In general, let $H'$ be the output set of the transformation sequence $S$ applied to $H$, or $H'=H$ if $S$ is not specified. The transformations in $S$ MUST be listed in the section on [Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) or in the section on [Hierarchical Transformations Preserving the Input Set Structure](#HierarchicalTransformationsPreservingtheInputSetStructure) or be service-defined bound functions whose output set is a subset of the input set. +The recursive hierarchy to be processed can also be a subset $H'$ of $H$. For this case a non-empty sequence $S$ of transformations MAY be specified as an optional parameter whose position varies from transformation to transformation and is given below. In general, let $H'$ be the output set of the transformation sequence $S$ applied to $H$, or $H'=H$ if $S$ is not specified. The transformations in $S$ MUST be listed in the section on [Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) or in the section on [Hierarchical Transformations Preserving the Input Set Structure](#HierarchicalTransformationsPreservingtheInputSetStructure) or be service-defined bound functions whose output set is a subset of the input set. The hierarchy $(H',Q)$ can have different [roots](#RecursiveHierarchy) than the hierarchy $(H,Q)$. ## ##subsec Hierarchical Transformations Preserving the Input Set Structure From 02179ca04f2a0e81cd78e27e0fb966fda41e6628 Mon Sep 17 00:00:00 2001 From: D024504 Date: Tue, 23 May 2023 14:21:17 +0200 Subject: [PATCH 005/116] Allow for orphans --- .../odata-data-aggregation-ext.html | 4 +++- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md | 2 +- .../5 Vocabulary for Data Aggregation.md | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index d29032149..c547c7b2f 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -3930,7 +3930,9 @@

    2
    • The NodeProperty contains a path with single-valued segments ending in a primitive property. This path points to the -property holding the node identifier of the node in the hierarchy.
    • +property holding the node identifier of the node in the hierarchy. +Entities for which this path evaluates to null are not nodes of the +hierarchy (sometimes called "orphans").
    • The ParentNavigationProperty allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 4049b9c97..f7bbcc561 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2367,7 +2367,7 @@ A recursive hierarchy is defined on a collection of entities by associating with A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: -- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. +- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy (sometimes called "orphans"). - The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index cd97fa110..b482e4e1e 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -170,7 +170,7 @@ A recursive hierarchy is defined on a collection of entities by associating with A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: -- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. +- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy (sometimes called "orphans"). - The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. From b097c0a47363d87af05e2cc50d63f450672b27ac Mon Sep 17 00:00:00 2001 From: D024504 Date: Tue, 23 May 2023 14:56:11 +0200 Subject: [PATCH 006/116] Island orphans --- .../odata-data-aggregation-ext.html | 4 ++-- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md | 2 +- .../5 Vocabulary for Data Aggregation.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index c547c7b2f..9422f117a 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -3936,8 +3936,8 @@

      2
    • The ParentNavigationProperty allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path -that addresses the entity type annotated with this term. There MUST NOT -be any cycles in the traversal of parent links.
    • +that addresses the entity type annotated with this term. Nodes MUST NOT +form cycles when following parent navigation properties.

    The term RecursiveHierarchy can only be applied to entity types, and MUST be applied with a qualifier, which is used to diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index f7bbcc561..1d4c18633 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2368,7 +2368,7 @@ A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: - The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy (sometimes called "orphans"). -- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. Nodes MUST NOT form cycles when following parent navigation properties. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index b482e4e1e..eead73780 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -171,7 +171,7 @@ A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: - The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy (sometimes called "orphans"). -- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. There MUST NOT be any cycles in the traversal of parent links. +- The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. Nodes MUST NOT form cycles when following parent navigation properties. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. From 36323cc6bb63b5424eaf4499c2aeb64101a560cd Mon Sep 17 00:00:00 2001 From: D024504 Date: Thu, 25 May 2023 08:01:27 +0200 Subject: [PATCH 007/116] Errors in JSON code --- .../odata-data-aggregation-ext.html | 154 +++++++++--------- .../odata-data-aggregation-ext.md | 40 ++--- ...ions Preserving the Input Set Structure.md | 6 +- ...5 Expressions Evaluable on a Collection.md | 2 +- odata-data-aggregation-ext/7 Examples.md | 32 ++-- 5 files changed, 119 insertions(+), 115 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index e7f27321f..a902d148d 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -2962,8 +2962,8 @@

    3.2.1.2

    results in

    {
    -  "@odata.context": "$metadata#Sales"
    -  "value": [
    +  "@odata.context": "$metadata#Sales",
    +  "value": [
         { "ID": 1, "Amount": 1 },
         { "ID": 7, "Amount": 1 }
       ]
    @@ -3180,8 +3180,8 @@ 

    3.2.1.2

    results in

    {
    -  "@odata.context": "$metadata#Sales"
    -  "value": [
    +  "@odata.context": "$metadata#Sales",
    +  "value": [
         { "ID": 6, "Amount": 2 },
         { "ID": 7, "Amount": 1 }
       ]
    @@ -3211,8 +3211,8 @@ 

    3.2.1.2

    results in

    {
    -  "@odata.context": "$metadata#Sales"
    -  "value": [
    +  "@odata.context": "$metadata#Sales",
    +  "value": [
         { "ID": 4, "Amount": 8 },
         { "ID": 5, "Amount": 4 }
       ]
    @@ -3573,12 +3573,13 @@ 

    3.2.1.2
    GET /service/Products?$filter=Sales/any(s:s/Amount ge
                                   Sales/aggregate(Amount with average) mul 2)

    Both examples result in

    -
    {
    -  "@odata.context": "$metadata#Products",
    -  "value": [
    -    { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Products",
    +  "value": [
    +    { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
    +  ]
    +}

    3.5.2 @@ -5163,9 +5164,9 @@

    7 { "Customer": { "Name": "Joe", "ID": "C1" }, "Product": { "Name": "Paper" } }, { "Customer": { "Name": "Joe", "ID": "C1" }, - "Product: { "Name": "Sugar" } }, - { "Customer": { "Name": "Sue", "ID": "C2" }, - "Product: { "Name": "Coffee"} }, + "Product": { "Name": "Sugar" } }, + { "Customer": { "Name": "Sue", "ID": "C2" }, + "Product": { "Name": "Coffee"} }, { "Customer": { "Name": "Sue", "ID": "C2" }, "Product": { "Name": "Paper" } }, { "Customer": { "Name": "Sue", "ID": "C3" }, @@ -5471,7 +5472,7 @@

    7 }

    The aggregate function can also be applied inside -$apply:

    +$apply:

    Example 82: Sales volume per customer in relation to total volume

    GET /service/Sales?$apply=
    @@ -5487,9 +5488,9 @@ 

    7 { "Customer": { "@odata.id": "Customers('C1')" }, "Contribution@odata.type": "Decimal", "Contribution": 0.2916667 }, { "Customer": { "@odata.id": "Customers('C2')" }, - "Contribution@odata.type": "Decimal", "Contribution": 0.5 } }, + "Contribution@odata.type": "Decimal", "Contribution": 0.5 }, { "Customer": { "@odata.id": "Customers('C3')" }, - "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 } } + "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 } ] }

    @@ -5538,12 +5539,12 @@

    7 "Addresses": [ { "Locality": "Seattle", "AugmentedSalesOrganization": - { "@odata.context": "#SalesOrganizations/$entity" - "ID": "US West", "SalesRegion": "US" } }, + { "@odata.context": "#SalesOrganizations/$entity", + "ID": "US West", "SalesRegion": "US" } }, { "Locality": "DC", "AugmentedSalesOrganization": - { "@odata.context": "#SalesOrganizations/$entity" - "ID": "US", "SalesRegion": "Corporate Sales" } }, + { "@odata.context": "#SalesOrganizations/$entity", + "ID": "US", "SalesRegion": "Corporate Sales" } }, ] }, ... ] @@ -5641,27 +5642,28 @@

    7
    GET /service/Customers?$apply=outerjoin(Sales as ProductSales)
                            /groupby((Country,ProductSales/Product/Name))

    returns the different combinations of products sold per country:

    -
    {
    -  "@odata.context":"$metadata#Customers(Country,ProductSales())",
    -  "value": [
    -    { "Country": "Netherlands",
    -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Paper"  } } },
    -    { "Country": "Netherlands",
    -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    -    { "Country": "USA",
    -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Coffee" } } },
    -    { "Country": "USA",
    -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Paper"  } } },
    -    { "Country": "USA",
    -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    -    { "Country": "France", "ProductSales": null }
    -  ]
    -}
    +
    {
    +  "@odata.context":"$metadata#Customers(Country,ProductSales())",
    +  "value": [
    +    { "Country": "Netherlands",
    +      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Paper"  } } },
    +    { "Country": "Netherlands",
    +      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    +    { "Country": "USA",
    +      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Coffee" } } },
    +    { "Country": "USA",
    +      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Paper"  } } },
    +    { "Country": "USA",
    +      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    +    { "Country": "France", "ProductSales": null }
    +  ]
    +}

    Requesting Custom Aggregates Custom aggregates are defined through the CustomAggregate @@ -5807,15 +5809,16 @@

    7.4
    GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total))

    will return all distinct amounts appearing in sales orders and how much money was made with deals of this amount

    -
    {
    -  "@odata.context": "$metadata#Sales(Amount,Total)",
    -  "value": [
    -    { "Amount": 1, "Total@odata.type": "Decimal", "Total": 2 },
    -    { "Amount": 2, "Total@odata.type": "Decimal", "Total": 6 },
    -    { "Amount": 4, "Total@odata.type": "Decimal", "Total": 8 },
    -    { "Amount": 8, "Total@odata.type": "Decimal", "Total": 8 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Amount,Total)",
    +  "value": [
    +    { "Amount": 1, "Total@odata.type": "Decimal", "Total": 2 },
    +    { "Amount": 2, "Total@odata.type": "Decimal", "Total": 6 },
    +    { "Amount": 4, "Total@odata.type": "Decimal", "Total": 8 },
    +    { "Amount": 8, "Total@odata.type": "Decimal", "Total": 8 }
    +  ]
    +}

    7.5 @@ -5895,23 +5898,24 @@

    7.4 UI consuming the response presents the two groupings in separate columns based on the per property, no contradiction effectively arises.

    -
    {
    -  "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
    -  "value": [
    -    { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
    -      "ID": "3", "Amount": 4, "per": "Customer" },
    -    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    -      "ID": "4", "Amount": 8, "per": "Customer" },
    -    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    -      "ID": "6", "Amount": 2, "per": "Customer" },
    -    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    -      "ID": "6", "Amount": 2, "per": "Product" },
    -    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    -      "ID": "4", "Amount": 8, "per": "Product" },
    -    { "Customer": { "ID": "C2" }, "Product": { "ID": "P3" },
    -      "ID": "5", "Amount": 4, "per": "Product" }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
    +  "value": [
    +    { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
    +      "ID": "3", "Amount": 4, "per": "Customer" },
    +    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    +      "ID": "4", "Amount": 8, "per": "Customer" },
    +    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    +      "ID": "6", "Amount": 2, "per": "Customer" },
    +    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    +      "ID": "6", "Amount": 2, "per": "Product" },
    +    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    +      "ID": "4", "Amount": 8, "per": "Product" },
    +    { "Customer": { "ID": "C2" }, "Product": { "ID": "P3" },
    +      "ID": "5", "Amount": 4, "per": "Product" }
    +  ]
    +}

    7.6 @@ -6259,17 +6263,17 @@

    7.4 "@odata.context": "$metadata#Products(Sales(SalesOrganization(ID)), SoldProducts)", "value": [ - { "Sales": [ "SalesOrganization": { "ID": "Sales" } ], + { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ], "SoldProducts": "P1,P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "EMEA" } ], + { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ], "SoldProducts": "P1,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "EMEA Central" } ], + { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ], "SoldProducts": "P1,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US" } } ], "SoldProducts": "P1,P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US East" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ], "SoldProducts": "P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US West" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ], "SoldProducts": "P1,P2,P3" } ] } diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index b1450245a..ec75577a1 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -1625,7 +1625,7 @@ GET /service/Sales?$apply=bottomcount(2,Amount) results in ```json { - "@odata.context": "$metadata#Sales" + "@odata.context": "$metadata#Sales", "value": [ { "ID": 1, "Amount": 1 }, { "ID": 7, "Amount": 1 } @@ -1831,7 +1831,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/skip(2)/top(2) results in ```json { - "@odata.context": "$metadata#Sales" + "@odata.context": "$metadata#Sales", "value": [ { "ID": 6, "Amount": 2 }, { "ID": 7, "Amount": 1 } @@ -1856,7 +1856,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/top(2) results in ```json { - "@odata.context": "$metadata#Sales" + "@odata.context": "$metadata#Sales", "value": [ { "ID": 4, "Amount": 8 }, { "ID": 5, "Amount": 4 } @@ -2092,7 +2092,7 @@ GET /service/Products?$filter=Sales/any(s:s/Amount ge Sales/aggregate(Amount with average) mul 2) ``` Both examples result in -``` +```json { "@odata.context": "$metadata#Products", "value": [ @@ -3112,9 +3112,9 @@ and results in { "Customer": { "Name": "Joe", "ID": "C1" }, "Product": { "Name": "Paper" } }, { "Customer": { "Name": "Joe", "ID": "C1" }, - "Product: { "Name": "Sugar" } }, + "Product": { "Name": "Sugar" } }, { "Customer": { "Name": "Sue", "ID": "C2" }, - "Product: { "Name": "Coffee"} }, + "Product": { "Name": "Coffee"} }, { "Customer": { "Name": "Sue", "ID": "C2" }, "Product": { "Name": "Paper" } }, { "Customer": { "Name": "Sue", "ID": "C3" }, @@ -3434,7 +3434,7 @@ results in ``` ::: -The `aggregate` function can also be applied inside $apply: +The `aggregate` function can also be applied inside `$apply`: ::: example Example 82: Sales volume per customer in relation to total volume @@ -3453,9 +3453,9 @@ results in { "Customer": { "@odata.id": "Customers('C1')" }, "Contribution@odata.type": "Decimal", "Contribution": 0.2916667 }, { "Customer": { "@odata.id": "Customers('C2')" }, - "Contribution@odata.type": "Decimal", "Contribution": 0.5 } }, + "Contribution@odata.type": "Decimal", "Contribution": 0.5 }, { "Customer": { "@odata.id": "Customers('C3')" }, - "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 } } + "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 } ] } ``` @@ -3506,11 +3506,11 @@ results in "Addresses": [ { "Locality": "Seattle", "AugmentedSalesOrganization": - { "@odata.context": "#SalesOrganizations/$entity" + { "@odata.context": "#SalesOrganizations/$entity", "ID": "US West", "SalesRegion": "US" } }, { "Locality": "DC", "AugmentedSalesOrganization": - { "@odata.context": "#SalesOrganizations/$entity" + { "@odata.context": "#SalesOrganizations/$entity", "ID": "US", "SalesRegion": "Corporate Sales" } }, ] }, ... @@ -3618,7 +3618,7 @@ GET /service/Customers?$apply=outerjoin(Sales as ProductSales) /groupby((Country,ProductSales/Product/Name)) ``` returns the different combinations of products sold per country: -``` +```json { "@odata.context":"$metadata#Customers(Country,ProductSales())", "value": [ @@ -3804,7 +3804,7 @@ Example 95: GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total)) ``` will return all distinct amounts appearing in sales orders and how much money was made with deals of this amount -``` +```json { "@odata.context": "$metadata#Sales(Amount,Total)", "value": [ @@ -3893,7 +3893,7 @@ GET /service/Sales?$apply=concat( &$expand=Customer($select=ID),Product($select=ID) ``` In the result, `Sales` entities 4 and 6 occur twice each with contradictory values of the dynamic property `per`. If a UI consuming the response presents the two groupings in separate columns based on the `per` property, no contradiction effectively arises. -``` +```json { "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))", "value": [ @@ -4265,17 +4265,17 @@ results in "@odata.context": "$metadata#Products(Sales(SalesOrganization(ID)), SoldProducts)", "value": [ - { "Sales": [ "SalesOrganization": { "ID": "Sales" } ], + { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ], "SoldProducts": "P1,P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "EMEA" } ], + { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ], "SoldProducts": "P1,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "EMEA Central" } ], + { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ], "SoldProducts": "P1,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US" } } ], "SoldProducts": "P1,P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US East" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ], "SoldProducts": "P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US West" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ], "SoldProducts": "P1,P2,P3" } ] } diff --git a/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md b/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md index bdd16b974..7e65f7e40 100644 --- a/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md +++ b/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md @@ -41,7 +41,7 @@ GET /service/Sales?$apply=bottomcount(2,Amount) results in ```json { - "@odata.context": "$metadata#Sales" + "@odata.context": "$metadata#Sales", "value": [ { "ID": 1, "Amount": 1 }, { "ID": 7, "Amount": 1 } @@ -247,7 +247,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/skip(2)/top(2) results in ```json { - "@odata.context": "$metadata#Sales" + "@odata.context": "$metadata#Sales", "value": [ { "ID": 6, "Amount": 2 }, { "ID": 7, "Amount": 1 } @@ -272,7 +272,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/top(2) results in ```json { - "@odata.context": "$metadata#Sales" + "@odata.context": "$metadata#Sales", "value": [ { "ID": 4, "Amount": 8 }, { "ID": 5, "Amount": 4 } diff --git a/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md b/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md index 79e60238f..2acc32795 100644 --- a/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md +++ b/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md @@ -46,7 +46,7 @@ GET /service/Products?$filter=Sales/any(s:s/Amount ge Sales/aggregate(Amount with average) mul 2) ``` Both examples result in -``` +```json { "@odata.context": "$metadata#Products", "value": [ diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 6fcb7dfe1..854abfd83 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -110,9 +110,9 @@ and results in { "Customer": { "Name": "Joe", "ID": "C1" }, "Product": { "Name": "Paper" } }, { "Customer": { "Name": "Joe", "ID": "C1" }, - "Product: { "Name": "Sugar" } }, + "Product": { "Name": "Sugar" } }, { "Customer": { "Name": "Sue", "ID": "C2" }, - "Product: { "Name": "Coffee"} }, + "Product": { "Name": "Coffee"} }, { "Customer": { "Name": "Sue", "ID": "C2" }, "Product": { "Name": "Paper" } }, { "Customer": { "Name": "Sue", "ID": "C3" }, @@ -432,7 +432,7 @@ results in ``` ::: -The `aggregate` function can also be applied inside $apply: +The `aggregate` function can also be applied inside `$apply`: ::: example Example ##ex: Sales volume per customer in relation to total volume @@ -451,9 +451,9 @@ results in { "Customer": { "@odata.id": "Customers('C1')" }, "Contribution@odata.type": "Decimal", "Contribution": 0.2916667 }, { "Customer": { "@odata.id": "Customers('C2')" }, - "Contribution@odata.type": "Decimal", "Contribution": 0.5 } }, + "Contribution@odata.type": "Decimal", "Contribution": 0.5 }, { "Customer": { "@odata.id": "Customers('C3')" }, - "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 } } + "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 } ] } ``` @@ -504,11 +504,11 @@ results in "Addresses": [ { "Locality": "Seattle", "AugmentedSalesOrganization": - { "@odata.context": "#SalesOrganizations/$entity" + { "@odata.context": "#SalesOrganizations/$entity", "ID": "US West", "SalesRegion": "US" } }, { "Locality": "DC", "AugmentedSalesOrganization": - { "@odata.context": "#SalesOrganizations/$entity" + { "@odata.context": "#SalesOrganizations/$entity", "ID": "US", "SalesRegion": "Corporate Sales" } }, ] }, ... @@ -616,7 +616,7 @@ GET /service/Customers?$apply=outerjoin(Sales as ProductSales) /groupby((Country,ProductSales/Product/Name)) ``` returns the different combinations of products sold per country: -``` +```json { "@odata.context":"$metadata#Customers(Country,ProductSales())", "value": [ @@ -802,7 +802,7 @@ Example ##ex: GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total)) ``` will return all distinct amounts appearing in sales orders and how much money was made with deals of this amount -``` +```json { "@odata.context": "$metadata#Sales(Amount,Total)", "value": [ @@ -891,7 +891,7 @@ GET /service/Sales?$apply=concat( &$expand=Customer($select=ID),Product($select=ID) ``` In the result, `Sales` entities 4 and 6 occur twice each with contradictory values of the dynamic property `per`. If a UI consuming the response presents the two groupings in separate columns based on the `per` property, no contradiction effectively arises. -``` +```json { "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))", "value": [ @@ -1263,17 +1263,17 @@ results in "@odata.context": "$metadata#Products(Sales(SalesOrganization(ID)), SoldProducts)", "value": [ - { "Sales": [ "SalesOrganization": { "ID": "Sales" } ], + { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ], "SoldProducts": "P1,P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "EMEA" } ], + { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ], "SoldProducts": "P1,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "EMEA Central" } ], + { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ], "SoldProducts": "P1,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US" } } ], "SoldProducts": "P1,P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US East" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ], "SoldProducts": "P2,P3" }, - { "Sales": [ "SalesOrganization": { "ID": "US West" } ], + { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ], "SoldProducts": "P1,P2,P3" } ] } From 8ee987e527db86fb77798b0242aafa5edbb1c6a9 Mon Sep 17 00:00:00 2001 From: D024504 Date: Thu, 25 May 2023 08:17:06 +0200 Subject: [PATCH 008/116] style change --- docs/odata-data-aggregation-ext/styles/odata.css | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/odata-data-aggregation-ext/styles/odata.css b/docs/odata-data-aggregation-ext/styles/odata.css index e82e9fa06..afd59074c 100644 --- a/docs/odata-data-aggregation-ext/styles/odata.css +++ b/docs/odata-data-aggregation-ext/styles/odata.css @@ -161,6 +161,7 @@ mjx-c.mjx-c2019::before { code .er { color: unset !important; + font-weight: unset !important; } hr:first-of-type { From 59cbfd95cc366803b88ca66990e5fc5119088728 Mon Sep 17 00:00:00 2001 From: D024504 Date: Thu, 25 May 2023 14:25:03 +0200 Subject: [PATCH 009/116] Variable clean-up --- .../odata-data-aggregation-ext.html | 172 +++++++++--------- .../odata-data-aggregation-ext.md | 107 +++++------ odata-data-aggregation-ext/1 Introduction.md | 11 +- ...1 Fundamentals of Input and Output Sets.md | 20 +- .../3.2 Basic Aggregation.md | 24 +-- ...ations Changing the Input Set Structure.md | 26 +-- .../6 Hierarchical Transformations.md | 22 +-- odata-data-aggregation-ext/7 Examples.md | 4 +- 8 files changed, 195 insertions(+), 191 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index a902d148d..a6679ba02 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -497,28 +497,30 @@

    1.1

    1.1.2 Acronyms and Abbreviations

    +

    The following non-exhaustive list contains variables that are used +throughout this document:

    Then, if \(r\) is empty, let \(U=E\), otherwise let \(U=\Gamma(E,r)\), this consists of instances +class="math inline">\(A=E\), otherwise let \(A=\Gamma(E,r)\), this consists of instances or primitive values, possibly with repetitions.

    3.2.1.2 Keyword as

    @@ -2616,7 +2618,7 @@

    3.2.1.2 relative to the input set as arguments and is computed recursively as follows: -
  • Return \(w\).
  • +
  • Return \(v\).
  • The input set is split into subsets where two instances are in the same subset if their projections are 3.2.1.2 href="#SamenessandPrecedence">order-preserving loop over the input set

      -
    1. the instance collection \(U\) +
    2. the instance collection \(A\) addressed by \(p\) is identified.
    3. If \(T\) is provided, \(U\) is replaced with the result of applying +class="math inline">\(A\) is replaced with the result of applying \(T\) to \(U\).
    4. +class="math inline">\(A\).
    5. In case of an outerjoin, if \(U\) is empty, a null instance is added to +class="math inline">\(A\) is empty, a null instance is added to it.
    6. For each instance \(w\) in an \(v\) in an order-preserving loop over \(U\) +class="math inline">\(A\)
        -
      • an instance \(x\) is appended to +
      • an instance \(w\) is appended to the output set of the transformation.
      • -
      • The instance \(x\) is a clone of +
      • The instance \(w\) is a clone of \(u\) with an additional dynamic property whose name is the given alias and whose value is \(w\).
      • -
      • The property \(x\) is a navigation +class="math inline">\(v\).
      • +
      • The property \(w\) is a navigation property if \(p\) is a collection-valued navigation property, otherwise it is a complex property.
      • -
      • The property \(x\) carries as +
      • The property \(w\) carries as control information the context URL of \(u\).
    7. @@ -3449,18 +3451,18 @@

      3.2.1.2

      For each instance \(u\) in \(\Gamma(A,p_1/…/p_{k-1})\), let \(U=γ(u,p_k/q)\) and let the resource \(w\) be

      +class="math inline">\(B=γ(u,p_k/q)\) and let the resource \(v\) be

        -
      • the collection \(U\) if the collection \(B\) if \(p_k\) is collection-valued
      • -
      • the single instance in \(U\) if +
      • the single instance in \(B\) if \(p_k\) is single-valued and \(U\) is non-empty
      • +class="math inline">\(B\) is non-empty
      • undefined if \(p_k\) is -single-valued and \(U\) is empty.
      • +single-valued and \(B\) is empty.
      -

      If \(w\) is defined, then for each +

      If \(v\) is defined, then for each transformation sequence, a dynamic property is added to \(u\) as follows: If \(p_k\) is a navigation property, the added @@ -3468,9 +3470,9 @@

      3.2.1.2 otherwise it is a dynamic structural property. Its name is the alias of the transformation sequence. The value of the added property is the result of the transformation sequence applied to \(w\). The dynamic property carries as +class="math inline">\(v\). The dynamic property carries as control information the context URL of \(w\).

      +class="math inline">\(v\).

      Example 39:

      GET /service/Customers?$apply=addnested(Sales,
      @@ -4296,14 +4298,14 @@ 

      5.5 class="math inline">\(γ(u,p)\) reached via \(p\) starting from \(u\). Let \(p=p_1/…/p_k/s\) with \(p=p_1/…/p_k/r\) with \(k≥0\) be the concatenation where each sub-path \(p_1,…,p_k\) consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either -\(s\) consists of one or more +\(r\) consists of one or more single-valued segments or \(k≥1\) and -\({}/s\) is absent.

      +\({}/r\) is absent.

      The recursive hierarchy to be processed can also be a subset \(H'\) of \(H\). For this case a non-empty sequence @@ -4403,7 +4405,7 @@

      5.5 HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \quad {\tt Node}=p,\;{\tt Ancestor}=u[p],\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true})).\hfill }\]

      -

      Otherwise \(p=p_1/…/p_k/s\) with +

      Otherwise \(p=p_1/…/p_k/r\) with \(k≥1\), in this case the output set of the transformation \(F(u)\) is defined as the union of the output @@ -4420,7 +4422,7 @@

      5.5 Aggregation.isancestor}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt -Node}=y_k/s,\;{\tt Descendant}=n,\;{\tt MaxDistance}=d,\;{\tt +Node}=y_k/r,\;{\tt Descendant}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }\] or, for descendants, 5.5 \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt -Node}=y_k/s,\;{\tt Ancestor}=n,\;{\tt MaxDistance}=d,\;{\tt +Node}=y_k/r,\;{\tt Ancestor}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }\] where \(y_1,…,y_k\) denote lambdaVariableExprs and \({}/s\) may be absent.

      +class="math inline">\({}/r\) may be absent.

      If parameter \(d\) is absent, the parameter \({\tt MaxDistance}=d\) is omitted. If keep start is absent, the parameter 5.5

      If \(p\) contains only single-valued segments, then \[F(x)={\tt filter}(p{\tt\ eq\ }x[q]).\]

      -

      Otherwise \(p=p_1/…/p_k/s\) with +

      Otherwise \(p=p_1/…/p_k/r\) with \(k≥1\) and \[\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ -\hskip5pc y_k/s{\tt\ eq\ }x[q]\hfill\\ \hskip4pc )\hfill\\ \hskip3pc +\hskip5pc y_k/r{\tt\ eq\ }x[q]\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }\] where \(y_1,…,y_k\) denote lambdaVariableExprs and \({}/s\) may be absent.

      +class="math inline">\({}/r\) may be absent.

      @@ -6208,7 +6210,7 @@

      7.4

      Example 107: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment \(p_1={\tt Sales}\) and \(s={\tt SalesOrganization}/{\tt ID}\).

      +class="math inline">\(r={\tt SalesOrganization}/{\tt ID}\).

      GET /service/Products?$apply=traverse(
             $root/SalesOrganizations,
             SalesOrgHierarchy,
      diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
      index ec75577a1..6f6b94e84 100644
      --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
      +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
      @@ -211,15 +211,16 @@ This specification defines the following terms:
       
       ### 1.1.2 Acronyms and Abbreviations
       
      -- $A,I,U$ – collections of instances
      +The following non-exhaustive list contains variables that are used throughout this document:
      +- $A,B,C$ – collections of instances
       - $H$ – hierarchical collection
      -- $u,w$ – instances in a collection
      +- $u,v,w$ – instances in a collection
       - $x,y$ – instances in a hierarchical collection, called nodes
      -- $p,q,v$ – paths
      +- $p,q,r$ – paths
       - $S,T$ – transformation sequences
       - $α$ – aggregate expression, defined [below](#AggregationAlgorithm)
      -- $\Gamma(A,v)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $v$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths)
      -- $γ(u,v)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $v$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths)
      +- $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths)
      +- $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths)
       - $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined [below](#SimpleGrouping)
       - $σ(x)$ – instance containing a grouping property that represents a node $x$, defined [below](#Transformationtraverse)
       
      @@ -1082,18 +1083,18 @@ This document specifies when a [data aggregation path](#DataAggregationPath) tha
       
       For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself.
       
      -To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $v$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,v)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,v)$ for each $u$ in $A$ that is not null. The function $γ(u,v)$ takes a non-null value and a path as arguments and is recursively defined as follows:
      -1. If $v$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9.
      -2. Let $v_1$ be the first segment of $v$ and $v_2$ the remainder, if any, such that $v$ equals the concatenated path $v_1/v_2$.
      -3. If $v_1$ is a type-cast segment and $u$ is of its type or a subtype thereof, let $w=u$ and continue with step 8.
      -4. If $v_1$ is a type-cast segment and $u$ is not of its type or a subtype thereof, let $B$ be an empty collection and continue with step 9. (This rule follows [OData-URL, section 4.11](#ODataURL) rather than [OData-CSDL, section 14.4.1.1](#ODataCSDL).)
      -5. Otherwise, $v_1$ is a non-type-cast segment. If $u$ does not contain a structural or navigation property $v_1$, let $B$ be an empty collection and continue with step 9.
      -6. If $v_1$ is single-valued, let $w$ be the value of the structural or navigation property $v_1$ in $u$. If $w$ is null, let $B$ be an empty collection and continue with step 9; otherwise continue with step 8.
      -7. Otherwise, $v_1$ is collection-valued. Let $C$ be the collection addressed by the structural or navigation property $v_1$ in $u$, and let $B=\Gamma(C,v_2)$. Then continue with step 9.
      -8. Let $B=γ(w,v_2)$.
      +To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and is recursively defined as follows:
      +1. If $p$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9.
      +2. Let $p_1$ be the first segment of $p$ and $p_2$ the remainder, if any, such that $p$ equals the concatenated path $p_1/p_2$.
      +3. If $p_1$ is a type-cast segment and $u$ is of its type or a subtype thereof, let $v=u$ and continue with step 8.
      +4. If $p_1$ is a type-cast segment and $u$ is not of its type or a subtype thereof, let $B$ be an empty collection and continue with step 9. (This rule follows [OData-URL, section 4.11](#ODataURL) rather than [OData-CSDL, section 14.4.1.1](#ODataCSDL).)
      +5. Otherwise, $p_1$ is a non-type-cast segment. If $u$ does not contain a structural or navigation property $p_1$, let $B$ be an empty collection and continue with step 9.
      +6. If $p_1$ is single-valued, let $v$ be the value of the structural or navigation property $p_1$ in $u$. If $v$ is null, let $B$ be an empty collection and continue with step 9; otherwise continue with step 8.
      +7. Otherwise, $p_1$ is collection-valued. Let $C$ be the collection addressed by the structural or navigation property $p_1$ in $u$, and let $B=\Gamma(C,p_2)$. Then continue with step 9.
      +8. Let $B=γ(v,p_2)$.
       9. Return $B$.
       
      -This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every $u$ in $\Gamma(A,v)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,v)$" effectively changes $A$.
      +This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every $u$ in $\Gamma(A,p)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,p)$" effectively changes $A$.
       
       ## 3.2 Basic Aggregation
       
      @@ -1103,29 +1104,29 @@ This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,
       
       The `aggregate` transformation takes a comma-separated list of one or more _aggregate expressions_ as parameters and returns an output set with a single instance of the [input type](#TypeStructureandContextURL) without entity-id containing one property per aggregate expression, representing the aggregated value of the input set.
       
      -An aggregate expression MUST have one of the types listed below. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $U$ of instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on the type of aggregate expression, $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(U)$ which the aggregate transformation evaluates to obtain the property value.
      +An aggregate expression MUST have one of the types listed below. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $A$ of instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on the type of aggregate expression, $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(A)$ which the aggregate transformation evaluates to obtain the property value.
       
       The property is a dynamic property, except for a special case in type 4. In types 1 and 2, the aggregate expression MUST end with the keyword `with` and an aggregation method $g$. The aggregation method also determines the type of the dynamic property. In types 1, 2 and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword [`as`](#Keywordas) and an [alias](#TypeStructureandContextURL), which is then the name of the dynamic property. More aggregate expressions can be constructed with the [`from`](#Keywordfrom) keyword, see later section.
       
       _Types of aggregate expressions:_
       1. A path $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a complex or entity or [aggregatable primitive type](#AggregatablePrimitiveType) whose values can be aggregated using the specified [aggregation method](#AggregationMethods) $g$, or $p=p_2$ if the input set can be aggregated using the [custom aggregation method](#CustomAggregationMethods) $g$.  
      -Let $f(U)=g(U)$.
      +Let $f(A)=g(A)$.
       2. An [aggregatable expression](#AggregatableExpression).  
      -Let $f(U)=g(V)$ where $V$ is the collection consisting of the aggregatable expression evaluated relative to each member of $U$ with null values removed from $V$. In this type, $p$ is absent.
      +Let $f(A)=g(B)$ where $B$ is the collection consisting of the aggregatable expression evaluated relative to each member of $A$ with null values removed from $B$. In this type, $p$ is absent.
       3. A path $p/{\tt\$count}$ (see [section 3.2.1.4](#AggregateExpressioncount)) with optional prefix $p/{}$ where $p=p_1$ or $p=p_2$ or $p=p_1/p_2$.  
      -Let $f(U)$ be the [cardinality](#SamenessandPrecedence) of $U$.
      +Let $f(A)$ be the [cardinality](#SamenessandPrecedence) of $A$.
       4. A path $p/c$ consisting of an optional prefix $p/{}$ with $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a structured type or $p=p_2$, and a [custom aggregate](#CustomAggregates) $c$ defined on the collection addressed by $p$.  
      -Let $f(U)=c(U)$, if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property.
      +Let $f(A)=c(A)$, if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property.
       
      -_Determination of $U$:_
      +_Determination of $A$:_
       
      -Let $I$ be the input set. If $p$ is absent, let $U=I$ with null values removed.
      +Let $I$ be the input set. If $p$ is absent, let $A=I$ with null values removed.
       
       Otherwise, let $q$ be the portion of $p$ up to and including the last navigation property, if any, and any type-cast segment that immediately follows, and let $r$ be the remainder, if any, of $p$ that contains no navigation properties, such that $p$ equals the concatenated path $q⁄r$. The aggregate transformation considers each entity reached via the path $q$ exactly once. To this end, using the [$\Gamma$ notation](#EvaluationofDataAggregationPaths):
       - If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example 119](#aggrconflict).) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$.
       - If $q$ is empty, let $E=I$.
       
      -Then, if $r$ is empty, let $U=E$, otherwise let $U=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions.
      +Then, if $r$ is empty, let $A=E$, otherwise let $A=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions.
       
       #### 3.2.1.2 Keyword `as`
       
      @@ -1426,12 +1427,12 @@ The algorithmic description of this transformation makes use of the following de
       
       The output set of the groupby transformation is constructed in five steps.
       1. [For each](#SamenessandPrecedence) instance $u$ in the input set, a projection is computed that contains only the grouping properties. This projection is $s_G(u,e)$ and the function $s_G(u,p)$ takes an instance and a path relative to the input set as arguments and is computed recursively as follows:
      -   - Let $w$ be an instance of the type of $u$ without properties and without entity-id.
      +   - Let $v$ be an instance of the type of $u$ without properties and without entity-id.
          - For each structural or navigation property $q$ of $u$:
            - If $u$ has a subtype of the type addressed by $p$ and $q$ is only declared on that subtype, let $p'=p/p''/q$ where $p''$ is a type-cast to the subtype, otherwise let $p'=p/q$.
      -     - If $p'$ occurs in $G$, let $w[q]=u[q]$.
      -     - Otherwise, if $p'$ is a prefix of a path in $G$, let $w[q]=s_G(u[q],p')$.
      -   - Return $w$.
      +     - If $p'$ occurs in $G$, let $v[q]=u[q]$.
      +     - Otherwise, if $p'$ is a prefix of a path in $G$, let $v[q]=s_G(u[q],p')$.
      +   - Return $v$.
       2. The input set is split into subsets where two instances are in the same subset if their projections are [the same](#SamenessandPrecedence). If [representations of the same non-transient entity](#SamenessandPrecedence) are encountered during the comparison of two projections, the service MUST assign them to one subset with the merged representation if they are complementary and MUST reject the request if they are contradictory.
       3. The set transformations from the second parameter are applied to each subset, resulting in a new set of potentially different structure and cardinality. Associated with each resulting set is the common projection of the instances in the subset from which the resulting set was computed.
       4. Each set resulting from the previous step is transformed to contain the associated common projection $s$. This transformation is denoted by $\Pi_G(s)$ and is defined below.
      @@ -1911,14 +1912,14 @@ results in
       The `join` and `outerjoin` transformations take as their first parameter $p$ a collection-valued complex property or navigation property, optionally followed by a type-cast segment to address only instances of that derived type or one of its sub-types, followed by the `as` keyword, followed by an [alias](#TypeStructureandContextURL). The optional second parameter specifies a transformation sequence $T$.
       
       [For each](#SamenessandPrecedence) instance $u$ in an [order-preserving loop](#SamenessandPrecedence) over the input set
      -1. the instance collection $U$ addressed by $p$ is identified.
      -2. If $T$ is provided, $U$ is replaced with the result of applying $T$ to $U$.
      -3. In case of an `outerjoin`, if $U$ is empty, a null instance is added to it.
      -4. [For each](#SamenessandPrecedence) instance $w$ in an [order-preserving loop](#SamenessandPrecedence) over $U$
      -   - an instance $x$ is appended to the output set of the transformation.
      -   - The instance $x$ is a clone of $u$ with an additional dynamic property whose name is the given alias and whose value is $w$.
      -   - The property $x$ is a navigation property if $p$ is a collection-valued navigation property, otherwise it is a complex property.
      -   - The property $x$ carries as control information the context URL of $u$.
      +1. the instance collection $A$ addressed by $p$ is identified.
      +2. If $T$ is provided, $A$ is replaced with the result of applying $T$ to $A$.
      +3. In case of an `outerjoin`, if $A$ is empty, a null instance is added to it.
      +4. [For each](#SamenessandPrecedence) instance $v$ in an [order-preserving loop](#SamenessandPrecedence) over $A$
      +   - an instance $w$ is appended to the output set of the transformation.
      +   - The instance $w$ is a clone of $u$ with an additional dynamic property whose name is the given alias and whose value is $v$.
      +   - The property $w$ is a navigation property if $p$ is a collection-valued navigation property, otherwise it is a complex property.
      +   - The property $w$ carries as control information the context URL of $u$.
       
       ::: example
       Example 37: all links between products and sales instances
      @@ -2006,12 +2007,12 @@ Further parameters are one or more transformation sequences followed by the as k
       
       If $p_k$ is single-valued, the transformation sequences MUST consist of only `identity` or `compute` or `addnested` transformations, because these transform one-element collections into one-element collections. This makes it meaningful to speak (in this section only) of a transformation sequence applied to a single instance; this means applying it to a collection containing the single instance and taking as result the single instance from the output set.
       
      -[For each](#SamenessandPrecedence) instance $u$ in $\Gamma(A,p_1/…/p_{k-1})$, let $U=γ(u,p_k/q)$ and let the resource $w$ be
      -- the collection $U$ if $p_k$ is collection-valued
      -- the single instance in $U$ if $p_k$ is single-valued and $U$ is non-empty
      -- undefined if $p_k$ is single-valued and $U$ is empty.
      +[For each](#SamenessandPrecedence) instance $u$ in $\Gamma(A,p_1/…/p_{k-1})$, let $B=γ(u,p_k/q)$ and let the resource $v$ be
      +- the collection $B$ if $p_k$ is collection-valued
      +- the single instance in $B$ if $p_k$ is single-valued and $B$ is non-empty
      +- undefined if $p_k$ is single-valued and $B$ is empty.
       
      -If $w$ is defined, then for each transformation sequence, a dynamic property is added to $u$ as follows: If $p_k$ is a navigation property, the added property is a dynamic navigation property, which is expanded by default, otherwise it is a dynamic structural property. Its name is the alias of the transformation sequence. The value of the added property is the result of the transformation sequence applied to $w$. The dynamic property carries as control information the context URL of $w$.
      +If $v$ is defined, then for each transformation sequence, a dynamic property is added to $u$ as follows: If $p_k$ is a navigation property, the added property is a dynamic navigation property, which is expanded by default, otherwise it is a dynamic structural property. Its name is the alias of the transformation sequence. The value of the added property is the result of the transformation sequence applied to $v$. The dynamic property carries as control information the context URL of $v$.
       
       ::: example
       Example 39:
      @@ -2630,7 +2631,7 @@ The parameter lists defined in the following subsections have three mandatory pa
       
       The recursive hierarchy is defined by a parameter pair $(H,Q)$, where $H$ and $Q$ MUST be specified as the first and second parameter. Here, $H$ MUST be an expression of type `Collection(Edm.EntityType)` starting with `$root` that has no multiple occurrences of the same entity. $H$ identifies the collection of node entities forming a recursive hierarchy based on an annotation of their common entity type with term `RecursiveHierarchy` with a `Qualifier` attribute whose value MUST be provided in $Q$. The property paths referenced by `NodeProperty` and `ParentNavigationProperty` in the `RecursiveHierarchy` annotation must be evaluable for the nodes in the recursive hierarchy, otherwise the service MUST reject the request. The `NodeProperty` is denoted by $q$ in this section.
       
      -The third parameter MUST be a data aggregation path $p$ with single- or collection-valued segments whose last segment MUST be a primitive property. The node identifier(s) of an instance $u$ in the input set are the primitive values in $γ(u,p)$ reached via $p$ starting from $u$. Let $p=p_1/…/p_k/s$ with $k≥0$ be the concatenation where each sub-path $p_1,…,p_k$ consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either $s$ consists of one or more single-valued segments or $k≥1$ and ${}/s$ is absent.
      +The third parameter MUST be a data aggregation path $p$ with single- or collection-valued segments whose last segment MUST be a primitive property. The node identifier(s) of an instance $u$ in the input set are the primitive values in $γ(u,p)$ reached via $p$ starting from $u$. Let $p=p_1/…/p_k/r$ with $k≥0$ be the concatenation where each sub-path $p_1,…,p_k$ consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either $r$ consists of one or more single-valued segments or $k≥1$ and ${}/r$ is absent.
       
       The recursive hierarchy to be processed can also be a subset $H'$ of $H$. For this case a non-empty sequence $S$ of transformations MAY be specified as an optional parameter whose position varies from transformation to transformation and is given below. In general, let $H'$ be the output set of the transformation sequence $S$ applied to $H$, or $H'=H$ if $S$ is not specified. The transformations in $S$ MUST be listed in the section on [Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) or in the section on [Hierarchical Transformations Preserving the Input Set Structure](#HierarchicalTransformationsPreservingtheInputSetStructure) or be service-defined bound functions whose output set is a subset of the input set.
       
      @@ -2661,13 +2662,13 @@ $$\matrix{ F(u)={\tt filter}(\hbox{\tt Aggregation.isancestor}(\hfill\\ \quad {\
       or, for `descendants`,
       $$\matrix{ F(u)={\tt filter}(\hbox{\tt Aggregation.isdescendant}(\hfill\\ \quad {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \quad {\tt Node}=p,\;{\tt Ancestor}=u[p],\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true})).\hfill }$$
       
      -Otherwise $p=p_1/…/p_k/s$ with $k≥1$, in this case the output set of the transformation $F(u)$ is defined as the [union](#HierarchicalTransformations) of the output sets of transformations $G(n)$ applied to the input set for all $n$ in $γ(u,p)$. The output set of $G(n)$ consists of the instances of the input set whose node identifier is an ancestor or descendant of the node identifier $n$:
      +Otherwise $p=p_1/…/p_k/r$ with $k≥1$, in this case the output set of the transformation $F(u)$ is defined as the [union](#HierarchicalTransformations) of the output sets of transformations $G(n)$ applied to the input set for all $n$ in $γ(u,p)$. The output set of $G(n)$ consists of the instances of the input set whose node identifier is an ancestor or descendant of the node identifier $n$:
       
       For `ancestors`,
      -$$\matrix{ G(n)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isancestor}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/s,\;{\tt Descendant}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      +$$\matrix{ G(n)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isancestor}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/r,\;{\tt Descendant}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
       or, for `descendants`,
      -$$\matrix{ G(n)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/s,\;{\tt Ancestor}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/s$ may be absent.
      +$$\matrix{ G(n)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/r,\;{\tt Ancestor}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent.
       
       If parameter $d$ is absent, the parameter ${\tt MaxDistance}=d$ is omitted. If `keep start` is absent, the parameter ${\tt IncludeSelf}={\tt true}$ is omitted.
       
      @@ -2807,9 +2808,9 @@ $F(x)$ is a transformation that determines for the specified node $x$ the instan
       If $p$ contains only single-valued segments, then
       $$F(x)={\tt filter}(p{\tt\ eq\ }x[q]).$$
       
      -Otherwise $p=p_1/…/p_k/s$ with $k≥1$ and
      -$$\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc y_k/s{\tt\ eq\ }x[q]\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/s$ may be absent.
      +Otherwise $p=p_1/…/p_k/r$ with $k≥1$ and
      +$$\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc y_k/r{\tt\ eq\ }x[q]\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent.
       
       ::: example
       Example 60: Based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples)
      @@ -2888,9 +2889,9 @@ $$\matrix{ R(x)={\tt concat}(\hfill\\ \quad F(x)/{\tt compute}(x{\tt\ as\ }χ_N)
       $F(x)$ is defined as follows: If $p$ contains only single-valued segments, then
       $$\matrix{ F(x)={\tt filter}(\hbox{\tt Aggregation.isdescendant}(\hfill\\ \quad {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \quad {\tt Node}=p,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true})).\hfill }$$
       
      -Otherwise $p=p_1/…/p_k/s$ with $k≥1$ and
      -$$\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/s,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/s$ may be absent. (See [example 108](#rollupcoll) for a case with $k=1$.)
      +Otherwise $p=p_1/…/p_k/r$ with $k≥1$ and
      +$$\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/r,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$
      +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent. (See [example 108](#rollupcoll) for a case with $k=1$.)
       
       Non-normatively speaking, the effect of the algorithm can be summarized as follows: If $M≥1$ and $\hat F_N(x)$ denotes the collection of all instances that are related to a node $x$ from the recursive hierarchy of the $N$-th `rolluprecursive` operator, then $T$ is applied to each of the intersections of $\hat F_1(χ_1),…,\hat F_M(χ_M)$, as $χ_N$ runs over all nodes of the $N$-th recursive hierarchy for $1≤N≤M$. Into the instances of the resulting output sets the $\Pi_G$ transformations inject information about the nodes $χ_1,…,χ_M$.
       
      @@ -3188,7 +3189,7 @@ results in
       }
       ```
       
      -Note that the base set of the request is `Products`, so there is a result item for product `Pencil` even though there are no sales items. The input set for the aggregation in the third row is $I$ consisting of the pencil, $p=q/r={\tt Sales}/{\tt Amount}$, $E=\Gamma(I,q)$ is empty and $U=\Gamma(E,r)$ is also empty. The sum over the empty collection is null.
      +Note that the base set of the request is `Products`, so there is a result item for product `Pencil` even though there are no sales items. The input set for the aggregation in the third row is $I$ consisting of the pencil, $p=q/r={\tt Sales}/{\tt Amount}$, $E=\Gamma(I,q)$ is empty and $A=\Gamma(E,r)$ is also empty. The sum over the empty collection is null.
       :::
       
       ::: example
      @@ -4210,7 +4211,7 @@ results in
       :::
       
       ::: example
      -Example 107: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment $p_1={\tt Sales}$ and $s={\tt SalesOrganization}/{\tt ID}$.
      +Example 107: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment $p_1={\tt Sales}$ and $r={\tt SalesOrganization}/{\tt ID}$.
       ```
       GET /service/Products?$apply=traverse(
             $root/SalesOrganizations,
      diff --git a/odata-data-aggregation-ext/1 Introduction.md b/odata-data-aggregation-ext/1 Introduction.md
      index 42d08a0e0..4705ebf80 100644
      --- a/odata-data-aggregation-ext/1 Introduction.md	
      +++ b/odata-data-aggregation-ext/1 Introduction.md	
      @@ -20,15 +20,16 @@ This specification defines the following terms:
       
       ### ##subsubsec Acronyms and Abbreviations
       
      -- $A,I,U$ – collections of instances
      +The following non-exhaustive list contains variables that are used throughout this document:
      +- $A,B,C$ – collections of instances
       - $H$ – hierarchical collection
      -- $u,w$ – instances in a collection
      +- $u,v,w$ – instances in a collection
       - $x,y$ – instances in a hierarchical collection, called nodes
      -- $p,q,v$ – paths
      +- $p,q,r$ – paths
       - $S,T$ – transformation sequences
       - $α$ – aggregate expression, defined [below](#AggregationAlgorithm)
      -- $\Gamma(A,v)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $v$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths)
      -- $γ(u,v)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $v$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths)
      +- $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths)
      +- $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths)
       - $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined [below](#SimpleGrouping)
       - $σ(x)$ – instance containing a grouping property that represents a node $x$, defined [below](#Transformationtraverse)
       
      diff --git a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md
      index 63eb88adf..f31994040 100644
      --- a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md	
      +++ b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md	
      @@ -95,15 +95,15 @@ This document specifies when a [data aggregation path](#DataAggregationPath) tha
       
       For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself.
       
      -To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $v$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,v)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,v)$ for each $u$ in $A$ that is not null. The function $γ(u,v)$ takes a non-null value and a path as arguments and is recursively defined as follows:
      -1. If $v$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9.
      -2. Let $v_1$ be the first segment of $v$ and $v_2$ the remainder, if any, such that $v$ equals the concatenated path $v_1/v_2$.
      -3. If $v_1$ is a type-cast segment and $u$ is of its type or a subtype thereof, let $w=u$ and continue with step 8.
      -4. If $v_1$ is a type-cast segment and $u$ is not of its type or a subtype thereof, let $B$ be an empty collection and continue with step 9. (This rule follows [OData-URL, section 4.11](#ODataURL) rather than [OData-CSDL, section 14.4.1.1](#ODataCSDL).)
      -5. Otherwise, $v_1$ is a non-type-cast segment. If $u$ does not contain a structural or navigation property $v_1$, let $B$ be an empty collection and continue with step 9.
      -6. If $v_1$ is single-valued, let $w$ be the value of the structural or navigation property $v_1$ in $u$. If $w$ is null, let $B$ be an empty collection and continue with step 9; otherwise continue with step 8.
      -7. Otherwise, $v_1$ is collection-valued. Let $C$ be the collection addressed by the structural or navigation property $v_1$ in $u$, and let $B=\Gamma(C,v_2)$. Then continue with step 9.
      -8. Let $B=γ(w,v_2)$.
      +To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and is recursively defined as follows:
      +1. If $p$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9.
      +2. Let $p_1$ be the first segment of $p$ and $p_2$ the remainder, if any, such that $p$ equals the concatenated path $p_1/p_2$.
      +3. If $p_1$ is a type-cast segment and $u$ is of its type or a subtype thereof, let $v=u$ and continue with step 8.
      +4. If $p_1$ is a type-cast segment and $u$ is not of its type or a subtype thereof, let $B$ be an empty collection and continue with step 9. (This rule follows [OData-URL, section 4.11](#ODataURL) rather than [OData-CSDL, section 14.4.1.1](#ODataCSDL).)
      +5. Otherwise, $p_1$ is a non-type-cast segment. If $u$ does not contain a structural or navigation property $p_1$, let $B$ be an empty collection and continue with step 9.
      +6. If $p_1$ is single-valued, let $v$ be the value of the structural or navigation property $p_1$ in $u$. If $v$ is null, let $B$ be an empty collection and continue with step 9; otherwise continue with step 8.
      +7. Otherwise, $p_1$ is collection-valued. Let $C$ be the collection addressed by the structural or navigation property $p_1$ in $u$, and let $B=\Gamma(C,p_2)$. Then continue with step 9.
      +8. Let $B=γ(v,p_2)$.
       9. Return $B$.
       
      -This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every $u$ in $\Gamma(A,v)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,v)$" effectively changes $A$.
      +This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every $u$ in $\Gamma(A,p)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,p)$" effectively changes $A$.
      diff --git a/odata-data-aggregation-ext/3.2 Basic Aggregation.md b/odata-data-aggregation-ext/3.2 Basic Aggregation.md
      index 97f56896c..60ccb935c 100644
      --- a/odata-data-aggregation-ext/3.2 Basic Aggregation.md	
      +++ b/odata-data-aggregation-ext/3.2 Basic Aggregation.md	
      @@ -6,29 +6,29 @@
       
       The `aggregate` transformation takes a comma-separated list of one or more _aggregate expressions_ as parameters and returns an output set with a single instance of the [input type](#TypeStructureandContextURL) without entity-id containing one property per aggregate expression, representing the aggregated value of the input set.
       
      -An aggregate expression MUST have one of the types listed below. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $U$ of instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on the type of aggregate expression, $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(U)$ which the aggregate transformation evaluates to obtain the property value.
      +An aggregate expression MUST have one of the types listed below. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $A$ of instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on the type of aggregate expression, $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(A)$ which the aggregate transformation evaluates to obtain the property value.
       
       The property is a dynamic property, except for a special case in type 4. In types 1 and 2, the aggregate expression MUST end with the keyword `with` and an aggregation method $g$. The aggregation method also determines the type of the dynamic property. In types 1, 2 and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword [`as`](#Keywordas) and an [alias](#TypeStructureandContextURL), which is then the name of the dynamic property. More aggregate expressions can be constructed with the [`from`](#Keywordfrom) keyword, see later section.
       
       _Types of aggregate expressions:_
       1. A path $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a complex or entity or [aggregatable primitive type](#AggregatablePrimitiveType) whose values can be aggregated using the specified [aggregation method](#AggregationMethods) $g$, or $p=p_2$ if the input set can be aggregated using the [custom aggregation method](#CustomAggregationMethods) $g$.  
      -Let $f(U)=g(U)$.
      +Let $f(A)=g(A)$.
       2. An [aggregatable expression](#AggregatableExpression).  
      -Let $f(U)=g(V)$ where $V$ is the collection consisting of the aggregatable expression evaluated relative to each member of $U$ with null values removed from $V$. In this type, $p$ is absent.
      +Let $f(A)=g(B)$ where $B$ is the collection consisting of the aggregatable expression evaluated relative to each member of $A$ with null values removed from $B$. In this type, $p$ is absent.
       3. A path $p/{\tt\$count}$ (see [section ##AggregateExpressioncount]) with optional prefix $p/{}$ where $p=p_1$ or $p=p_2$ or $p=p_1/p_2$.  
      -Let $f(U)$ be the [cardinality](#SamenessandPrecedence) of $U$.
      +Let $f(A)$ be the [cardinality](#SamenessandPrecedence) of $A$.
       4. A path $p/c$ consisting of an optional prefix $p/{}$ with $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a structured type or $p=p_2$, and a [custom aggregate](#CustomAggregates) $c$ defined on the collection addressed by $p$.  
      -Let $f(U)=c(U)$, if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property.
      +Let $f(A)=c(A)$, if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property.
       
      -_Determination of $U$:_
      +_Determination of $A$:_
       
      -Let $I$ be the input set. If $p$ is absent, let $U=I$ with null values removed.
      +Let $I$ be the input set. If $p$ is absent, let $A=I$ with null values removed.
       
       Otherwise, let $q$ be the portion of $p$ up to and including the last navigation property, if any, and any type-cast segment that immediately follows, and let $r$ be the remainder, if any, of $p$ that contains no navigation properties, such that $p$ equals the concatenated path $q⁄r$. The aggregate transformation considers each entity reached via the path $q$ exactly once. To this end, using the [$\Gamma$ notation](#EvaluationofDataAggregationPaths):
       - If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example ##aggrconflict].) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$.
       - If $q$ is empty, let $E=I$.
       
      -Then, if $r$ is empty, let $U=E$, otherwise let $U=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions.
      +Then, if $r$ is empty, let $A=E$, otherwise let $A=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions.
       
       #### ##subsubsubsec Keyword `as`
       
      @@ -329,12 +329,12 @@ The algorithmic description of this transformation makes use of the following de
       
       The output set of the groupby transformation is constructed in five steps.
       1. [For each](#SamenessandPrecedence) instance $u$ in the input set, a projection is computed that contains only the grouping properties. This projection is $s_G(u,e)$ and the function $s_G(u,p)$ takes an instance and a path relative to the input set as arguments and is computed recursively as follows:
      -   - Let $w$ be an instance of the type of $u$ without properties and without entity-id.
      +   - Let $v$ be an instance of the type of $u$ without properties and without entity-id.
          - For each structural or navigation property $q$ of $u$:
            - If $u$ has a subtype of the type addressed by $p$ and $q$ is only declared on that subtype, let $p'=p/p''/q$ where $p''$ is a type-cast to the subtype, otherwise let $p'=p/q$.
      -     - If $p'$ occurs in $G$, let $w[q]=u[q]$.
      -     - Otherwise, if $p'$ is a prefix of a path in $G$, let $w[q]=s_G(u[q],p')$.
      -   - Return $w$.
      +     - If $p'$ occurs in $G$, let $v[q]=u[q]$.
      +     - Otherwise, if $p'$ is a prefix of a path in $G$, let $v[q]=s_G(u[q],p')$.
      +   - Return $v$.
       2. The input set is split into subsets where two instances are in the same subset if their projections are [the same](#SamenessandPrecedence). If [representations of the same non-transient entity](#SamenessandPrecedence) are encountered during the comparison of two projections, the service MUST assign them to one subset with the merged representation if they are complementary and MUST reject the request if they are contradictory.
       3. The set transformations from the second parameter are applied to each subset, resulting in a new set of potentially different structure and cardinality. Associated with each resulting set is the common projection of the instances in the subset from which the resulting set was computed.
       4. Each set resulting from the previous step is transformed to contain the associated common projection $s$. This transformation is denoted by $\Pi_G(s)$ and is defined below.
      diff --git a/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md b/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md
      index 9b0db53c0..58debcadd 100644
      --- a/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md	
      +++ b/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md	
      @@ -40,14 +40,14 @@ results in
       The `join` and `outerjoin` transformations take as their first parameter $p$ a collection-valued complex property or navigation property, optionally followed by a type-cast segment to address only instances of that derived type or one of its sub-types, followed by the `as` keyword, followed by an [alias](#TypeStructureandContextURL). The optional second parameter specifies a transformation sequence $T$.
       
       [For each](#SamenessandPrecedence) instance $u$ in an [order-preserving loop](#SamenessandPrecedence) over the input set
      -1. the instance collection $U$ addressed by $p$ is identified.
      -2. If $T$ is provided, $U$ is replaced with the result of applying $T$ to $U$.
      -3. In case of an `outerjoin`, if $U$ is empty, a null instance is added to it.
      -4. [For each](#SamenessandPrecedence) instance $w$ in an [order-preserving loop](#SamenessandPrecedence) over $U$
      -   - an instance $x$ is appended to the output set of the transformation.
      -   - The instance $x$ is a clone of $u$ with an additional dynamic property whose name is the given alias and whose value is $w$.
      -   - The property $x$ is a navigation property if $p$ is a collection-valued navigation property, otherwise it is a complex property.
      -   - The property $x$ carries as control information the context URL of $u$.
      +1. the instance collection $A$ addressed by $p$ is identified.
      +2. If $T$ is provided, $A$ is replaced with the result of applying $T$ to $A$.
      +3. In case of an `outerjoin`, if $A$ is empty, a null instance is added to it.
      +4. [For each](#SamenessandPrecedence) instance $v$ in an [order-preserving loop](#SamenessandPrecedence) over $A$
      +   - an instance $w$ is appended to the output set of the transformation.
      +   - The instance $w$ is a clone of $u$ with an additional dynamic property whose name is the given alias and whose value is $v$.
      +   - The property $w$ is a navigation property if $p$ is a collection-valued navigation property, otherwise it is a complex property.
      +   - The property $w$ carries as control information the context URL of $u$.
       
       ::: example
       Example ##ex: all links between products and sales instances
      @@ -135,12 +135,12 @@ Further parameters are one or more transformation sequences followed by the as k
       
       If $p_k$ is single-valued, the transformation sequences MUST consist of only `identity` or `compute` or `addnested` transformations, because these transform one-element collections into one-element collections. This makes it meaningful to speak (in this section only) of a transformation sequence applied to a single instance; this means applying it to a collection containing the single instance and taking as result the single instance from the output set.
       
      -[For each](#SamenessandPrecedence) instance $u$ in $\Gamma(A,p_1/…/p_{k-1})$, let $U=γ(u,p_k/q)$ and let the resource $w$ be
      -- the collection $U$ if $p_k$ is collection-valued
      -- the single instance in $U$ if $p_k$ is single-valued and $U$ is non-empty
      -- undefined if $p_k$ is single-valued and $U$ is empty.
      +[For each](#SamenessandPrecedence) instance $u$ in $\Gamma(A,p_1/…/p_{k-1})$, let $B=γ(u,p_k/q)$ and let the resource $v$ be
      +- the collection $B$ if $p_k$ is collection-valued
      +- the single instance in $B$ if $p_k$ is single-valued and $B$ is non-empty
      +- undefined if $p_k$ is single-valued and $B$ is empty.
       
      -If $w$ is defined, then for each transformation sequence, a dynamic property is added to $u$ as follows: If $p_k$ is a navigation property, the added property is a dynamic navigation property, which is expanded by default, otherwise it is a dynamic structural property. Its name is the alias of the transformation sequence. The value of the added property is the result of the transformation sequence applied to $w$. The dynamic property carries as control information the context URL of $w$.
      +If $v$ is defined, then for each transformation sequence, a dynamic property is added to $u$ as follows: If $p_k$ is a navigation property, the added property is a dynamic navigation property, which is expanded by default, otherwise it is a dynamic structural property. Its name is the alias of the transformation sequence. The value of the added property is the result of the transformation sequence applied to $v$. The dynamic property carries as control information the context URL of $v$.
       
       ::: example
       Example ##ex:
      diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md
      index dd25fd4f0..3d799ab80 100644
      --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md	
      +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md	
      @@ -18,7 +18,7 @@ The parameter lists defined in the following subsections have three mandatory pa
       
       The recursive hierarchy is defined by a parameter pair $(H,Q)$, where $H$ and $Q$ MUST be specified as the first and second parameter. Here, $H$ MUST be an expression of type `Collection(Edm.EntityType)` starting with `$root` that has no multiple occurrences of the same entity. $H$ identifies the collection of node entities forming a recursive hierarchy based on an annotation of their common entity type with term `RecursiveHierarchy` with a `Qualifier` attribute whose value MUST be provided in $Q$. The property paths referenced by `NodeProperty` and `ParentNavigationProperty` in the `RecursiveHierarchy` annotation must be evaluable for the nodes in the recursive hierarchy, otherwise the service MUST reject the request. The `NodeProperty` is denoted by $q$ in this section.
       
      -The third parameter MUST be a data aggregation path $p$ with single- or collection-valued segments whose last segment MUST be a primitive property. The node identifier(s) of an instance $u$ in the input set are the primitive values in $γ(u,p)$ reached via $p$ starting from $u$. Let $p=p_1/…/p_k/s$ with $k≥0$ be the concatenation where each sub-path $p_1,…,p_k$ consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either $s$ consists of one or more single-valued segments or $k≥1$ and ${}/s$ is absent.
      +The third parameter MUST be a data aggregation path $p$ with single- or collection-valued segments whose last segment MUST be a primitive property. The node identifier(s) of an instance $u$ in the input set are the primitive values in $γ(u,p)$ reached via $p$ starting from $u$. Let $p=p_1/…/p_k/r$ with $k≥0$ be the concatenation where each sub-path $p_1,…,p_k$ consists of a collection-valued segment that is optionally followed by a type-cast segment and preceded by zero or more single-valued segments, and either $r$ consists of one or more single-valued segments or $k≥1$ and ${}/r$ is absent.
       
       The recursive hierarchy to be processed can also be a subset $H'$ of $H$. For this case a non-empty sequence $S$ of transformations MAY be specified as an optional parameter whose position varies from transformation to transformation and is given below. In general, let $H'$ be the output set of the transformation sequence $S$ applied to $H$, or $H'=H$ if $S$ is not specified. The transformations in $S$ MUST be listed in the section on [Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) or in the section on [Hierarchical Transformations Preserving the Input Set Structure](#HierarchicalTransformationsPreservingtheInputSetStructure) or be service-defined bound functions whose output set is a subset of the input set.
       
      @@ -57,7 +57,7 @@ F(u)={\tt filter}(\hbox{\tt Aggregation.isdescendant}(\hfill\\
       \quad {\tt Node}=p,\;{\tt Ancestor}=u[p],\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true})).\hfill 
       }$$
       
      -Otherwise $p=p_1/…/p_k/s$ with $k≥1$, in this case the output set of the transformation $F(u)$ is defined as the [union](#HierarchicalTransformations) of the output sets of transformations $G(n)$ applied to the input set for all $n$ in $γ(u,p)$. The output set of $G(n)$ consists of the instances of the input set whose node identifier is an ancestor or descendant of the node identifier $n$:
      +Otherwise $p=p_1/…/p_k/r$ with $k≥1$, in this case the output set of the transformation $F(u)$ is defined as the [union](#HierarchicalTransformations) of the output sets of transformations $G(n)$ applied to the input set for all $n$ in $γ(u,p)$. The output set of $G(n)$ consists of the instances of the input set whose node identifier is an ancestor or descendant of the node identifier $n$:
       
       For `ancestors`,
       $$\matrix{ 
      @@ -68,7 +68,7 @@ G(n)={\tt filter}(\hfill\\
       \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ 
       \hskip5pc \hbox{\tt Aggregation.isancestor}(\hfill\\ 
       \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ 
      -\hskip6pc {\tt Node}=y_k/s,\;{\tt Descendant}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ 
      +\hskip6pc {\tt Node}=y_k/r,\;{\tt Descendant}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ 
       \hskip5pc )\hfill\\ 
       \hskip4pc )\hfill\\ 
       \hskip3pc ⋰\hfill\\ 
      @@ -85,7 +85,7 @@ G(n)={\tt filter}(\hfill\\
       \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ 
       \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ 
       \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ 
      -\hskip6pc {\tt Node}=y_k/s,\;{\tt Ancestor}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ 
      +\hskip6pc {\tt Node}=y_k/r,\;{\tt Ancestor}=n,\;{\tt MaxDistance}=d,\;{\tt IncludeSelf}={\tt true}\hfill\\ 
       \hskip5pc )\hfill\\ 
       \hskip4pc )\hfill\\ 
       \hskip3pc ⋰\hfill\\ 
      @@ -93,7 +93,7 @@ G(n)={\tt filter}(\hfill\\
       \hskip1pc )\hfill\\ 
       )\hfill 
       }$$
      -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/s$ may be absent.
      +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent.
       
       If parameter $d$ is absent, the parameter ${\tt MaxDistance}=d$ is omitted. If `keep start` is absent, the parameter ${\tt IncludeSelf}={\tt true}$ is omitted.
       
      @@ -233,21 +233,21 @@ $F(x)$ is a transformation that determines for the specified node $x$ the instan
       If $p$ contains only single-valued segments, then
       $$F(x)={\tt filter}(p{\tt\ eq\ }x[q]).$$
       
      -Otherwise $p=p_1/…/p_k/s$ with $k≥1$ and
      +Otherwise $p=p_1/…/p_k/r$ with $k≥1$ and
       $$\matrix{ 
       F(x)={\tt filter}(\hfill\\ 
       \hskip1pc p_1/{\tt any}(y_1:\hfill\\ 
       \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ 
       \hskip3pc ⋱\hfill\\ 
       \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ 
      -\hskip5pc y_k/s{\tt\ eq\ }x[q]\hfill\\ 
      +\hskip5pc y_k/r{\tt\ eq\ }x[q]\hfill\\ 
       \hskip4pc )\hfill\\ 
       \hskip3pc ⋰\hfill\\ 
       \hskip2pc )\hfill\\ 
       \hskip1pc )\hfill\\ 
       )\hfill 
       }$$
      -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/s$ may be absent.
      +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent.
       
       ::: example
       Example ##ex: Based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples)
      @@ -340,7 +340,7 @@ F(x)={\tt filter}(\hbox{\tt Aggregation.isdescendant}(\hfill\\
       \quad {\tt Node}=p,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true})).\hfill 
       }$$
       
      -Otherwise $p=p_1/…/p_k/s$ with $k≥1$ and
      +Otherwise $p=p_1/…/p_k/r$ with $k≥1$ and
       $$\matrix{ 
       F(x)={\tt filter}(\hfill\\ 
       \hskip1pc p_1/{\tt any}(y_1:\hfill\\ 
      @@ -349,7 +349,7 @@ F(x)={\tt filter}(\hfill\\
       \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ 
       \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ 
       \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ 
      -\hskip6pc {\tt Node}=y_k/s,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true}\hfill\\ 
      +\hskip6pc {\tt Node}=y_k/r,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true}\hfill\\ 
       \hskip5pc )\hfill\\ 
       \hskip4pc )\hfill\\ 
       \hskip3pc ⋰\hfill\\ 
      @@ -357,7 +357,7 @@ F(x)={\tt filter}(\hfill\\
       \hskip1pc )\hfill\\ 
       )\hfill 
       }$$
      -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/s$ may be absent. (See [example ##rollupcoll] for a case with $k=1$.)
      +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent. (See [example ##rollupcoll] for a case with $k=1$.)
       
       Non-normatively speaking, the effect of the algorithm can be summarized as follows: If $M≥1$ and $\hat F_N(x)$ denotes the collection of all instances that are related to a node $x$ from the recursive hierarchy of the $N$-th `rolluprecursive` operator, then $T$ is applied to each of the intersections of $\hat F_1(χ_1),…,\hat F_M(χ_M)$, as $χ_N$ runs over all nodes of the $N$-th recursive hierarchy for $1≤N≤M$. Into the instances of the resulting output sets the $\Pi_G$ transformations inject information about the nodes $χ_1,…,χ_M$.
       
      diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md
      index 854abfd83..5aa53ad39 100644
      --- a/odata-data-aggregation-ext/7 Examples.md	
      +++ b/odata-data-aggregation-ext/7 Examples.md	
      @@ -186,7 +186,7 @@ results in
       }
       ```
       
      -Note that the base set of the request is `Products`, so there is a result item for product `Pencil` even though there are no sales items. The input set for the aggregation in the third row is $I$ consisting of the pencil, $p=q/r={\tt Sales}/{\tt Amount}$, $E=\Gamma(I,q)$ is empty and $U=\Gamma(E,r)$ is also empty. The sum over the empty collection is null.
      +Note that the base set of the request is `Products`, so there is a result item for product `Pencil` even though there are no sales items. The input set for the aggregation in the third row is $I$ consisting of the pencil, $p=q/r={\tt Sales}/{\tt Amount}$, $E=\Gamma(I,q)$ is empty and $A=\Gamma(E,r)$ is also empty. The sum over the empty collection is null.
       :::
       
       ::: example
      @@ -1208,7 +1208,7 @@ results in
       :::
       
       ::: example
      -Example ##ex_traversecoll: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment $p_1={\tt Sales}$ and $s={\tt SalesOrganization}/{\tt ID}$.
      +Example ##ex_traversecoll: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment $p_1={\tt Sales}$ and $r={\tt SalesOrganization}/{\tt ID}$.
       ```
       GET /service/Products?$apply=traverse(
             $root/SalesOrganizations,
      
      From 884e089fabd64956b5504f9abaa726ea68ace732 Mon Sep 17 00:00:00 2001
      From: D024504 
      Date: Thu, 25 May 2023 15:59:57 +0200
      Subject: [PATCH 010/116] CSDL-XML-Link
      
      ---
       .../odata-data-aggregation-ext.html                      | 9 ++++++++-
       .../odata-data-aggregation-ext.md                        | 5 ++++-
       odata-data-aggregation-ext/8 Conformance.md              | 5 ++++-
       3 files changed, 16 insertions(+), 3 deletions(-)
      
      diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
      index a6679ba02..cbfc89ce7 100644
      --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
      +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
      @@ -6512,7 +6512,14 @@ 
      [OData-CSDL]
      https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/os/odata-csdl-json-v4.01-os.html.
      Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html.

      +href="https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html">https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html.
      +OData Common Schema Definition Language (CSDL) XML Representation +Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. +11 May 2020. OASIS Standard.
      +https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/os/odata-csdl-xml-v4.01-os.html.
      +Latest stage: https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html.

      [OData-JSON]

      OData JSON Format Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Mark Biamonte. 11 May 2020. OASIS Standard.
      diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 6f6b94e84..ed18aac41 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -4520,7 +4520,10 @@ See link in "[Additional artifacts](#AdditionalArtifacts)" section on cover page ###### [OData-CSDL] _OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/os/odata-csdl-json-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html. +Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html. +_OData Common Schema Definition Language (CSDL) XML Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ +https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/os/odata-csdl-xml-v4.01-os.html. +Latest stage: https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html. ###### [OData-JSON] _OData JSON Format Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Mark Biamonte. 11 May 2020. OASIS Standard._ diff --git a/odata-data-aggregation-ext/8 Conformance.md b/odata-data-aggregation-ext/8 Conformance.md index 5082a94bf..8fae32566 100644 --- a/odata-data-aggregation-ext/8 Conformance.md +++ b/odata-data-aggregation-ext/8 Conformance.md @@ -29,7 +29,10 @@ See link in "[Additional artifacts](#AdditionalArtifacts)" section on cover page ###### [OData-CSDL] _OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/os/odata-csdl-json-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html. +Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html. +_OData Common Schema Definition Language (CSDL) XML Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ +https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/os/odata-csdl-xml-v4.01-os.html. +Latest stage: https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html. ###### [OData-JSON] _OData JSON Format Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Mark Biamonte. 11 May 2020. OASIS Standard._ From 3ec305985e35dc9ae2d0baad22dc80e58c0b926e Mon Sep 17 00:00:00 2001 From: D024504 Date: Thu, 25 May 2023 16:57:46 +0200 Subject: [PATCH 011/116] Non-standard roots --- .../odata-data-aggregation-ext.html | 110 +++++++++++------- .../odata-data-aggregation-ext.md | 33 +++--- .../5 Vocabulary for Data Aggregation.md | 15 ++- .../6 Hierarchical Transformations.md | 18 +-- 4 files changed, 105 insertions(+), 71 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 6725975e3..edad64057 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -4013,12 +4013,21 @@

      5.5 segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the -hierarchy (sometimes called "orphans"). +hierarchy.
    8. The ParentNavigationProperty allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. Nodes MUST NOT form cycles when following parent navigation properties.
    9. +
    10. IsRoot is a Boolean value and nodes in the hierarchy +for which this is true are called root nodes. A recursive +hierarchy can have one or more root nodes. The standard definition +for root is "node without parents", which for a single-valued +ParentNavigationProperty is expressed by giving the +IsRoot property a dynamic annotation value OData-CSDL, section 14.4 like in example 51. The standard definition for root is +also implied if the IsRoot property is null or absent.
    11. The term RecursiveHierarchy can only be applied to entity types, and MUST be applied with a qualifier, which is used to @@ -4028,19 +4037,18 @@

      5.5 href="#HierarchyFunctions">hierarchy functions. The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers.

      -

      A node without parent node is a root node, a node is a -child node of its parent nodes, a node without child nodes is a -leaf node. Two nodes with a common parent node are sibling -nodes and so are two root nodes. The descendants of a node -are its child nodes, their child nodes, and so on, up to and including -all leaf nodes that can be reached. A node together with its descendants -forms a sub-hierarchy of the hierarchy. The ancestors -of a node are its parent nodes, the parents of its parent nodes, and so -on, up to and including root nodes that can be reached. A recursive -hierarchy can have one or more root nodes.

      +

      A node is a child node of its parent nodes, a node without +child nodes is a leaf node. Two nodes with a common parent node +are sibling nodes and so are two root nodes. The +descendants of a node are its child nodes, their child nodes, +and so on, up to and including all leaf nodes that can be reached. A +node together with its descendants forms a sub-hierarchy of the +hierarchy. The ancestors of a node are its parent nodes, the +parents of its parent nodes, and so on, until no more parent nodes +exist.

      The term UpNode can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again -annotated with UpNode and so on until a path to the root is +annotated with UpNode and so on until a path to a root is constructed.

      5.5.2.1 @@ -4086,8 +4094,9 @@

      5.5 Hierarchy Examples

      The hierarchy terms can be applied to the Example Data Model.

      -

      Example 51: leveled hierarchies for products and time, and a -recursive hierarchy for the sales organizations

      +

      Example 51: leveled +hierarchies for products and time, and a recursive hierarchy for the +sales organizations

      <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"
                  Version="4.0">
      @@ -4127,12 +4136,18 @@ 

      5.5 PropertyPath="ID" /> <PropertyValue Property="ParentNavigationProperty" PropertyPath="Superordinate" /> - </Record> - </Annotation> - </Annotations> - </Schema> - </edmx:DataServices> -</edmx:Edmx>

      + <PropertyValue Property="IsRoot"> + <Eq> + <Path>Superordinate</Path> + <Null /> + </Eq> + </PropertyValue> + </Record> + </Annotation> + </Annotations> + </Schema> + </edmx:DataServices> +</edmx:Edmx>

      The recursive hierarchy SalesOrgHierarchy can be used in functions with the $filter system query option.

      @@ -4677,8 +4692,8 @@

      5.5

    (See Example 107.)

    Let \(r_1,…,r_n\) be a sequence of -the root nodes of the recursive hierarchy \((H',Q)\) root nodes of the recursive +hierarchy \((H',Q)\) preserving the order of \(H'\) stable-sorted by \(o\). Then the transformation 5.5 class="math inline">\(x\). Let \(c_1,…,c_m\) with \(m≥0\) be an order-preserving sequence of the -children of \(x\) in order-preserving sequence of the children of \(x\) in \((H',Q)\). The recursive formula for \(R(x)\) is as follows:

    If \(h={\tt preorder}\), then 5.5 }

    The algorithm given so far is valid for a single-valued -RecursiveHierarchy/ParentNavigationProperty. The remainder -of this section describes the case where it is collection-valued.

    -

    If the recursive algorithm reaches a node RecursiveHierarchy/ParentNavigationProperty with the standard definition for root. The +remainder of this section describes the general case.

    +

    In the general case, the recursive algorithm can reach a node \(x\) multiple times, via different parents -or ancestors, then the output set contains multiple instances that -include \(σ(x)\). In order to -distinguish these, information about the ancestors up to the root is -injected into each \(σ(x)\) by +or ancestors, or because \(x\) is a +root and a child at the same time. Then the output set contains multiple +instances that include \(σ(x)\). In +order to distinguish these, information about the ancestors up to the +root is injected into each \(σ(x)\) by annotating \(x\) differently before each \(σ(x)\) is computed.

    -

    More precisely, a path-to-the-root is a node More precisely, an path-to-the-root is a node \(x\) that is annotated with the term UpNode from the Aggregation vocabulary OData-VocAggr where the annotation value is the @@ -4794,9 +4812,9 @@

    5.5

    If \(h={\tt postorder}\), then \[R(x)={\tt concat}(R(ρ(c_1,x)),…,R(ρ(c_m,x)),F(x)/\Pi_G(σ(x))).\]

    -

    If there is only one parent, the result is the same as in the -single-parent case, except for the presence of the -Aggregation.UpNode annotations.

    +

    If there is only one parent and the standard definition for root is +in force, the result is the same as in the single-parent case, except +for the presence of the Aggregation.UpNode annotations.

    6.3 Grouping with rolluprecursive

    @@ -4846,10 +4864,10 @@

    5.5 class="math inline">\(Z_N\) is a transformation whose output set is its input set with property \(χ_N\) removed.

    -

    If \(r_1,…,r_n\) are the root nodes -of the recursive hierarchy \((H',Q)\), the transformation \({\tt groupby}((P_1,{\tt +

    If \(r_1,…,r_n\) are the root nodes of the recursive hierarchy +\((H',Q)\), the transformation +\({\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)\) is defined as equivalent to \[{\tt concat}(R(r_1),…,R(r_n))\] with no order defined on the output set.

    @@ -4859,8 +4877,9 @@

    5.5 class="math inline">\(x\) (see (1) below) and then recurs for all children of \(x\) (see (2) below). Its output set is a collection of aggregated instances for all rollup -results. Let \(c_1,…,c_m\) be the -children of \(x\) in \(c_1,…,c_m\) be the children of \(x\) in \((H',Q)\):

    If at least one of \(P_1\) or \(P_2\) is non-empty, then 5.5 }

    The algorithm given so far is valid for a single-valued -RecursiveHierarchy/ParentNavigationProperty. The remainder -of this section describes the case where it is collection-valued. The -function \(ρ(c,x)\) used below -constructs a path-to-the-root and was defined in the RecursiveHierarchy/ParentNavigationProperty with the standard definition for root. The +remainder of this section describes the general case. The function \(ρ(c,x)\) used below constructs a +path-to-the-root and was defined in the traverse section.

    With \(r_1,…,r_n\) as above, \({\tt groupby}((P_1,{\tt diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index c56617e05..8c262c53f 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2412,14 +2412,15 @@ A recursive hierarchy is defined on a collection of entities by associating with A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: -- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy (sometimes called "orphans"). +- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy. - The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. Nodes MUST NOT form cycles when following parent navigation properties. +- `IsRoot` is a Boolean value and nodes in the hierarchy for which this is true are called _root nodes_. A recursive hierarchy can have one or more root nodes. The _standard definition for root_ is "node without parents", which for a single-valued `ParentNavigationProperty` is expressed by giving the `IsRoot` property a dynamic annotation value [OData-CSDL, section 14.4](#ODataCSDL) like in [example 51](#salesorghier). The standard definition for root is also implied if the `IsRoot` property is null or absent. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. -A node without parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. +A node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, until no more parent nodes exist. -The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to the root is constructed. +The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to a root is constructed. #### 5.5.2.1 Hierarchy Functions @@ -2441,7 +2442,7 @@ The following functions are defined: The hierarchy terms can be applied to the Example Data Model. ::: example -Example 51: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations +Example 51: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations ```xml @@ -2481,6 +2482,12 @@ Example 51: leveled hierarchies for products and time, and a recursive hierarchy PropertyPath="ID" /> + + + Superordinate + + + @@ -2795,9 +2802,9 @@ The function $a(u,v,x)$ takes an instance, a path and another instance as argume (See [Example 107](#traversecoll).) -Let $r_1,…,r_n$ be a sequence of the root nodes of the recursive hierarchy $(H',Q)$ [preserving the order](#SamenessandPrecedence) of $H'$ stable-sorted by $o$. Then the transformation ${\tt traverse}(H,Q,p,h,S,o)$ is defined as equivalent to +Let $r_1,…,r_n$ be a sequence of the [root nodes](#RecursiveHierarchy) of the recursive hierarchy $(H',Q)$ [preserving the order](#SamenessandPrecedence) of $H'$ stable-sorted by $o$. Then the transformation ${\tt traverse}(H,Q,p,h,S,o)$ is defined as equivalent to $${\tt concat}(R(r_1),…,R(r_n)).$$ -$R(x)$ is a transformation producing the specified tree order for a sub-hierarchy of $H'$ with root node $x$. Let $c_1,…,c_m$ with $m≥0$ be an [order-preserving sequence](#SamenessandPrecedence) of the children of $x$ in $(H',Q)$. The _recursive formula for $R(x)$_ is as follows: +$R(x)$ is a transformation producing the specified tree order for a sub-hierarchy of $H'$ with root node $x$. Let $c_1,…,c_m$ with $m≥0$ be an [order-preserving sequence](#SamenessandPrecedence) of the [children](#RecursiveHierarchy) of $x$ in $(H',Q)$. The _recursive formula for $R(x)$_ is as follows: If $h={\tt preorder}$, then $$R(x)={\tt concat}(F(x)/\Pi_G(σ(x)),R(c_1),…,R(c_m)).$$ @@ -2838,11 +2845,11 @@ results in ``` ::: -The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty`. The remainder of this section describes the case where it is collection-valued. +The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty` with the [standard definition for root](#RecursiveHierarchy). The remainder of this section describes the general case. -If the recursive algorithm reaches a node $x$ multiple times, via different parents or ancestors, then the output set contains multiple instances that include $σ(x)$. In order to distinguish these, information about the ancestors up to the root is injected into each $σ(x)$ by annotating $x$ differently before each $σ(x)$ is computed. +In the general case, the recursive algorithm can reach a node $x$ multiple times, via different parents or ancestors, or because $x$ is a root and a child at the same time. Then the output set contains multiple instances that include $σ(x)$. In order to distinguish these, information about the ancestors up to the root is injected into each $σ(x)$ by annotating $x$ differently before each $σ(x)$ is computed. -More precisely, a _path-to-the-root_ is a node $x$ that is annotated with the term `UpNode` from the `Aggregation` vocabulary [OData-VocAggr](#ODataVocAggr) where the annotation value is the parent node $y$ such that $R(x)$ appears on the right-hand side of the recursive formula for $R(y)$. The annotation value $y$ is again annotated with `Aggregation.UpNode` and so on until a root is reached. Every instance in the output set of `traverse` is related to one path-to-the-root. +More precisely, an _path-to-the-root_ is a node $x$ that is annotated with the term `UpNode` from the `Aggregation` vocabulary [OData-VocAggr](#ODataVocAggr) where the annotation value is the parent node $y$ such that $R(x)$ appears on the right-hand side of the recursive formula for $R(y)$. The annotation value $y$ is again annotated with `Aggregation.UpNode` and so on until a root is reached. Every instance in the output set of `traverse` is related to one path-to-the-root. The transformation $\Pi_G(σ(x))$ is extended with an additional step between steps 2 and 3 of the function $a_G(u,s,p)$ as defined in the [simple grouping section](#SimpleGrouping): - If $s$ is annotated with `Aggregation.UpNode`, copy the annotation from $s$ to $u$. @@ -2857,7 +2864,7 @@ $$R(x)={\tt concat}(F(x)/\Pi_G(σ(x)),R(ρ(c_1,x)),…,R(ρ(c_m,x))).$$ If $h={\tt postorder}$, then $$R(x)={\tt concat}(R(ρ(c_1,x)),…,R(ρ(c_m,x)),F(x)/\Pi_G(σ(x))).$$ -If there is only one parent, the result is the same as in the single-parent case, except for the presence of the `Aggregation.UpNode` annotations. +If there is only one parent and the standard definition for root is in force, the result is the same as in the single-parent case, except for the presence of the `Aggregation.UpNode` annotations. ## 6.3 Grouping with `rolluprecursive` @@ -2873,11 +2880,11 @@ _The `rolluprecursive` algorithm:_ A property $χ_N$ appears in the algorithm, but is not present in the output set. It is explained later (see [Example 62](#rollupnode)). $Z_N$ is a transformation whose output set is its input set with property $χ_N$ removed. -If $r_1,…,r_n$ are the root nodes of the recursive hierarchy $(H',Q)$, the transformation ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to +If $r_1,…,r_n$ are the [root nodes](#RecursiveHierarchy) of the recursive hierarchy $(H',Q)$, the transformation ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to $${\tt concat}(R(r_1),…,R(r_n))$$ with no order defined on the output set. -$R(x)$ is a transformation that processes the entire sub-hierarchy $F(x)$ rooted at $x$ (see (1) below) and then recurs for all children of $x$ (see (2) below). Its output set is a collection of aggregated instances for all rollup results. Let $c_1,…,c_m$ be the children of $x$ in $(H',Q)$: +$R(x)$ is a transformation that processes the entire sub-hierarchy $F(x)$ rooted at $x$ (see (1) below) and then recurs for all children of $x$ (see (2) below). Its output set is a collection of aggregated instances for all rollup results. Let $c_1,…,c_m$ be the [children](#RecursiveHierarchy) of $x$ in $(H',Q)$: If at least one of $P_1$ or $P_2$ is non-empty, then $$\matrix{ R(x)={\tt concat}(\hfill\\ \quad F(x)/{\tt compute}(x{\tt\ as\ }χ_N)/{\tt groupby}((P_1,P_2),T/Z_N/\Pi_G(σ(x))),\hfill&\tt(1)\\ \quad R(c_1),…,R(c_m)\hfill&\tt(2)\\ ).\hskip25pc }$$ @@ -2992,7 +2999,7 @@ results in ``` ::: -The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty`. The remainder of this section describes the case where it is collection-valued. The function $ρ(c,x)$ used below constructs a path-to-the-root and was defined in the [`traverse`](#Transformationtraverse) section. +The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty` with the [standard definition for root](#RecursiveHierarchy). The remainder of this section describes the general case. The function $ρ(c,x)$ used below constructs a path-to-the-root and was defined in the [`traverse`](#Transformationtraverse) section. With $r_1,…,r_n$ as above, ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to $${\tt concat}(R(ρ(r_1,{\tt null}),…,R(ρ(r_n,{\tt null}))),$$ diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index 03458f956..a0be7fb1b 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -172,14 +172,15 @@ A recursive hierarchy is defined on a collection of entities by associating with A recursive hierarchy does not need to be as uniform as a leveled hierarchy. The recursive hierarchy is described in the model by an annotation of the entity type with the complex term `RecursiveHierarchy` with these properties: -- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy (sometimes called "orphans"). +- The `NodeProperty` contains a path with single-valued segments ending in a primitive property. This path points to the property holding the node identifier of the node in the hierarchy. Entities for which this path evaluates to null are not nodes of the hierarchy. - The `ParentNavigationProperty` allows navigation to the instance or instances representing the parent nodes. It MUST be a collection-valued or nullable single-valued navigation property path that addresses the entity type annotated with this term. Nodes MUST NOT form cycles when following parent navigation properties. +- `IsRoot` is a Boolean value and nodes in the hierarchy for which this is true are called _root nodes_. A recursive hierarchy can have one or more root nodes. The _standard definition for root_ is "node without parents", which for a single-valued `ParentNavigationProperty` is expressed by giving the `IsRoot` property a dynamic annotation value [OData-CSDL, section 14.4](#ODataCSDL) like in [example ##salesorghier]. The standard definition for root is also implied if the `IsRoot` property is null or absent. The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. -A node without parent node is a _root node_, a node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, up to and including root nodes that can be reached. A recursive hierarchy can have one or more root nodes. +A node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, until no more parent nodes exist. -The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to the root is constructed. +The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to a root is constructed. #### ##subsubsubsec Hierarchy Functions @@ -201,7 +202,7 @@ The following functions are defined: The hierarchy terms can be applied to the Example Data Model. ::: example -Example ##ex: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations +Example ##ex_salesorghier: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations ```xml @@ -241,6 +242,12 @@ Example ##ex: leveled hierarchies for products and time, and a recursive hierarc PropertyPath="ID" /> + + + Superordinate + + + diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md index c66a557c6..e2d994dd3 100644 --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md @@ -219,9 +219,9 @@ The function $a(u,v,x)$ takes an instance, a path and another instance as argume (See [Example ##traversecoll].) -Let $r_1,…,r_n$ be a sequence of the root nodes of the recursive hierarchy $(H',Q)$ [preserving the order](#SamenessandPrecedence) of $H'$ stable-sorted by $o$. Then the transformation ${\tt traverse}(H,Q,p,h,S,o)$ is defined as equivalent to +Let $r_1,…,r_n$ be a sequence of the [root nodes](#RecursiveHierarchy) of the recursive hierarchy $(H',Q)$ [preserving the order](#SamenessandPrecedence) of $H'$ stable-sorted by $o$. Then the transformation ${\tt traverse}(H,Q,p,h,S,o)$ is defined as equivalent to $${\tt concat}(R(r_1),…,R(r_n)).$$ -$R(x)$ is a transformation producing the specified tree order for a sub-hierarchy of $H'$ with root node $x$. Let $c_1,…,c_m$ with $m≥0$ be an [order-preserving sequence](#SamenessandPrecedence) of the children of $x$ in $(H',Q)$. The _recursive formula for $R(x)$_ is as follows: +$R(x)$ is a transformation producing the specified tree order for a sub-hierarchy of $H'$ with root node $x$. Let $c_1,…,c_m$ with $m≥0$ be an [order-preserving sequence](#SamenessandPrecedence) of the [children](#RecursiveHierarchy) of $x$ in $(H',Q)$. The _recursive formula for $R(x)$_ is as follows: If $h={\tt preorder}$, then $$R(x)={\tt concat}(F(x)/\Pi_G(σ(x)),R(c_1),…,R(c_m)).$$ @@ -274,11 +274,11 @@ results in ``` ::: -The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty`. The remainder of this section describes the case where it is collection-valued. +The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty` with the [standard definition for root](#RecursiveHierarchy). The remainder of this section describes the general case. -If the recursive algorithm reaches a node $x$ multiple times, via different parents or ancestors, then the output set contains multiple instances that include $σ(x)$. In order to distinguish these, information about the ancestors up to the root is injected into each $σ(x)$ by annotating $x$ differently before each $σ(x)$ is computed. +In the general case, the recursive algorithm can reach a node $x$ multiple times, via different parents or ancestors, or because $x$ is a root and a child at the same time. Then the output set contains multiple instances that include $σ(x)$. In order to distinguish these, information about the ancestors up to the root is injected into each $σ(x)$ by annotating $x$ differently before each $σ(x)$ is computed. -More precisely, a _path-to-the-root_ is a node $x$ that is annotated with the term `UpNode` from the `Aggregation` vocabulary [OData-VocAggr](#ODataVocAggr) where the annotation value is the parent node $y$ such that $R(x)$ appears on the right-hand side of the recursive formula for $R(y)$. The annotation value $y$ is again annotated with `Aggregation.UpNode` and so on until a root is reached. Every instance in the output set of `traverse` is related to one path-to-the-root. +More precisely, an _path-to-the-root_ is a node $x$ that is annotated with the term `UpNode` from the `Aggregation` vocabulary [OData-VocAggr](#ODataVocAggr) where the annotation value is the parent node $y$ such that $R(x)$ appears on the right-hand side of the recursive formula for $R(y)$. The annotation value $y$ is again annotated with `Aggregation.UpNode` and so on until a root is reached. Every instance in the output set of `traverse` is related to one path-to-the-root. The transformation $\Pi_G(σ(x))$ is extended with an additional step between steps 2 and 3 of the function $a_G(u,s,p)$ as defined in the [simple grouping section](#SimpleGrouping): - If $s$ is annotated with `Aggregation.UpNode`, copy the annotation from $s$ to $u$. @@ -293,7 +293,7 @@ $$R(x)={\tt concat}(F(x)/\Pi_G(σ(x)),R(ρ(c_1,x)),…,R(ρ(c_m,x))).$$ If $h={\tt postorder}$, then $$R(x)={\tt concat}(R(ρ(c_1,x)),…,R(ρ(c_m,x)),F(x)/\Pi_G(σ(x))).$$ -If there is only one parent, the result is the same as in the single-parent case, except for the presence of the `Aggregation.UpNode` annotations. +If there is only one parent and the standard definition for root is in force, the result is the same as in the single-parent case, except for the presence of the `Aggregation.UpNode` annotations. ## ##subsec Grouping with `rolluprecursive` @@ -309,11 +309,11 @@ _The `rolluprecursive` algorithm:_ A property $χ_N$ appears in the algorithm, but is not present in the output set. It is explained later (see [Example ##rollupnode]). $Z_N$ is a transformation whose output set is its input set with property $χ_N$ removed. -If $r_1,…,r_n$ are the root nodes of the recursive hierarchy $(H',Q)$, the transformation ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to +If $r_1,…,r_n$ are the [root nodes](#RecursiveHierarchy) of the recursive hierarchy $(H',Q)$, the transformation ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to $${\tt concat}(R(r_1),…,R(r_n))$$ with no order defined on the output set. -$R(x)$ is a transformation that processes the entire sub-hierarchy $F(x)$ rooted at $x$ (see (1) below) and then recurs for all children of $x$ (see (2) below). Its output set is a collection of aggregated instances for all rollup results. Let $c_1,…,c_m$ be the children of $x$ in $(H',Q)$: +$R(x)$ is a transformation that processes the entire sub-hierarchy $F(x)$ rooted at $x$ (see (1) below) and then recurs for all children of $x$ (see (2) below). Its output set is a collection of aggregated instances for all rollup results. Let $c_1,…,c_m$ be the [children](#RecursiveHierarchy) of $x$ in $(H',Q)$: If at least one of $P_1$ or $P_2$ is non-empty, then $$\matrix{ @@ -457,7 +457,7 @@ results in ``` ::: -The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty`. The remainder of this section describes the case where it is collection-valued. The function $ρ(c,x)$ used below constructs a path-to-the-root and was defined in the [`traverse`](#Transformationtraverse) section. +The algorithm given so far is valid for a single-valued `RecursiveHierarchy/ParentNavigationProperty` with the [standard definition for root](#RecursiveHierarchy). The remainder of this section describes the general case. The function $ρ(c,x)$ used below constructs a path-to-the-root and was defined in the [`traverse`](#Transformationtraverse) section. With $r_1,…,r_n$ as above, ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to $${\tt concat}(R(ρ(r_1,{\tt null}),…,R(ρ(r_n,{\tt null}))),$$ From 125b493e13dcd1931eb6b9352d8853fa7326d8d5 Mon Sep 17 00:00:00 2001 From: D024504 Date: Thu, 25 May 2023 17:59:08 +0200 Subject: [PATCH 012/116] Avoid overlong line --- .../odata-data-aggregation-ext.html | 97 ++++++++++--------- .../odata-data-aggregation-ext.md | 3 +- .../5 Vocabulary for Data Aggregation.md | 3 +- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index edad64057..062e62d35 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -4100,54 +4100,55 @@

    5.5
    <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"
                Version="4.0">
    - <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/vocabularies/Org.OData.Aggregation.V1.xml">
    -  <edmx:Include Alias="Aggregation"
    -                Namespace="Org.OData.Aggregation.V1" />
    - </edmx:Reference>
    - <edmx:DataServices>
    -  <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm"
    -          Alias="SalesModel" Namespace="org.example.odata.salesservice">
    -   <Annotations Target="SalesModel.Product">
    -    <Annotation Term="Aggregation.LeveledHierarchy"
    -                Qualifier="ProductHierarchy">
    -        <Collection>
    -          <PropertyPath>Category/Name</PropertyPath>
    -          <PropertyPath>Name</PropertyPath>
    -        </Collection>
    -    </Annotation>
    -   </Annotations>
    -
    -   <Annotations Target="SalesModel.Time">
    -    <Annotation Term="Aggregation.LeveledHierarchy"
    -                Qualifier="TimeHierarchy">
    -       <Collection>
    -         <PropertyPath>Year</PropertyPath>
    -         <PropertyPath>Quarter</PropertyPath>
    -         <PropertyPath>Month</PropertyPath>
    -       </Collection>
    -    </Annotation>
    -   </Annotations>
    -
    -   <Annotations Target="SalesModel.SalesOrganization">
    -    <Annotation Term="Aggregation.RecursiveHierarchy"
    -                Qualifier="SalesOrgHierarchy">
    -     <Record>
    -      <PropertyValue Property="NodeProperty"
    -                     PropertyPath="ID" />
    -      <PropertyValue Property="ParentNavigationProperty"
    -                     PropertyPath="Superordinate" />
    -      <PropertyValue Property="IsRoot">
    -       <Eq>
    -        <Path>Superordinate</Path>
    -        <Null />
    -       </Eq>
    -      </PropertyValue>
    -     </Record>
    -    </Annotation>
    -   </Annotations>
    -  </Schema>
    - </edmx:DataServices>
    -</edmx:Edmx>
    + <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata-data-aggregation- + ext/v4.0/cs01/vocabularies/Org.OData.Aggregation.V1.xml"> + <edmx:Include Alias="Aggregation" + Namespace="Org.OData.Aggregation.V1" /> + </edmx:Reference> + <edmx:DataServices> + <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" + Alias="SalesModel" Namespace="org.example.odata.salesservice"> + <Annotations Target="SalesModel.Product"> + <Annotation Term="Aggregation.LeveledHierarchy" + Qualifier="ProductHierarchy"> + <Collection> + <PropertyPath>Category/Name</PropertyPath> + <PropertyPath>Name</PropertyPath> + </Collection> + </Annotation> + </Annotations> + + <Annotations Target="SalesModel.Time"> + <Annotation Term="Aggregation.LeveledHierarchy" + Qualifier="TimeHierarchy"> + <Collection> + <PropertyPath>Year</PropertyPath> + <PropertyPath>Quarter</PropertyPath> + <PropertyPath>Month</PropertyPath> + </Collection> + </Annotation> + </Annotations> + + <Annotations Target="SalesModel.SalesOrganization"> + <Annotation Term="Aggregation.RecursiveHierarchy" + Qualifier="SalesOrgHierarchy"> + <Record> + <PropertyValue Property="NodeProperty" + PropertyPath="ID" /> + <PropertyValue Property="ParentNavigationProperty" + PropertyPath="Superordinate" /> + <PropertyValue Property="IsRoot"> + <Eq> + <Path>Superordinate</Path> + <Null /> + </Eq> + </PropertyValue> + </Record> + </Annotation> + </Annotations> + </Schema> + </edmx:DataServices> +</edmx:Edmx>

    The recursive hierarchy SalesOrgHierarchy can be used in functions with the $filter system query option.

    diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 8c262c53f..672245abc 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2446,7 +2446,8 @@ Example 51: leveled hierarchies ```xml - + diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index a0be7fb1b..b419ea8ea 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -206,7 +206,8 @@ Example ##ex_salesorghier: leveled hierarchies for products and time, and a recu ```xml - + From ec3f3e0537a407ba5a8979b63837bd8f6f779257 Mon Sep 17 00:00:00 2001 From: Ralf Handl Date: Thu, 25 May 2023 19:44:40 +0200 Subject: [PATCH 013/116] First three parts (#11) --- .../odata-data-aggregation-ext.html | 76 ++++++++++--------- .../odata-data-aggregation-ext.md | 30 ++++---- odata-data-aggregation-ext/0 frontmatter.md | 6 +- odata-data-aggregation-ext/1 Introduction.md | 14 ++-- ...1 Fundamentals of Input and Output Sets.md | 10 +-- 5 files changed, 73 insertions(+), 63 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index a6679ba02..c12dfe716 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -163,9 +163,14 @@

    Additional
  • OData Aggregation ABNF Construction Rules Version 4.0: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-abnf.txt
  • OData Aggregation ABNF Test Cases: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-testcases.xml
  • -
  • OData Aggregation Vocabulary: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-testcases.yaml
  • +
  • OData Aggregation Vocabulary: +
  • This specification is related to:

    @@ -481,12 +486,12 @@

    1.1 Type – a primitive type other than Edm.Stream or subtypes of Edm.Geography or Edm.Geometry

  • Data Aggregation Path – a -path that consists of one or more segments separated by a forward slash. -Segments are names of declared or dynamic structural or navigation -properties, or type-cast segments consisting of the (optionally -qualified) name of a structured type that is derived from the type -identified by the preceding path segment to reach properties declared by -the derived type.
  • +path that consists of one or more segments joined together by forward +slashes (/). Segments are names of declared or dynamic +structural or navigation properties, or type-cast segments consisting of +the (optionally qualified) name of a structured type that is derived +from the type identified by the preceding path segment to reach +properties declared by the derived type.
  • Expression – derived from the commonExpr rule (see OData-ABNF)
  • @@ -567,12 +572,12 @@

    1.1

    2 Overview

    -

    Open Data (OData) services expose a data model that describes the -schema of the service in terms of the Entity Data Model (EDM, see OData-CSDL) and then allows for querying data in -terms of this model. The responses returned by an OData service are -based on that data model and retain the relationships between the -entities in the model.

    +

    Open Data Protocol (OData) services expose a data model that +describes the schema of the service in terms of the Entity Data Model +(EDM, see OData-CSDL) and then allows for +querying data in terms of this model. The responses returned by an OData +service are based on that data model and retain the relationships +between the entities in the model.

    Extending the OData query features with simple aggregation capabilities avoids cluttering OData services with an exponential number of explicitly modeled "aggregation level entities" or else restricting @@ -1091,18 +1096,18 @@

    2 -

    The Amount property in the Sale entity type is an aggregatable property, and the -properties of the related entity types are groupable. These can be +

    The Amount property in the Sale entity type +is an aggregatable property, and +the properties of the related entity types are groupable. These can be arranged in four hierarchies:

    • Product hierarchy based on groupable properties of the Category -and Product entity types
    • -
    • Customer hierarchy based on Country -and Customer
    • -
    • Time hierarchy based on Year, Month -and Date
    • +href="#AggregationCapabilities">groupable properties of the +Category and Product entity types +
    • Customer hierarchy based on +Country and Customer
    • +
    • Time hierarchy based on +Year, Month, and Date
    • SalesOrganization hierarchy based on the recursive association to itself
    @@ -1111,8 +1116,8 @@

    2 and three "dimensions". This document will avoid such terms, as they are heavily overloaded.

    -

    Query extensions and descriptive annotations can both be applied to -normalized as well as partly or fully denormalized schemas.

    +

    Query extensions and descriptive annotations can be applied to +normalized schemas as well as partly or fully denormalized schemas.

    Example 3: The following diagram depicts a denormalized schema for the simple model.

    @@ -1764,7 +1769,7 @@

    2.2 transformation. The output set of the last set transformation in the transformation sequence invoked by the system query option $apply is the result of $apply. This is -consistent with the use of service-defined bindable and composable +consistent with the use of service-defined bound and composable functions in path segments.

    The system query option $apply MUST NOT be used if the resource path addresses a single instance.

    @@ -1814,7 +1819,7 @@

    2.2 the plain-text number of items in the result of $apply. This is similar to the combination of /$count and $filter.

    -

    During serialization of the result of $apply, declared +

    During serialization of the result of $apply declared properties and dynamic properties are represented as defined by the response format. Other properties have been aggregated away and are not represented in the response. The entities returned in the request @@ -1823,8 +1828,8 @@

    2.2

    3.1 Fundamentals of Input and Output Sets

    -

    (The definitions of italicized terms made in this section are used -throughout this text, always with a hyperlink to this section.)

    +

    The definitions of italicized terms made in this section are used +throughout this text, always with a hyperlink to this section.

    3.1.1 Type, Structure and Context URL

    @@ -1861,11 +1866,12 @@

    2.2 properties that occurred in the input set.
  • Instances in an output set can have dynamic properties that did not occur in the input set. The name for such a dynamic property is called -an alias, it is a SimpleIdentifier (see OData-CSDL, -section 17.2). Aliases MUST differ from names of declared properties -in the input type, from names of properties in the first input set, and -from names of properties in the current input set. Aliases in one -collection MUST also differ from each other.
  • +an alias, it is a simple identifier (see OData-CSDL, section 17.2). Aliases MUST differ +from names of declared properties in the input type, from names of +properties in the first input set, and from names of properties in the +current input set. Aliases in one collection MUST also differ from each +other.

    Here is an overview of the structural changes made by different transformations:

    @@ -2058,7 +2064,7 @@

    2.2

    3.1.3 Evaluation of Data Aggregation Paths

    -

    This document specifies when a data +

    This document specifies how a data aggregation path that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request.

    diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 6f6b94e84..32fd62d2e 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -46,8 +46,10 @@ Martin Zurmühl (martin.zurmuehl@sap.com), [SAP SE](http://www.sap.com/) #### Additional artifacts: This document is one component of a Work Product that also includes: * OData Aggregation ABNF Construction Rules Version 4.0: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-abnf.txt -* OData Aggregation ABNF Test Cases: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-testcases.xml -* OData Aggregation Vocabulary: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/vocabularies/Org.OData.Aggregation.V1.xml +* OData Aggregation ABNF Test Cases: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-testcases.yaml +* OData Aggregation Vocabulary: + * https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/vocabularies/Org.OData.Aggregation.V1.json + * https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/vocabularies/Org.OData.Aggregation.V1.xml #### Related work: This specification is related to: @@ -205,7 +207,7 @@ This specification adds aggregation functionality to the Open Data Protocol (ODa This specification defines the following terms: - _Aggregatable Expression_ – an [expression](#Expression) resulting in a value of an [aggregatable primitive type](#AggregatablePrimitiveType) - _Aggregatable Primitive Type_ – a primitive type other than `Edm.Stream` or subtypes of `Edm.Geography` or `Edm.Geometry` -- _Data Aggregation Path_ – a path that consists of one or more segments separated by a forward slash. Segments are names of declared or dynamic structural or navigation properties, or type-cast segments consisting of the (optionally qualified) name of a structured type that is derived from the type identified by the preceding path segment to reach properties declared by the derived type. +- _Data Aggregation Path_ – a path that consists of one or more segments joined together by forward slashes (`/`). Segments are names of declared or dynamic structural or navigation properties, or type-cast segments consisting of the (optionally qualified) name of a structured type that is derived from the type identified by the preceding path segment to reach properties declared by the derived type. - _Expression_ – derived from the `commonExpr` rule (see [OData-ABNF](#ODataABNF)) - _Single-Valued Property Path_ – property path ending in a single-valued primitive, complex, or navigation property @@ -264,7 +266,7 @@ This uses pandoc 3.1.2 from https://github.com/jgm/pandoc/releases/tag/3.1.2. # 2 Overview -Open Data (OData) services expose a data model that describes the schema of the service in terms of the Entity Data Model (EDM, see [OData-CSDL](#ODataCSDL)) and then allows for querying data in terms of this model. The responses returned by an OData service are based on that data model and retain the relationships between the entities in the model. +Open Data Protocol (OData) services expose a data model that describes the schema of the service in terms of the Entity Data Model (EDM, see [OData-CSDL](#ODataCSDL)) and then allows for querying data in terms of this model. The responses returned by an OData service are based on that data model and retain the relationships between the entities in the model. Extending the OData query features with simple aggregation capabilities avoids cluttering OData services with an exponential number of explicitly modeled "aggregation level entities" or else restricting the consumer to a small subset of predefined aggregations. @@ -770,16 +772,16 @@ Example 2: The following diagram depicts a simple model that is used throughout -The Amount property in the Sale entity type is an [aggregatable property](#AggregationCapabilities), and the properties of the related entity types are groupable. These can be arranged in four hierarchies: -- Product hierarchy based on [groupable](#AggregationCapabilities) properties of the Category and Product entity types -- Customer [hierarchy](#LeveledHierarchy) based on Country and Customer -- Time [hierarchy](#LeveledHierarchy) based on Year, Month and Date +The `Amount` property in the `Sale` entity type is an [aggregatable property](#AggregationCapabilities), and the properties of the related entity types are groupable. These can be arranged in four hierarchies: +- Product hierarchy based on [groupable](#AggregationCapabilities) properties of the `Category` and `Product` entity types +- Customer [hierarchy](#LeveledHierarchy) based on `Country` and `Customer` +- Time [hierarchy](#LeveledHierarchy) based on `Year`, `Month`, and `Date` - SalesOrganization [hierarchy](#RecursiveHierarchy) based on the recursive association to itself In the context of Online Analytical Processing (OLAP), this model might be described in terms of a Sales "cube" with an Amount "measure" and three "dimensions". This document will avoid such terms, as they are heavily overloaded. ::: -Query extensions and descriptive annotations can both be applied to normalized as well as partly or fully denormalized schemas. +Query extensions and descriptive annotations can be applied to normalized schemas as well as partly or fully denormalized schemas. ::: example Example 3: The following diagram depicts a denormalized schema for the simple model. @@ -990,7 +992,7 @@ Note that this result contains seven fully qualified aggregate values, followed # 3 System Query Option `$apply` -A _set transformation_ (_transformation_ for short) is an operation on an input set that produces an output set. A _transformation sequence_ is a sequence of set transformations, separated by forward slashes to express that they are consecutively applied. A transformation sequence is invoked using the system query option `$apply` or they appear as a parameter of certain set transformations defined below. The input set of the first set transformation is the collection addressed by the resource path. The output set of each set transformation is the input set for the next set transformation. The output set of the last set transformation in the transformation sequence invoked by the system query option `$apply` is the result of `$apply`. This is consistent with the use of service-defined bindable and composable functions in path segments. +A _set transformation_ (_transformation_ for short) is an operation on an input set that produces an output set. A _transformation sequence_ is a sequence of set transformations, separated by forward slashes to express that they are consecutively applied. A transformation sequence is invoked using the system query option `$apply` or they appear as a parameter of certain set transformations defined below. The input set of the first set transformation is the collection addressed by the resource path. The output set of each set transformation is the input set for the next set transformation. The output set of the last set transformation in the transformation sequence invoked by the system query option `$apply` is the result of `$apply`. This is consistent with the use of service-defined bound and composable functions in path segments. The system query option `$apply` MUST NOT be used if the resource path addresses a single instance. @@ -1012,11 +1014,11 @@ If a data service that supports `$apply` does not support it on the collection i On resource paths ending in `/$count` the system query option `$apply` is evaluated on the set identified by the resource path without the `/$count` segment, the result is the plain-text number of items in the result of `$apply`. This is similar to the combination of `/$count` and `$filter`. -During serialization of the result of `$apply`, declared properties and dynamic properties are represented as defined by the response format. Other properties have been aggregated away and are not represented in the response. The entities returned in the request examples in the following sections that involve aggregation are therefore transient. +During serialization of the result of `$apply` declared properties and dynamic properties are represented as defined by the response format. Other properties have been aggregated away and are not represented in the response. The entities returned in the request examples in the following sections that involve aggregation are therefore transient. ## 3.1 Fundamentals of Input and Output Sets -(The definitions of italicized terms made in this section are used throughout this text, always with a hyperlink to this section.) +The definitions of italicized terms made in this section are used throughout this text, always with a hyperlink to this section. ### 3.1.1 Type, Structure and Context URL @@ -1026,7 +1028,7 @@ The _structure_ of an instance that occurs in an input or output set is defined - Declared properties of the input type or a nested or related type thereof or of a subtype of one of these MUST have their declared type and meaning when they occur in an input or output set. - Single- or collection-valued primitive properties addressed by a property path starting at a non-transient entity MUST keep their values from the addressed resource path collection throughout the transformation sequence. Likewise, single- or collection-valued navigation property paths starting at a non-transient entity MUST keep addressing the same non-transient entities as in the addressed resource path collection. - Instances in an output set need not have all declared or dynamic properties that occurred in the input set. -- Instances in an output set can have dynamic properties that did not occur in the input set. The name for such a dynamic property is called an alias, it is a SimpleIdentifier (see [OData-CSDL, section 17.2](#ODataCSDL)). Aliases MUST differ from names of declared properties in the input type, from names of properties in the first input set, and from names of properties in the current input set. Aliases in one collection MUST also differ from each other. +- Instances in an output set can have dynamic properties that did not occur in the input set. The name for such a dynamic property is called an alias, it is a simple identifier (see [OData-CSDL, section 17.2](#ODataCSDL)). Aliases MUST differ from names of declared properties in the input type, from names of properties in the first input set, and from names of properties in the current input set. Aliases in one collection MUST also differ from each other. Here is an overview of the structural changes made by different transformations: - During [aggregation](#BasicAggregation) or [nest](#Transformationnest), many instances are replaced by one instance, properties that represent the aggregation level are retained, and others are replaced by dynamic properties holding the aggregate value of the many instances or a transformed copy of them. @@ -1079,7 +1081,7 @@ Collections are _the same_ if there is a one-to-one correspondence $f$ between t ### 3.1.3 Evaluation of Data Aggregation Paths -This document specifies when a [data aggregation path](#DataAggregationPath) that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request. +This document specifies how a [data aggregation path](#DataAggregationPath) that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request. For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. diff --git a/odata-data-aggregation-ext/0 frontmatter.md b/odata-data-aggregation-ext/0 frontmatter.md index 58eb4e043..9bd05ab9b 100644 --- a/odata-data-aggregation-ext/0 frontmatter.md +++ b/odata-data-aggregation-ext/0 frontmatter.md @@ -46,8 +46,10 @@ Martin Zurmühl (martin.zurmuehl@sap.com), [SAP SE](http://www.sap.com/) #### Additional artifacts: This document is one component of a Work Product that also includes: * OData Aggregation ABNF Construction Rules Version 4.0: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-abnf.txt -* OData Aggregation ABNF Test Cases: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-testcases.xml -* OData Aggregation Vocabulary: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/vocabularies/Org.OData.Aggregation.V1.xml +* OData Aggregation ABNF Test Cases: https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/abnf/odata-aggregation-testcases.yaml +* OData Aggregation Vocabulary: + * https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/vocabularies/Org.OData.Aggregation.V1.json + * https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/csd04/vocabularies/Org.OData.Aggregation.V1.xml #### Related work: This specification is related to: diff --git a/odata-data-aggregation-ext/1 Introduction.md b/odata-data-aggregation-ext/1 Introduction.md index 4705ebf80..3af889c18 100644 --- a/odata-data-aggregation-ext/1 Introduction.md +++ b/odata-data-aggregation-ext/1 Introduction.md @@ -14,7 +14,7 @@ This specification adds aggregation functionality to the Open Data Protocol (ODa This specification defines the following terms: - _Aggregatable Expression_ – an [expression](#Expression) resulting in a value of an [aggregatable primitive type](#AggregatablePrimitiveType) - _Aggregatable Primitive Type_ – a primitive type other than `Edm.Stream` or subtypes of `Edm.Geography` or `Edm.Geometry` -- _Data Aggregation Path_ – a path that consists of one or more segments separated by a forward slash. Segments are names of declared or dynamic structural or navigation properties, or type-cast segments consisting of the (optionally qualified) name of a structured type that is derived from the type identified by the preceding path segment to reach properties declared by the derived type. +- _Data Aggregation Path_ – a path that consists of one or more segments joined together by forward slashes (`/`). Segments are names of declared or dynamic structural or navigation properties, or type-cast segments consisting of the (optionally qualified) name of a structured type that is derived from the type identified by the preceding path segment to reach properties declared by the derived type. - _Expression_ – derived from the `commonExpr` rule (see [OData-ABNF](#ODataABNF)) - _Single-Valued Property Path_ – property path ending in a single-valued primitive, complex, or navigation property @@ -73,7 +73,7 @@ This uses pandoc 3.1.2 from https://github.com/jgm/pandoc/releases/tag/3.1.2. # ##sec Overview -Open Data (OData) services expose a data model that describes the schema of the service in terms of the Entity Data Model (EDM, see [OData-CSDL](#ODataCSDL)) and then allows for querying data in terms of this model. The responses returned by an OData service are based on that data model and retain the relationships between the entities in the model. +Open Data Protocol (OData) services expose a data model that describes the schema of the service in terms of the Entity Data Model (EDM, see [OData-CSDL](#ODataCSDL)) and then allows for querying data in terms of this model. The responses returned by an OData service are based on that data model and retain the relationships between the entities in the model. Extending the OData query features with simple aggregation capabilities avoids cluttering OData services with an exponential number of explicitly modeled "aggregation level entities" or else restricting the consumer to a small subset of predefined aggregations. @@ -579,16 +579,16 @@ Example ##ex: The following diagram depicts a simple model that is used througho -The Amount property in the Sale entity type is an [aggregatable property](#AggregationCapabilities), and the properties of the related entity types are groupable. These can be arranged in four hierarchies: -- Product hierarchy based on [groupable](#AggregationCapabilities) properties of the Category and Product entity types -- Customer [hierarchy](#LeveledHierarchy) based on Country and Customer -- Time [hierarchy](#LeveledHierarchy) based on Year, Month and Date +The `Amount` property in the `Sale` entity type is an [aggregatable property](#AggregationCapabilities), and the properties of the related entity types are groupable. These can be arranged in four hierarchies: +- Product hierarchy based on [groupable](#AggregationCapabilities) properties of the `Category` and `Product` entity types +- Customer [hierarchy](#LeveledHierarchy) based on `Country` and `Customer` +- Time [hierarchy](#LeveledHierarchy) based on `Year`, `Month`, and `Date` - SalesOrganization [hierarchy](#RecursiveHierarchy) based on the recursive association to itself In the context of Online Analytical Processing (OLAP), this model might be described in terms of a Sales "cube" with an Amount "measure" and three "dimensions". This document will avoid such terms, as they are heavily overloaded. ::: -Query extensions and descriptive annotations can both be applied to normalized as well as partly or fully denormalized schemas. +Query extensions and descriptive annotations can be applied to normalized schemas as well as partly or fully denormalized schemas. ::: example Example ##ex: The following diagram depicts a denormalized schema for the simple model. diff --git a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md index f31994040..8d8acd061 100644 --- a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md +++ b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md @@ -2,7 +2,7 @@ # ##sec System Query Option `$apply` -A _set transformation_ (_transformation_ for short) is an operation on an input set that produces an output set. A _transformation sequence_ is a sequence of set transformations, separated by forward slashes to express that they are consecutively applied. A transformation sequence is invoked using the system query option `$apply` or they appear as a parameter of certain set transformations defined below. The input set of the first set transformation is the collection addressed by the resource path. The output set of each set transformation is the input set for the next set transformation. The output set of the last set transformation in the transformation sequence invoked by the system query option `$apply` is the result of `$apply`. This is consistent with the use of service-defined bindable and composable functions in path segments. +A _set transformation_ (_transformation_ for short) is an operation on an input set that produces an output set. A _transformation sequence_ is a sequence of set transformations, separated by forward slashes to express that they are consecutively applied. A transformation sequence is invoked using the system query option `$apply` or they appear as a parameter of certain set transformations defined below. The input set of the first set transformation is the collection addressed by the resource path. The output set of each set transformation is the input set for the next set transformation. The output set of the last set transformation in the transformation sequence invoked by the system query option `$apply` is the result of `$apply`. This is consistent with the use of service-defined bound and composable functions in path segments. The system query option `$apply` MUST NOT be used if the resource path addresses a single instance. @@ -24,11 +24,11 @@ If a data service that supports `$apply` does not support it on the collection i On resource paths ending in `/$count` the system query option `$apply` is evaluated on the set identified by the resource path without the `/$count` segment, the result is the plain-text number of items in the result of `$apply`. This is similar to the combination of `/$count` and `$filter`. -During serialization of the result of `$apply`, declared properties and dynamic properties are represented as defined by the response format. Other properties have been aggregated away and are not represented in the response. The entities returned in the request examples in the following sections that involve aggregation are therefore transient. +During serialization of the result of `$apply` declared properties and dynamic properties are represented as defined by the response format. Other properties have been aggregated away and are not represented in the response. The entities returned in the request examples in the following sections that involve aggregation are therefore transient. ## ##subsec Fundamentals of Input and Output Sets -(The definitions of italicized terms made in this section are used throughout this text, always with a hyperlink to this section.) +The definitions of italicized terms made in this section are used throughout this text, always with a hyperlink to this section. ### ##subsubsec Type, Structure and Context URL @@ -38,7 +38,7 @@ The _structure_ of an instance that occurs in an input or output set is defined - Declared properties of the input type or a nested or related type thereof or of a subtype of one of these MUST have their declared type and meaning when they occur in an input or output set. - Single- or collection-valued primitive properties addressed by a property path starting at a non-transient entity MUST keep their values from the addressed resource path collection throughout the transformation sequence. Likewise, single- or collection-valued navigation property paths starting at a non-transient entity MUST keep addressing the same non-transient entities as in the addressed resource path collection. - Instances in an output set need not have all declared or dynamic properties that occurred in the input set. -- Instances in an output set can have dynamic properties that did not occur in the input set. The name for such a dynamic property is called an alias, it is a SimpleIdentifier (see [OData-CSDL, section 17.2](#ODataCSDL)). Aliases MUST differ from names of declared properties in the input type, from names of properties in the first input set, and from names of properties in the current input set. Aliases in one collection MUST also differ from each other. +- Instances in an output set can have dynamic properties that did not occur in the input set. The name for such a dynamic property is called an alias, it is a simple identifier (see [OData-CSDL, section 17.2](#ODataCSDL)). Aliases MUST differ from names of declared properties in the input type, from names of properties in the first input set, and from names of properties in the current input set. Aliases in one collection MUST also differ from each other. Here is an overview of the structural changes made by different transformations: - During [aggregation](#BasicAggregation) or [nest](#Transformationnest), many instances are replaced by one instance, properties that represent the aggregation level are retained, and others are replaced by dynamic properties holding the aggregate value of the many instances or a transformed copy of them. @@ -91,7 +91,7 @@ Collections are _the same_ if there is a one-to-one correspondence $f$ between t ### ##subsubsec Evaluation of Data Aggregation Paths -This document specifies when a [data aggregation path](#DataAggregationPath) that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request. +This document specifies how a [data aggregation path](#DataAggregationPath) that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request. For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. From 51d8798eacc35b5b4f83952769b4268614bb462b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20Thei=C3=9Fen?= Date: Fri, 26 May 2023 08:00:06 +0200 Subject: [PATCH 014/116] Hierarchy maintenance (#7) --- .../odata-data-aggregation-ext.html | 262 ++++++++++++++---- .../odata-data-aggregation-ext.md | 117 +++++++- odata-data-aggregation-ext/7 Examples.md | 88 ++++++ 3 files changed, 394 insertions(+), 73 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index edad64057..a66aa08c8 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -437,7 +437,9 @@

    Table of Contents

    Aggregation per Rollup Level
  • 7.8 Aggregation in Recursive Hierarchies
  • -
  • 7.9 Transformation +
  • 7.9 Maintaining Recursive +Hierarchies
  • +
  • 7.10 Transformation Sequences
  • 8 Conformance
  • @@ -2245,7 +2247,7 @@

    2.2 service MUST merge them into one occurrence in \(E\) if they are complementary and MUST reject the request if they are contradictory. (See example 119.) If example 122.) If multiple occurrences of the same transient entity are reached, the service MUST keep only one occurrence in \(E\). @@ -6332,7 +6334,149 @@

    7.4 changing the result.

    7.9 +id="79-maintaining-recursive-hierarchies">7.9 +Maintaining Recursive Hierarchies

    +

    Besides changes to the structural properties of the entities in a +hierarchical collection, hierarchy maintenance involves changes to the +parent-child relationships.

    +
    +

    Example 110: Move a sales organization Switzerland under the parent +EMEA Central by setting the reference OData-Protocol section 11.4.6.3 of the parent +navigation property target OData-URL, section +4.4 to a reference to EMEA Central OData-JSON, +section 14:

    +
    PUT /service/SalesOrganizations('Switzerland')/Superordinate/$ref
    +Content-Type: application/json
    +
    +{ "@odata.id": "SalesOrganizations('EMEA Central')" }
    +

    results in 204 No Content.

    +
    +
    +

    Example 111: If the parent navigation property contained a +referential constraint for the key of the target OData-CSDL, section 8.5,

    +
    <EntityType Name="SalesOrganization">
    +  <Key>
    +    <PropertyRef Name="ID" />
    +  </Key>
    +  <Property Name="ID" Type="Edm.String" Nullable="false" />
    +  <Property Name="Name" Type="Edm.String" />
    +  <Property Name="SuperordinateID" Type="Edm.String" />
    +  <NavigationProperty Name="Superordinate"
    +                      Type="SalesModel.SalesOrganization">
    +    <ReferentialConstraint Property="SuperordinateID"
    +                           ReferencedProperty="ID" />
    +  </NavigationProperty>
    +</EntityType>
    +

    then alternatively the property taking part in the referential +constraint OData-Protocol, section 11.4.9.1 +could be changed to EMEA Central:

    +
    PATCH /service/SalesOrganizations('Switzerland')
    +Content-Type: application/json
    +
    +{ "SuperordinateID": "EMEA Central" }
    +
    +

    An entity set where the key property ID differs from the +node identfier property NodeID can contain entities without +node identifier. And by using a non-standard definition of root, even nodes +with node identifier can be unreachable from any root, these are called +orphans.

    +
    +

    ⚠ Example 112: Given the following types of +SalesOrganizations and if only Sales is a root,

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeIDNodeIDSuperordinateID
    root nodeSalesSales
    parent nodeEMEAEMEASales
    child nodeEMEA CentralEMEA CentralEMEA
    not a nodeMarsSales
    true orphanPhobosPhobosMars
    true orphanPhobos South PolePhobos South PolePhobos
    unreachable orphanVenusVenus
    island orphanAtlantisAtlantisAtlantis
    +

    the orphans can appear as descendants:

    +
    GET /service/SalesOrganizations?$apply=descendants(
    +    $root/SalesOrganizations,SalesOrgHierarchy,NodeID,
    +    filter(ID eq 'Phobos'),keep start)
    +  &$select=ID
    +

    results in

    +
    {
    +  "@odata.context": "$metadata#SalesOrganizations(ID)",
    +  "value": [
    +    { "ID": "Phobos" },
    +    { "ID": "Phobos South Pole" }
    +  ]
    +}
    +

    An analogous request for the descendants of Atlantis would fail +because of the cycle.

    +

    Mars, Phobos and Phobos South Pole can be made descendants of the +root node by giving Mars a node identifier:

    +
    PATCH /service/SalesOrganizations('Mars')
    +Content-Type: application/json
    +
    +{ "NodeID": "Mars" }
    +

    An attempt to make the island orphan Atlantis a child of the root +node fails, because it would introduce cycles into the hierarchy.

    +
    +

    7.10 Transformation Sequences

    Applying aggregation first covers the most prominent use cases. The slightly more sophisticated question "how much money is earned with @@ -6341,39 +6485,39 @@

    7.4 be specified in $apply in the order they are to be applied, separated by a forward slash.

    -

    Example 110:

    +

    Example 113:

    GET /service/Sales?$apply=filter(Amount le 1)
         /aggregate(Amount with sum as Total)

    means "filter first, then aggregate", and results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Total)",
    -  "value": [
    -    { "Total@odata.type": "Decimal", "Total": 2 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Total)",
    +  "value": [
    +    { "Total@odata.type": "Decimal", "Total": 2 }
    +  ]
    +}

    Using filter within $apply does not preclude using it as a normal system query option.

    -

    Example 111:

    +

    Example 114:

    GET /service/Sales?$apply=filter(Amount le 2)/groupby((Product/Name),
                                              aggregate(Amount with sum as Total))
                &$filter=Total ge 4

    results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    -  "value": [
    -    { "Product": { "Name": "Paper" },
    -      "Total@odata.type": "Decimal", "Total": 4 },
    -    { "Product": { "Name": "Sugar" },
    -      "Total@odata.type": "Decimal", "Total": 4 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    +  "value": [
    +    { "Product": { "Name": "Paper" },
    +      "Total@odata.type": "Decimal", "Total": 4 },
    +    { "Product": { "Name": "Sugar" },
    +      "Total@odata.type": "Decimal", "Total": 4 }
    +  ]
    +}
    -

    Example 112: Revisiting example 16 for using the +

    Example 115: Revisiting example 16 for using the from keyword with the aggregate function, the request

    GET /service/Sales?$apply=aggregate(Amount from Time with average
    @@ -6387,31 +6531,31 @@ 

    7.4 sets for cities, countries and continents and the obvious associations between them.

    -

    Example 113: getting the population per country with

    +

    Example 116: getting the population per country with

    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))

    results in

    -
    {
    -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    -                                      TotalPopulation)",
    -  "value": [
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    -    ...
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    +                                      TotalPopulation)",
    +  "value": [
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    +    ...
    +  ]
    +}
    -

    Example 114: all countries with megacities and their continents

    +

    Example 117: all countries with megacities and their continents

    GET /service/Cities?$apply=filter(Population ge 10000000)
                        /groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))
    -

    Example 115: all countries with tens of millions of city dwellers and +

    Example 118: all countries with tens of millions of city dwellers and the continents only for these countries

    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                               aggregate(Population with sum as CountryPopulation))
    @@ -6429,7 +6573,7 @@ 

    7.4 as TotalPopulation))

    -

    Example 119: The +

    Example 122: The output set of the concat transformation contains Sales entities multiple times with conflicting related AugmentedProduct entities that cannot be aggregated by the @@ -6469,28 +6613,28 @@

    7.4

    results in an error.

    -

    Example 120: The nest transformation can be used inside +

    Example 123: The nest transformation can be used inside groupby to produce one or more collection-valued properties per group.

    GET /service/Sales?$apply=groupby((Product/Category/ID),
                           nest(groupby((Customer/ID)) as Customers))

    results in

    -
    {
    -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    -  "value": [
    -    { "Product": { "Category": { "ID": "PG1" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] },
    -    { "Product": { "Category": { "ID": "PG2" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] }
    -  ]
    -}
    +
    {
    +  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    +  "value": [
    +    { "Product": { "Category": { "ID": "PG1" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] },
    +    { "Product": { "Category": { "ID": "PG2" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] }
    +  ]
    +}

    8 diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 8c262c53f..bd1e7799f 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -178,7 +178,8 @@ For complete copyright information please see the full Notices section in an App - [7.6 Model Functions as Set Transformations](#ModelFunctionsasSetTransformations) - [7.7 Controlling Aggregation per Rollup Level](#ControllingAggregationperRollupLevel) - [7.8 Aggregation in Recursive Hierarchies](#AggregationinRecursiveHierarchies) - - [7.9 Transformation Sequences](#TransformationSequences) + - [7.9 Maintaining Recursive Hierarchies](#MaintainingRecursiveHierarchies) + - [7.10 Transformation Sequences](#TransformationSequences) - [8 Conformance](#Conformance) - [A References](#References) - [A.1 Normative References](#NormativeReferences) @@ -1122,7 +1123,7 @@ _Determination of $U$:_ Let $I$ be the input set. If $p$ is absent, let $U=I$ with null values removed. Otherwise, let $q$ be the portion of $p$ up to and including the last navigation property, if any, and any type-cast segment that immediately follows, and let $r$ be the remainder, if any, of $p$ that contains no navigation properties, such that $p$ equals the concatenated path $q⁄r$. The aggregate transformation considers each entity reached via the path $q$ exactly once. To this end, using the [$\Gamma$ notation](#EvaluationofDataAggregationPaths): -- If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example 119](#aggrconflict).) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$. +- If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example 122](#aggrconflict).) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$. - If $q$ is empty, let $E=I$. Then, if $r$ is empty, let $U=E$, otherwise let $U=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions. @@ -4317,12 +4318,100 @@ GET /service/Sales?$apply=groupby((rolluprecursive( `traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. ::: -## 7.9 Transformation Sequences +## 7.9 Maintaining Recursive Hierarchies + +Besides changes to the structural properties of the entities in a hierarchical collection, hierarchy maintenance involves changes to the parent-child relationships. + +::: example +Example 110: Move a sales organization Switzerland under the parent EMEA Central by setting the reference [OData-Protocol section 11.4.6.3](#ODataProtocol) of the parent navigation property target [OData-URL, section 4.4](#ODataURL) to a reference to EMEA Central [OData-JSON, section 14](#ODataJSON): +```json +PUT /service/SalesOrganizations('Switzerland')/Superordinate/$ref +Content-Type: application/json + +{ "@odata.id": "SalesOrganizations('EMEA Central')" } +``` +results in `204 No Content`. +::: + +::: example +Example 111: If the parent navigation property contained a referential constraint for the key of the target [OData-CSDL, section 8.5](#ODataCSDL), +```xml + + + + + + + + + + + +``` +then alternatively the property taking part in the referential constraint [OData-Protocol, section 11.4.9.1](#ODataProtocol) could be changed to EMEA Central: +```json +PATCH /service/SalesOrganizations('Switzerland') +Content-Type: application/json + +{ "SuperordinateID": "EMEA Central" } +``` +::: + +An entity set where the key property `ID` differs from the node identfier property `NodeID` can contain entities without node identifier. And by using a non-[standard definition of root](#RecursiveHierarchy), even nodes with node identifier can be unreachable from any root, these are called orphans. + +::: example +⚠ Example 112: Given the following types of `SalesOrganizations` and if only Sales is a root, + +Type|ID|NodeID|SuperordinateID +----|--|------|--------------- +root node|Sales|Sales| +parent node|EMEA|EMEA|Sales +child node|EMEA Central|EMEA Central|EMEA +not a node|Mars||Sales +true orphan|Phobos|Phobos|Mars +true orphan|Phobos South Pole|Phobos South Pole|Phobos +unreachable orphan|Venus|Venus| +island orphan|Atlantis|Atlantis|Atlantis + +the orphans can appear as descendants: +``` +GET /service/SalesOrganizations?$apply=descendants( + $root/SalesOrganizations,SalesOrgHierarchy,NodeID, + filter(ID eq 'Phobos'),keep start) + &$select=ID +``` +results in +```json +{ + "@odata.context": "$metadata#SalesOrganizations(ID)", + "value": [ + { "ID": "Phobos" }, + { "ID": "Phobos South Pole" } + ] +} +``` + +An analogous request for the descendants of Atlantis would fail because of the cycle. + +Mars, Phobos and Phobos South Pole can be made descendants of the root node by giving Mars a node identifier: +```json +PATCH /service/SalesOrganizations('Mars') +Content-Type: application/json + +{ "NodeID": "Mars" } +``` + +An attempt to make the island orphan Atlantis a child of the root node fails, because it would introduce cycles into the hierarchy. +::: + +## 7.10 Transformation Sequences Applying aggregation first covers the most prominent use cases. The slightly more sophisticated question "how much money is earned with small sales" requires filtering the base set before applying the aggregation. To enable this type of question several transformations can be specified in `$apply` in the order they are to be applied, separated by a forward slash. ::: example -Example 110: +Example 113: ``` GET /service/Sales?$apply=filter(Amount le 1) /aggregate(Amount with sum as Total) @@ -4341,7 +4430,7 @@ means "filter first, then aggregate", and results in Using `filter` within `$apply` does not preclude using it as a normal system query option. ::: example -Example 111: +Example 114: ``` GET /service/Sales?$apply=filter(Amount le 2)/groupby((Product/Name), aggregate(Amount with sum as Total)) @@ -4362,7 +4451,7 @@ results in ::: ::: example -Example 112: Revisiting [example 16](#from) for using the `from` keyword with the `aggregate` function, the request +Example 115: Revisiting [example 16](#from) for using the `from` keyword with the `aggregate` function, the request ``` GET /service/Sales?$apply=aggregate(Amount from Time with average as DailyAverage) @@ -4376,7 +4465,7 @@ GET /service/Sales?$apply=groupby((Time),aggregate(Amount with sum as Total)) For further examples, consider another data model containing entity sets for cities, countries and continents and the obvious associations between them. ::: example -Example 113: getting the population per country with +Example 116: getting the population per country with ``` GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), aggregate(Population with sum as TotalPopulation)) @@ -4398,7 +4487,7 @@ results in ::: ::: example -Example 114: all countries with megacities and their continents +Example 117: all countries with megacities and their continents ``` GET /service/Cities?$apply=filter(Population ge 10000000) /groupby((Continent/Name,Country/Name), @@ -4407,7 +4496,7 @@ GET /service/Cities?$apply=filter(Population ge 10000000) ::: ::: example -Example 115: all countries with tens of millions of city dwellers and the continents only for these countries +Example 118: all countries with tens of millions of city dwellers and the continents only for these countries ``` GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), aggregate(Population with sum as CountryPopulation)) @@ -4429,7 +4518,7 @@ GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), ::: ::: example -Example 116: all countries with tens of millions of city dwellers and all continents with cities independent of their size +Example 119: all countries with tens of millions of city dwellers and all continents with cities independent of their size ``` GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), aggregate(Population with sum as CountryPopulation)) @@ -4441,7 +4530,7 @@ GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), ::: ::: example -Example 117: assuming the data model includes a sales order entity set with related sets for order items and customers, the base set as well as the related items can be filtered before aggregation +Example 120: assuming the data model includes a sales order entity set with related sets for order items and customers, the base set as well as the related items can be filtered before aggregation ``` GET /service/SalesOrders?$apply=filter(Status eq 'incomplete') /addnested(Items,filter(not Shipped) as FilteredItems) @@ -4451,7 +4540,7 @@ GET /service/SalesOrders?$apply=filter(Status eq 'incomplete') ::: ::: example -Example 118: assuming that `Amount` is a custom aggregate in addition to the property, determine the total for countries with an `Amount` greater than 1000 +Example 121: assuming that `Amount` is a custom aggregate in addition to the property, determine the total for countries with an `Amount` greater than 1000 ``` GET /service/SalesOrders?$apply= groupby((Customer/Country),aggregate(Amount)) @@ -4461,7 +4550,7 @@ GET /service/SalesOrders?$apply= ::: ::: example -Example 119: The output set of the `concat` transformation contains `Sales` entities multiple times with conflicting related `AugmentedProduct` entities that cannot be aggregated by the second transformation. +Example 122: The output set of the `concat` transformation contains `Sales` entities multiple times with conflicting related `AugmentedProduct` entities that cannot be aggregated by the second transformation. ``` GET /service/Sales?$apply= concat(addnested(Product,compute(0.1 as Discount) as AugmentedProduct), @@ -4472,7 +4561,7 @@ results in an error. ::: ::: example -Example 120: The `nest` transformation can be used inside `groupby` to produce one or more collection-valued properties per group. +Example 123: The `nest` transformation can be used inside `groupby` to produce one or more collection-valued properties per group. ``` GET /service/Sales?$apply=groupby((Product/Category/ID), nest(groupby((Customer/ID)) as Customers)) diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 6fcb7dfe1..36a83c9f8 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -1306,6 +1306,94 @@ GET /service/Sales?$apply=groupby((rolluprecursive( `traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. ::: +## ##subsec Maintaining Recursive Hierarchies + +Besides changes to the structural properties of the entities in a hierarchical collection, hierarchy maintenance involves changes to the parent-child relationships. + +::: example +Example ##ex: Move a sales organization Switzerland under the parent EMEA Central by setting the reference [OData-Protocol section 11.4.6.3](#ODataProtocol) of the parent navigation property target [OData-URL, section 4.4](#ODataURL) to a reference to EMEA Central [OData-JSON, section 14](#ODataJSON): +```json +PUT /service/SalesOrganizations('Switzerland')/Superordinate/$ref +Content-Type: application/json + +{ "@odata.id": "SalesOrganizations('EMEA Central')" } +``` +results in `204 No Content`. +::: + +::: example +Example ##ex: If the parent navigation property contained a referential constraint for the key of the target [OData-CSDL, section 8.5](#ODataCSDL), +```xml + + + + + + + + + + + +``` +then alternatively the property taking part in the referential constraint [OData-Protocol, section 11.4.9.1](#ODataProtocol) could be changed to EMEA Central: +```json +PATCH /service/SalesOrganizations('Switzerland') +Content-Type: application/json + +{ "SuperordinateID": "EMEA Central" } +``` +::: + +An entity set where the key property `ID` differs from the node identfier property `NodeID` can contain entities without node identifier. And by using a non-[standard definition of root](#RecursiveHierarchy), even nodes with node identifier can be unreachable from any root, these are called orphans. + +::: example +⚠ Example ##ex: Given the following types of `SalesOrganizations` and if only Sales is a root, + +Type|ID|NodeID|SuperordinateID +----|--|------|--------------- +root node|Sales|Sales| +parent node|EMEA|EMEA|Sales +child node|EMEA Central|EMEA Central|EMEA +not a node|Mars||Sales +true orphan|Phobos|Phobos|Mars +true orphan|Phobos South Pole|Phobos South Pole|Phobos +unreachable orphan|Venus|Venus| +island orphan|Atlantis|Atlantis|Atlantis + +the orphans can appear as descendants: +``` +GET /service/SalesOrganizations?$apply=descendants( + $root/SalesOrganizations,SalesOrgHierarchy,NodeID, + filter(ID eq 'Phobos'),keep start) + &$select=ID +``` +results in +```json +{ + "@odata.context": "$metadata#SalesOrganizations(ID)", + "value": [ + { "ID": "Phobos" }, + { "ID": "Phobos South Pole" } + ] +} +``` + +An analogous request for the descendants of Atlantis would fail because of the cycle. + +Mars, Phobos and Phobos South Pole can be made descendants of the root node by giving Mars a node identifier: +```json +PATCH /service/SalesOrganizations('Mars') +Content-Type: application/json + +{ "NodeID": "Mars" } +``` + +An attempt to make the island orphan Atlantis a child of the root node fails, because it would introduce cycles into the hierarchy. +::: + ## ##subsec Transformation Sequences Applying aggregation first covers the most prominent use cases. The slightly more sophisticated question "how much money is earned with small sales" requires filtering the base set before applying the aggregation. To enable this type of question several transformations can be specified in `$apply` in the order they are to be applied, separated by a forward slash. From 9acc7b30f71440975fec674c98eeba1ec315e073 Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 08:17:46 +0200 Subject: [PATCH 015/116] fine-tuning --- .../odata-data-aggregation-ext.html | 4 ++-- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md | 2 +- odata-data-aggregation-ext/1 Introduction.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index d7fff90e3..7d84b50b2 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -511,8 +511,8 @@

    1.1 collection
  • \(u,v,w\) – instances in a collection
  • -
  • \(x,y\) – instances in a -hierarchical collection, called nodes
  • +
  • \(x\) – an instance in a +hierarchical collection, called a node
  • \(p,q,r\) – paths
  • \(S,T\) – transformation sequences
  • diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index e7627c781..51d714e7c 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -217,7 +217,7 @@ The following non-exhaustive list contains variables that are used throughout th - $A,B,C$ – collections of instances - $H$ – hierarchical collection - $u,v,w$ – instances in a collection -- $x,y$ – instances in a hierarchical collection, called nodes +- $x$ – an instance in a hierarchical collection, called a node - $p,q,r$ – paths - $S,T$ – transformation sequences - $α$ – aggregate expression, defined [below](#AggregationAlgorithm) diff --git a/odata-data-aggregation-ext/1 Introduction.md b/odata-data-aggregation-ext/1 Introduction.md index 3af889c18..955352c2c 100644 --- a/odata-data-aggregation-ext/1 Introduction.md +++ b/odata-data-aggregation-ext/1 Introduction.md @@ -24,7 +24,7 @@ The following non-exhaustive list contains variables that are used throughout th - $A,B,C$ – collections of instances - $H$ – hierarchical collection - $u,v,w$ – instances in a collection -- $x,y$ – instances in a hierarchical collection, called nodes +- $x$ – an instance in a hierarchical collection, called a node - $p,q,r$ – paths - $S,T$ – transformation sequences - $α$ – aggregate expression, defined [below](#AggregationAlgorithm) From b4ae6d9569aabc874d57deb73da45a92fb49babf Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 08:11:56 +0200 Subject: [PATCH 016/116] fine-tuning --- .../odata-data-aggregation-ext.html | 38 +++++++++---------- .../odata-data-aggregation-ext.md | 16 ++++---- .../6 Hierarchical Transformations.md | 6 +-- odata-data-aggregation-ext/7 Examples.md | 10 ++--- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 431c4bc0a..de8c1c2ab 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -4772,17 +4772,24 @@

    5.5 root is injected into each \(σ(x)\) by annotating \(x\) differently before each \(σ(x)\) is computed.

    -

    More precisely, an path-to-the-root is a node \(x\) that is annotated with the term +

    More precisely, a path-to-the-root is a node \(y\) that is annotated with the term UpNode from the Aggregation vocabulary OData-VocAggr where the annotation value is the -parent node \(y\) such that \(R(x)\) appears on the right-hand side of -the recursive formula for \(R(y)\). The -annotation value \(y\) is again +parent node \(x\) such that \(R(y)\) appears on the right-hand side of +the recursive formula for \(R(x)\). The +annotation value \(x\) is again annotated with Aggregation.UpNode and so on until a root is reached. Every instance in the output set of traverse is related to one path-to-the-root.

    +

    Given a path-to-the-root \(x\) and a +child \(c\) of \(x\), let \(ρ(c,x)\) be the path-to-the-root consisting +of the node \(c\) annotated with +Aggregation.UpNode and value \(x\).

    The transformation \(\Pi_G(σ(x))\) is extended with an additional step between steps 2 and 3 of the function \(a_G(u,s,p)\) as defined in @@ -4793,13 +4800,6 @@

    5.5 class="math inline">\(s\) to \(u\). -

    Given a path-to-the-root \(x\) and a -child \(c\) of \(x\), let \(ρ(c,x)\) be the path-to-the-root consisting -of the node \(c\) annotated with -Aggregation.UpNode and value \(x\).

    The Aggregation.UpNode annotation of a root has value null. With \(r_1,…,r_n\) as above, the transformation \({\tt @@ -6389,7 +6389,7 @@

    7.4 orphans.

    ⚠ Example 112: Given the following types of -SalesOrganizations and if only Sales is a root,

    +SalesOrganizations where only Sales is a root,

    @@ -6450,10 +6450,10 @@

    7.4

    -

    the orphans can appear as descendants:

    -
    GET /service/SalesOrganizations?$apply=descendants(
    +

    the orphan nodes can appear as ancestors:

    +
    GET /service/SalesOrganizations?$apply=ancestors(
         $root/SalesOrganizations,SalesOrgHierarchy,NodeID,
    -    filter(ID eq 'Phobos'),keep start)
    +    filter(ID eq 'Phobos South Pole'),keep start)
       &$select=ID

    results in

    7.4
         { "ID": "Phobos South Pole" }
       ]
     }
    -

    An analogous request for the descendants of Atlantis would fail -because of the cycle.

    +

    An analogous request for the ancestors of Atlantis would fail because +of the cycle.

    Mars, Phobos and Phobos South Pole can be made descendants of the root node by giving Mars a node identifier:

    Date: Fri, 26 May 2023 09:48:26 +0200
    Subject: [PATCH 017/116] extended example 109
    
    ---
     .../odata-data-aggregation-ext.html           | 150 +++++++++++-------
     .../odata-data-aggregation-ext.md             |  33 +++-
     .../6 Hierarchical Transformations.md         |   4 +-
     odata-data-aggregation-ext/7 Examples.md      |  29 +++-
     4 files changed, 154 insertions(+), 62 deletions(-)
    
    diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
    index 7d84b50b2..195bc89c2 100644
    --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
    +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
    @@ -5002,7 +5002,7 @@ 

    5.5

    results in

    {
    -  "@odata.context": "$metadata#Sales(SalesOrganization,
    +  "@odata.context": "$metadata#Sales(SalesOrganization(),
                                          TotalAmountIncl,TotalAmountExcl)",
       "value": [
         { "SalesOrganization": { "ID": "US West", "Name": "US West" },
    @@ -5034,7 +5034,7 @@ 

    5.5

    results in

    {
    -  "@odata.context": "$metadata#Sales(SalesOrganization,TotalAmount)",
    +  "@odata.context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
       "value": [
         { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" },
           "TotalAmount": null },
    @@ -6287,13 +6287,39 @@ 

    7.4 }

    -

    ⚠ Example 109: Assuming an extension of the data model where a +

    ⚠ Example 109: Assume an extension of the data model where a SalesOrganization is associated with one or more instances of ProductCategory, and ProductCategory also -organizes categories in a recursive hierarchy, aggregation of sales -amounts along the sales organization hierarchy could be restricted to -those organizations linked with product category "Car" or a descendant -of it:

    +organizes categories in a recursive hierarchy:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    ProductCategoryparent ProductCategoryassociated SalesOrganizations
    FoodUS, EMEA
    CerealsFoodUS
    Organic cerealsCerealsUS West
    +

    Aggregation of sales amounts along the sales organization hierarchy +could be restricted to those organizations linked with product category +"Cereals" or a descendant of it:

    GET /service/Sales?$apply=groupby((rolluprecursive(
       $root/SalesOrganizations,SalesOrgHierarchy,
       SalesOrganization/ID,
    @@ -6307,11 +6333,27 @@ 

    7.4 descendants( $root/ProductCategories,ProductCategoryHierarchy, ID, - filter(Name eq 'Car'), + filter(Name eq 'Cereals'), keep start)), keep start) )), - aggregate(Amount with sum as TotalAmount))

    + aggregate(Amount with sum as TotalAmount)) + &$expand=SalesOrganization($select=ID,$expand=ProductCategories/$ref)

    +

    results in

    +
    {
    +  "@odata.context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
    +  "value": [
    +    { "SalesOrganization": { "ID": "Sales",   "ProductCategories": [ ] },
    +      "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 },
    +    { "SalesOrganization": { "ID": "US",      "ProductCategories": [
    +      { "@odata.id": "ProductCategories('Cereals')" } ] },
    +      "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 },
    +    { "SalesOrganization": { "ID": "US West", "ProductCategories": [
    +      { "@odata.id": "ProductCategories('Organic cereals')" } ] },
    +      "TotalAmount@odata.type": "Decimal", "TotalAmount":  7 }
    +  ]
    +}

    traverse acts here as a filter, hence preorder could be changed to postorder without changing the result.

    @@ -6330,13 +6372,13 @@

    7.4
    GET /service/Sales?$apply=filter(Amount le 1)
         /aggregate(Amount with sum as Total)

    means "filter first, then aggregate", and results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Total)",
    -  "value": [
    -    { "Total@odata.type": "Decimal", "Total": 2 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Total)",
    +  "value": [
    +    { "Total@odata.type": "Decimal", "Total": 2 }
    +  ]
    +}

    Using filter within $apply does not preclude using it as a normal system query option.

    @@ -6346,16 +6388,16 @@

    7.4 aggregate(Amount with sum as Total)) &$filter=Total ge 4

    results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    -  "value": [
    -    { "Product": { "Name": "Paper" },
    -      "Total@odata.type": "Decimal", "Total": 4 },
    -    { "Product": { "Name": "Sugar" },
    -      "Total@odata.type": "Decimal", "Total": 4 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    +  "value": [
    +    { "Product": { "Name": "Paper" },
    +      "Total@odata.type": "Decimal", "Total": 4 },
    +    { "Product": { "Name": "Sugar" },
    +      "Total@odata.type": "Decimal", "Total": 4 }
    +  ]
    +}

    Example 112: Revisiting example 16 for using the @@ -6376,18 +6418,18 @@

    7.4
    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))

    results in

    -
    {
    -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    -                                      TotalPopulation)",
    -  "value": [
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    -    ...
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    +                                      TotalPopulation)",
    +  "value": [
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    +    ...
    +  ]
    +}

    Example 114: all countries with megacities and their continents

    @@ -6460,22 +6502,22 @@

    7.4
    GET /service/Sales?$apply=groupby((Product/Category/ID),
                           nest(groupby((Customer/ID)) as Customers))

    results in

    -
    {
    -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    -  "value": [
    -    { "Product": { "Category": { "ID": "PG1" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] },
    -    { "Product": { "Category": { "ID": "PG2" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] }
    -  ]
    -}
    +
    {
    +  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    +  "value": [
    +    { "Product": { "Category": { "ID": "PG1" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] },
    +    { "Product": { "Category": { "ID": "PG2" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] }
    +  ]
    +}


    8 diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 51d714e7c..1f5022a9c 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2951,7 +2951,7 @@ GET /service/Sales?$apply=groupby( results in ```json { - "@odata.context": "$metadata#Sales(SalesOrganization, + "@odata.context": "$metadata#Sales(SalesOrganization(), TotalAmountIncl,TotalAmountExcl)", "value": [ { "SalesOrganization": { "ID": "US West", "Name": "US West" }, @@ -2979,7 +2979,7 @@ GET /service/Sales?$apply= results in ```json { - "@odata.context": "$metadata#Sales(SalesOrganization,TotalAmount)", + "@odata.context": "$metadata#Sales(SalesOrganization(),TotalAmount)", "value": [ { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" }, "TotalAmount": null }, @@ -4286,7 +4286,15 @@ results in ::: ::: example -⚠ Example 109: Assuming an extension of the data model where a `SalesOrganization` is associated with one or more instances of `ProductCategory`, and `ProductCategory` also organizes categories in a recursive hierarchy, aggregation of sales amounts along the sales organization hierarchy could be restricted to those organizations linked with product category "Car" or a descendant of it: +⚠ Example 109: Assume an extension of the data model where a `SalesOrganization` is associated with one or more instances of `ProductCategory`, and `ProductCategory` also organizes categories in a recursive hierarchy: + +ProductCategory|parent ProductCategory|associated SalesOrganizations +---------------|----------------------|----------------------------- +Food||US, EMEA +Cereals|Food|US +Organic cereals|Cereals|US West + +Aggregation of sales amounts along the sales organization hierarchy could be restricted to those organizations linked with product category "Cereals" or a descendant of it: ``` GET /service/Sales?$apply=groupby((rolluprecursive( $root/SalesOrganizations,SalesOrgHierarchy, @@ -4301,11 +4309,28 @@ GET /service/Sales?$apply=groupby((rolluprecursive( descendants( $root/ProductCategories,ProductCategoryHierarchy, ID, - filter(Name eq 'Car'), + filter(Name eq 'Cereals'), keep start)), keep start) )), aggregate(Amount with sum as TotalAmount)) + &$expand=SalesOrganization($select=ID,$expand=ProductCategories/$ref) +``` +results in +```json +{ + "@odata.context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)", + "value": [ + { "SalesOrganization": { "ID": "Sales", "ProductCategories": [ ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 }, + { "SalesOrganization": { "ID": "US", "ProductCategories": [ + { "@odata.id": "ProductCategories('Cereals')" } ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 }, + { "SalesOrganization": { "ID": "US West", "ProductCategories": [ + { "@odata.id": "ProductCategories('Organic cereals')" } ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 7 } + ] +} ``` `traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md index 3d799ab80..540920d96 100644 --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md @@ -415,7 +415,7 @@ GET /service/Sales?$apply=groupby( results in ```json { - "@odata.context": "$metadata#Sales(SalesOrganization, + "@odata.context": "$metadata#Sales(SalesOrganization(), TotalAmountIncl,TotalAmountExcl)", "value": [ { "SalesOrganization": { "ID": "US West", "Name": "US West" }, @@ -443,7 +443,7 @@ GET /service/Sales?$apply= results in ```json { - "@odata.context": "$metadata#Sales(SalesOrganization,TotalAmount)", + "@odata.context": "$metadata#Sales(SalesOrganization(),TotalAmount)", "value": [ { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" }, "TotalAmount": null }, diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 5aa53ad39..32715bf91 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -1281,7 +1281,15 @@ results in ::: ::: example -⚠ Example ##ex: Assuming an extension of the data model where a `SalesOrganization` is associated with one or more instances of `ProductCategory`, and `ProductCategory` also organizes categories in a recursive hierarchy, aggregation of sales amounts along the sales organization hierarchy could be restricted to those organizations linked with product category "Car" or a descendant of it: +⚠ Example ##ex: Assume an extension of the data model where a `SalesOrganization` is associated with one or more instances of `ProductCategory`, and `ProductCategory` also organizes categories in a recursive hierarchy: + +ProductCategory|parent ProductCategory|associated SalesOrganizations +---------------|----------------------|----------------------------- +Food||US, EMEA +Cereals|Food|US +Organic cereals|Cereals|US West + +Aggregation of sales amounts along the sales organization hierarchy could be restricted to those organizations linked with product category "Cereals" or a descendant of it: ``` GET /service/Sales?$apply=groupby((rolluprecursive( $root/SalesOrganizations,SalesOrgHierarchy, @@ -1296,11 +1304,28 @@ GET /service/Sales?$apply=groupby((rolluprecursive( descendants( $root/ProductCategories,ProductCategoryHierarchy, ID, - filter(Name eq 'Car'), + filter(Name eq 'Cereals'), keep start)), keep start) )), aggregate(Amount with sum as TotalAmount)) + &$expand=SalesOrganization($select=ID,$expand=ProductCategories/$ref) +``` +results in +```json +{ + "@odata.context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)", + "value": [ + { "SalesOrganization": { "ID": "Sales", "ProductCategories": [ ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 }, + { "SalesOrganization": { "ID": "US", "ProductCategories": [ + { "@odata.id": "ProductCategories('Cereals')" } ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 }, + { "SalesOrganization": { "ID": "US West", "ProductCategories": [ + { "@odata.id": "ProductCategories('Organic cereals')" } ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 7 } + ] +} ``` `traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. From e193e06b478a08b346b64804bc056217fa7b759b Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 09:56:21 +0200 Subject: [PATCH 018/116] corrected example 109 --- .../odata-data-aggregation-ext.html | 119 ++++++++++-------- .../odata-data-aggregation-ext.md | 15 ++- odata-data-aggregation-ext/7 Examples.md | 15 ++- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 195bc89c2..3fe7053fb 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -6347,16 +6347,29 @@

    7.4 { "SalesOrganization": { "ID": "Sales", "ProductCategories": [ ] }, "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 }, { "SalesOrganization": { "ID": "US", "ProductCategories": [ - { "@odata.id": "ProductCategories('Cereals')" } ] }, - "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 }, - { "SalesOrganization": { "ID": "US West", "ProductCategories": [ - { "@odata.id": "ProductCategories('Organic cereals')" } ] }, - "TotalAmount@odata.type": "Decimal", "TotalAmount": 7 } - ] -}

    + { "@odata.id": "ProductCategories('Food')" }, + { "@odata.id": "ProductCategories('Cereals')" } ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 }, + { "SalesOrganization": { "ID": "US West", "ProductCategories": [ + { "@odata.id": "ProductCategories('Organic cereals')" } ] }, + "TotalAmount@odata.type": "Decimal", "TotalAmount": 7 } + ] +}

    traverse acts here as a filter, hence preorder could be changed to postorder without -changing the result.

    +changing the result. If traverse was omitted, the +transformation

    +
    ancestors(
    +  $root/SalesOrganizations,SalesOrgHierarchy,
    +  ID,
    +  descendants(
    +    $root/ProductCategories,ProductCategoryHierarchy,
    +    ProductCategories/ID,
    +    filter(ProductCategories/any(c:c/Name eq 'Cereals')),
    +    keep start),
    +  keep start)
    +

    would determine descendants of sales organizations for "Cereals" and +their ancestors, so US East would appear in the result.

    7.9 @@ -6372,13 +6385,13 @@

    7.4
    GET /service/Sales?$apply=filter(Amount le 1)
         /aggregate(Amount with sum as Total)

    means "filter first, then aggregate", and results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Total)",
    -  "value": [
    -    { "Total@odata.type": "Decimal", "Total": 2 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Total)",
    +  "value": [
    +    { "Total@odata.type": "Decimal", "Total": 2 }
    +  ]
    +}

    Using filter within $apply does not preclude using it as a normal system query option.

    @@ -6388,16 +6401,16 @@

    7.4 aggregate(Amount with sum as Total)) &$filter=Total ge 4

    results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    -  "value": [
    -    { "Product": { "Name": "Paper" },
    -      "Total@odata.type": "Decimal", "Total": 4 },
    -    { "Product": { "Name": "Sugar" },
    -      "Total@odata.type": "Decimal", "Total": 4 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    +  "value": [
    +    { "Product": { "Name": "Paper" },
    +      "Total@odata.type": "Decimal", "Total": 4 },
    +    { "Product": { "Name": "Sugar" },
    +      "Total@odata.type": "Decimal", "Total": 4 }
    +  ]
    +}

    Example 112: Revisiting example 16 for using the @@ -6418,18 +6431,18 @@

    7.4
    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))

    results in

    -
    {
    -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    -                                      TotalPopulation)",
    -  "value": [
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    -    ...
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    +                                      TotalPopulation)",
    +  "value": [
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    +    ...
    +  ]
    +}

    Example 114: all countries with megacities and their continents

    @@ -6502,22 +6515,22 @@

    7.4
    GET /service/Sales?$apply=groupby((Product/Category/ID),
                           nest(groupby((Customer/ID)) as Customers))

    results in

    -
    {
    -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    -  "value": [
    -    { "Product": { "Category": { "ID": "PG1" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] },
    -    { "Product": { "Category": { "ID": "PG2" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] }
    -  ]
    -}
    +
    {
    +  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    +  "value": [
    +    { "Product": { "Category": { "ID": "PG1" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] },
    +    { "Product": { "Category": { "ID": "PG2" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] }
    +  ]
    +}


    8 diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 1f5022a9c..d6f7f5d3a 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -4324,6 +4324,7 @@ results in { "SalesOrganization": { "ID": "Sales", "ProductCategories": [ ] }, "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 }, { "SalesOrganization": { "ID": "US", "ProductCategories": [ + { "@odata.id": "ProductCategories('Food')" }, { "@odata.id": "ProductCategories('Cereals')" } ] }, "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 }, { "SalesOrganization": { "ID": "US West", "ProductCategories": [ @@ -4333,7 +4334,19 @@ results in } ``` -`traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. +`traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. If `traverse` was omitted, the transformation +``` +ancestors( + $root/SalesOrganizations,SalesOrgHierarchy, + ID, + descendants( + $root/ProductCategories,ProductCategoryHierarchy, + ProductCategories/ID, + filter(ProductCategories/any(c:c/Name eq 'Cereals')), + keep start), + keep start) +``` +would determine descendants of sales organizations for "Cereals" and their ancestors, so US East would appear in the result. ::: ## 7.9 Transformation Sequences diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 32715bf91..3b22d6b1a 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -1319,6 +1319,7 @@ results in { "SalesOrganization": { "ID": "Sales", "ProductCategories": [ ] }, "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 }, { "SalesOrganization": { "ID": "US", "ProductCategories": [ + { "@odata.id": "ProductCategories('Food')" }, { "@odata.id": "ProductCategories('Cereals')" } ] }, "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 }, { "SalesOrganization": { "ID": "US West", "ProductCategories": [ @@ -1328,7 +1329,19 @@ results in } ``` -`traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. +`traverse` acts here as a filter, hence `preorder` could be changed to `postorder` without changing the result. If `traverse` was omitted, the transformation +``` +ancestors( + $root/SalesOrganizations,SalesOrgHierarchy, + ID, + descendants( + $root/ProductCategories,ProductCategoryHierarchy, + ProductCategories/ID, + filter(ProductCategories/any(c:c/Name eq 'Cereals')), + keep start), + keep start) +``` +would determine descendants of sales organizations for "Cereals" and their ancestors, so US East would appear in the result. ::: ## ##subsec Transformation Sequences From d77197446a47a18fb4ccc0db4dc0a7dd0829f542 Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 10:24:37 +0200 Subject: [PATCH 019/116] rephrased --- .../odata-data-aggregation-ext.html | 19 ++++++++++++------- .../odata-data-aggregation-ext.md | 12 ++++++------ odata-data-aggregation-ext/1 Introduction.md | 2 +- ...1 Fundamentals of Input and Output Sets.md | 4 ++-- .../3.2 Basic Aggregation.md | 2 +- odata-data-aggregation-ext/7 Examples.md | 2 +- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 3fe7053fb..9453e159f 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -322,7 +322,8 @@

    Table of Contents

  • 3.2.2 Transformation concat
  • -
  • 3.2.3 Transformation groupby +
  • 3.2.3 Transformation +groupby
    • 3.2.3.1 Simple Grouping
    • 3.2.3.2 Grouping with @@ -502,8 +503,8 @@

      1.1

      1.1.2 Acronyms and Abbreviations

      -

      The following non-exhaustive list contains variables that are used -throughout this document:

      +

      The following non-exhaustive list contains variable names that are +used throughout this document:

      • \(A,B,C\) – collections of instances
      • @@ -2086,7 +2087,10 @@

        2.2 class="math inline">\(u\) in \(A\) that is not null. The function \(γ(u,p)\) takes a non-null value and a path -as arguments and is recursively defined as follows:

        +as arguments and returns a collection of structured instances or +primitive values, depending on the type of the final segment of \(p\). It is recursively defined as +follows:

        1. If \(p\) is an empty path, let \(B\) be a collection with 2.2

        This notation is extended to the case of an empty path \(e\) by setting \(\Gamma(A,e)=A\). Note every \(\Gamma(A,e)=A\). Note every instance \(u\) in \(\Gamma(A,p)\) occurs also in \(A\) or nested into 3.2.1.2 the structures imposed by the two transformation sequences.

        3.2.3 -Transformation groupby

        +Transformation groupby

        The groupby transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second @@ -6369,7 +6373,8 @@

        7.4 keep start), keep start)

        would determine descendants of sales organizations for "Cereals" and -their ancestors, so US East would appear in the result.

        +their ancestor sales organizations, so US East would appear in the +result.

        7.9 diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index d6f7f5d3a..016cd7e81 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -128,7 +128,7 @@ For complete copyright information please see the full Notices section in an App - [3.2.1.4 Aggregate Expression `$count`](#AggregateExpressioncount) - [3.2.1.5 Keyword `from`](#Keywordfrom) - [3.2.2 Transformation `concat`](#Transformationconcat) - - [3.2.3 Transformation groupby](#Transformationgroupby) + - [3.2.3 Transformation `groupby`](#Transformationgroupby) - [3.2.3.1 Simple Grouping](#SimpleGrouping) - [3.2.3.2 Grouping with `rollup`](#Groupingwithrollup) - [3.3 Transformations Preserving the Input Set Structure](#TransformationsPreservingtheInputSetStructure) @@ -213,7 +213,7 @@ This specification defines the following terms: ### 1.1.2 Acronyms and Abbreviations -The following non-exhaustive list contains variables that are used throughout this document: +The following non-exhaustive list contains variable names that are used throughout this document: - $A,B,C$ – collections of instances - $H$ – hierarchical collection - $u,v,w$ – instances in a collection @@ -1085,7 +1085,7 @@ This document specifies how a [data aggregation path](#DataAggregationPath) that For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. -To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and is recursively defined as follows: +To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and returns a collection of structured instances or primitive values, depending on the type of the final segment of $p$. It is recursively defined as follows: 1. If $p$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9. 2. Let $p_1$ be the first segment of $p$ and $p_2$ the remainder, if any, such that $p$ equals the concatenated path $p_1/p_2$. 3. If $p_1$ is a type-cast segment and $u$ is of its type or a subtype thereof, let $v=u$ and continue with step 8. @@ -1096,7 +1096,7 @@ To this end, the following notation is used in the subsequent sections: If $A$ i 8. Let $B=γ(v,p_2)$. 9. Return $B$. -This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every $u$ in $\Gamma(A,p)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,p)$" effectively changes $A$. +This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every instance $u$ in $\Gamma(A,p)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,p)$" effectively changes $A$. ## 3.2 Basic Aggregation @@ -1417,7 +1417,7 @@ Note that two Sales entities with the second highest amount 4 exist in the input The output set of `concat` has a mixed form consisting of the structures imposed by the two transformation sequences. -### 3.2.3 Transformation groupby +### 3.2.3 Transformation `groupby` The `groupby` transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the [input type](#TypeStructureandContextURL) without properties and without entity-id. @@ -4346,7 +4346,7 @@ ancestors( keep start), keep start) ``` -would determine descendants of sales organizations for "Cereals" and their ancestors, so US East would appear in the result. +would determine descendants of sales organizations for "Cereals" and their ancestor sales organizations, so US East would appear in the result. ::: ## 7.9 Transformation Sequences diff --git a/odata-data-aggregation-ext/1 Introduction.md b/odata-data-aggregation-ext/1 Introduction.md index 955352c2c..87b31a18f 100644 --- a/odata-data-aggregation-ext/1 Introduction.md +++ b/odata-data-aggregation-ext/1 Introduction.md @@ -20,7 +20,7 @@ This specification defines the following terms: ### ##subsubsec Acronyms and Abbreviations -The following non-exhaustive list contains variables that are used throughout this document: +The following non-exhaustive list contains variable names that are used throughout this document: - $A,B,C$ – collections of instances - $H$ – hierarchical collection - $u,v,w$ – instances in a collection diff --git a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md index 8d8acd061..5e7f7157f 100644 --- a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md +++ b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md @@ -95,7 +95,7 @@ This document specifies how a [data aggregation path](#DataAggregationPath) that For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. -To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and is recursively defined as follows: +To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and returns a collection of structured instances or primitive values, depending on the type of the final segment of $p$. It is recursively defined as follows: 1. If $p$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9. 2. Let $p_1$ be the first segment of $p$ and $p_2$ the remainder, if any, such that $p$ equals the concatenated path $p_1/p_2$. 3. If $p_1$ is a type-cast segment and $u$ is of its type or a subtype thereof, let $v=u$ and continue with step 8. @@ -106,4 +106,4 @@ To this end, the following notation is used in the subsequent sections: If $A$ i 8. Let $B=γ(v,p_2)$. 9. Return $B$. -This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every $u$ in $\Gamma(A,p)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,p)$" effectively changes $A$. +This notation is extended to the case of an empty path $e$ by setting $\Gamma(A,e)=A$. Note every instance $u$ in $\Gamma(A,p)$ occurs also in $A$ or nested into $A$, therefore an algorithmic step like "Add a dynamic property to each $u$ in $\Gamma(A,p)$" effectively changes $A$. diff --git a/odata-data-aggregation-ext/3.2 Basic Aggregation.md b/odata-data-aggregation-ext/3.2 Basic Aggregation.md index 60ccb935c..2a7f86d00 100644 --- a/odata-data-aggregation-ext/3.2 Basic Aggregation.md +++ b/odata-data-aggregation-ext/3.2 Basic Aggregation.md @@ -317,7 +317,7 @@ Note that two Sales entities with the second highest amount 4 exist in the input The output set of `concat` has a mixed form consisting of the structures imposed by the two transformation sequences. -### ##subsubsec Transformation groupby +### ##subsubsec Transformation `groupby` The `groupby` transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the [input type](#TypeStructureandContextURL) without properties and without entity-id. diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 3b22d6b1a..cb2a3c5ac 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -1341,7 +1341,7 @@ ancestors( keep start), keep start) ``` -would determine descendants of sales organizations for "Cereals" and their ancestors, so US East would appear in the result. +would determine descendants of sales organizations for "Cereals" and their ancestor sales organizations, so US East would appear in the result. ::: ## ##subsec Transformation Sequences From 416ca061af3f93ea67b0d8784badfc071e16357f Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 11:51:21 +0200 Subject: [PATCH 020/116] replace @odata.type with @type etc. --- .../odata-data-aggregation-ext.html | 598 +++++++++--------- .../odata-data-aggregation-ext.md | 598 +++++++++--------- .../3.2 Basic Aggregation.md | 70 +- ...ions Preserving the Input Set Structure.md | 28 +- ...ations Changing the Input Set Structure.md | 54 +- ...5 Expressions Evaluable on a Collection.md | 16 +- .../4 Cross-Joins and Aggregation.md | 16 +- .../5 Vocabulary for Data Aggregation.md | 10 +- .../6 Hierarchical Transformations.md | 38 +- odata-data-aggregation-ext/7 Examples.md | 366 +++++------ 10 files changed, 897 insertions(+), 897 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 9453e159f..654f22160 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -2280,10 +2280,10 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Total, MxA)",
        +  "@context": "$metadata#Sales(Total, MxA)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 24,
        -      "MxA@odata.type": "Decimal", "MxA": 8 }
        +    { "Total@type": "Decimal", "Total": 24,
        +      "MxA@type": "Decimal", "MxA": 8 }
           ]
         }
        @@ -2294,9 +2294,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Tax)",
        +  "@context": "$metadata#Sales(Tax)",
           "value": [
        -    { "Tax@odata.type": "Decimal", "Tax": 2.08 }
        +    { "Tax@type": "Decimal", "Tax": 2.08 }
           ]
         }
        @@ -2333,9 +2333,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 24 }
        +    { "Total@type": "Decimal", "Total": 24 }
           ]
         }
        @@ -2353,9 +2353,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(MinAmount)",
        +  "@context": "$metadata#Sales(MinAmount)",
           "value": [
        -    { "MinAmount@odata.type": "Decimal", "MinAmount": 1 }
        +    { "MinAmount@type": "Decimal", "MinAmount": 1 }
           ]
         }
        @@ -2372,9 +2372,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(MaxAmount)",
        +  "@context": "$metadata#Sales(MaxAmount)",
           "value": [
        -    { "MaxAmount@odata.type": "Decimal", "MaxAmount": 8 }
        +    { "MaxAmount@type": "Decimal", "MaxAmount": 8 }
           ]
         }
        @@ -2396,9 +2396,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(AverageAmount)",
        +  "@context": "$metadata#Sales(AverageAmount)",
           "value": [
        -    { "AverageAmount@odata.type": "Decimal", "AverageAmount": 3.0 }
        +    { "AverageAmount@type": "Decimal", "AverageAmount": 3.0 }
           ]
         }
        @@ -2418,9 +2418,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(DistinctProducts)",
        +  "@context": "$metadata#Sales(DistinctProducts)",
           "value": [
        -    { "DistinctProducts@odata.type": "Decimal", "DistinctProducts": 3 }
        +    { "DistinctProducts@type": "Decimal", "DistinctProducts": 3 }
           ]
         }
        @@ -2446,13 +2446,13 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total,ProductNames)",
        +  "@context": "$metadata#Sales(Customer(Country),Total,ProductNames)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5,
        +      "Total@type": "Decimal", "Total":  5,
               "ProductNames": "Paper,Sugar" },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19,
        +      "Total@type": "Decimal", "Total": 19,
               "ProductNames": "Coffee,Paper,Sugar" }
           ]
         }
        @@ -2473,9 +2473,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(SalesCount)",
        +  "@context": "$metadata#Sales(SalesCount)",
           "value": [
        -    { "SalesCount@odata.type": "Decimal", "SalesCount": 8 }
        +    { "SalesCount@type": "Decimal", "SalesCount": 8 }
           ]
         }
        @@ -2533,9 +2533,9 @@

        3.2.1.2

        and results in the average sales volume per day

        {
        -  "@odata.context": "$metadata#Sales(DailyAverage)",
        +  "@context": "$metadata#Sales(DailyAverage)",
           "value": [
        -    { "DailyAverage@odata.type": "Decimal", "DailyAverage": 3.428571428571429 }
        +    { "DailyAverage@type": "Decimal", "DailyAverage": 3.428571428571429 }
           ]
         }
        @@ -2570,7 +2570,7 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Amount)",
        +  "@context": "$metadata#Sales(Amount)",
           "value": [
             { "ID": 4, "Amount": 8 },
             { "ID": 3, "Amount": 4 },
        @@ -2727,23 +2727,23 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3 },
        +      "Total@type": "Decimal", "Total":  3 },
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  5 },
        +      "Total@type": "Decimal", "Total":  5 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 }
        +      "Total@type": "Decimal", "Total":  2 }
           ]
         }

        @@ -2756,7 +2756,7 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Product(Name),Amount)",
        +  "@context": "$metadata#Sales(Product(Name),Amount)",
           "value": [
             { "Product": { "Name": "Coffee" }, "Amount": 4 },
             { "Product": { "Name": "Coffee" }, "Amount": 8 },
        @@ -2843,31 +2843,31 @@ 

        3.2.1.2

        results in seven entities for the finest grouping level

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),
        +  "@context": "$metadata#Sales(Customer(Country),
                                              Product(Category(Name)),Total)",
           "value": [
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total": 1 },
        +      "Total@type": "Decimal", "Total": 1 },
             ...

        plus additional fifteen rollup entities for subtotals: five without customer name

            { "Customer": { "Country": "USA" },
               "Product":  { "Category": { "Name": "Food" }, "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             ...

        six without product name

            { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Food" } },
        -      "Total@odata.type": "Decimal", "Total":  6 },
        +      "Total@type": "Decimal", "Total":  6 },
             ...

        and four with neither customer nor product name

            { "Customer": { "Country": "USA" },
               "Product":  { "Category": { "Name": "Food" } },
        -      "Total@odata.type": "Decimal", "Total": 14 },
        +      "Total@type": "Decimal", "Total": 14 },
             ...
           ]
         }
        @@ -2974,7 +2974,7 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 7, "Amount": 1 }
        @@ -2987,7 +2987,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -3017,7 +3017,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 2, "Amount": 2 },
        @@ -3034,7 +3034,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -3058,7 +3058,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 2, "Amount": 2 },
        @@ -3074,7 +3074,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 },
        @@ -3097,7 +3097,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 },
        @@ -3137,14 +3137,14 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
        +  "@context": "$metadata#Sales(Product(Name),Total)",
           "value": [
             { "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  8 },
        +      "Total@type": "Decimal", "Total":  8 },
             { "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  4 }
        +      "Total@type": "Decimal", "Total":  4 }
           ]
         }

        @@ -3165,7 +3165,7 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -3192,7 +3192,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 6, "Amount": 2 },
             { "ID": 7, "Amount": 1 }
        @@ -3223,7 +3223,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 4, "Amount": 8 },
             { "ID": 5, "Amount": 4 }
        @@ -3276,16 +3276,16 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(*,Tax)",
        +  "@context": "$metadata#Sales(*,Tax)",
           "value": [
        -    { "ID": 1, "Amount": 1, "Tax@odata.type": "Decimal", "Tax": 0.14 },
        -    { "ID": 2, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.12 },
        -    { "ID": 3, "Amount": 4, "Tax@odata.type": "Decimal", "Tax": 0.24 },
        -    { "ID": 4, "Amount": 8, "Tax@odata.type": "Decimal", "Tax": 0.48 },
        -    { "ID": 5, "Amount": 4, "Tax@odata.type": "Decimal", "Tax": 0.56 },
        -    { "ID": 6, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.12 },
        -    { "ID": 7, "Amount": 1, "Tax@odata.type": "Decimal", "Tax": 0.14 },
        -    { "ID": 8, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.28 }
        +    { "ID": 1, "Amount": 1, "Tax@type": "Decimal", "Tax": 0.14 },
        +    { "ID": 2, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.12 },
        +    { "ID": 3, "Amount": 4, "Tax@type": "Decimal", "Tax": 0.24 },
        +    { "ID": 4, "Amount": 8, "Tax@type": "Decimal", "Tax": 0.48 },
        +    { "ID": 5, "Amount": 4, "Tax@type": "Decimal", "Tax": 0.56 },
        +    { "ID": 6, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.12 },
        +    { "ID": 7, "Amount": 1, "Tax@type": "Decimal", "Tax": 0.14 },
        +    { "ID": 8, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.28 }
           ]
         }

        @@ -3340,39 +3340,39 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Products(ID,Sale())",
        +  "@context": "$metadata#Products(ID,Sale())",
           "value": [
             { "ID": "P1",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 2, "Amount": 2 } },
             { "ID": "P1",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 6, "Amount": 2 } },
             { "ID": "P2",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 3, "Amount": 4 } },
             { "ID": "P2",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 4, "Amount": 8 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 1, "Amount": 1 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 5, "Amount": 4 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 7, "Amount": 1 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 8, "Amount": 2 } }
           ]
         }
        @@ -3383,7 +3383,7 @@

        3.2.1.2 complex property of type SalesModel.SalesComplexType, the complex property Sale would be in the result regardless, and its context would be -"@odata.context": "#SalesModel.SalesComplexType".

        +"@context": "#SalesModel.SalesComplexType".

        Applying outerjoin instead would return an additional instance for product with "ID": "P4" and Sale having a null value.

        @@ -3410,9 +3410,9 @@

        3.2.1.2

        results in

        {
        -  "@odata.context":"$metadata#Sales(Customers())",
        +  "@context":"$metadata#Sales(Customers())",
           "value": [
        -    { "Customers@odata.context": "#Sales(Customer(ID))",
        +    { "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] }
        @@ -3490,26 +3490,26 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Customers(FilteredSales())",
        +  "@context": "$metadata#Customers(FilteredSales())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "USA",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": [{ "ID": "3", "Amount": 4 }]},
             { "ID": "C2", "Name": "Sue", "Country": "USA",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": [{ "ID": "4", "Amount": 8 },
                                 { "ID": "5", "Amount": 4 }]},
             { "ID": "C3", "Name": "Sue", "Country": "Netherlands",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": []},
             { "ID": "C4", "Name": "Luc", "Country": "France",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": []}
           ]
         }

        If Sales was a collection-valued complex property of type SalesModel.SalesComplexType, the context would be -"FilteredSales@odata.context": "#Collection(SalesModel.SalesComplexType)".

        +"FilteredSales@context": "#Collection(SalesModel.SalesComplexType)".

        3.5 @@ -3566,7 +3566,7 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": "4", "Amount": 8 }
           ]
        @@ -3587,7 +3587,7 @@ 

        3.2.1.2

        Both examples result in

        {
        -  "@odata.context": "$metadata#Products",
        +  "@context": "$metadata#Products",
           "value": [
             { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
           ]
        @@ -3609,7 +3609,7 @@ 

        3.2.1.2 the total amount.)

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -3647,7 +3647,7 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": []
         }

        @@ -3669,16 +3669,16 @@

        3.2.1.2

        results in

        {
        -  "@odata.context":"$metadata#Products(Sales(Total))",
        +  "@context":"$metadata#Products(Sales(Total))",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":   12 } ] },
        +      "Sales": [ { "Total@type": "Decimal", "Total":   12 } ] },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":    8 } ] },
        +      "Sales": [ { "Total@type": "Decimal", "Total":    8 } ] },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
               "Sales": [ { "Total": null } ] },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":    4 } ] }
        +      "Sales": [ { "Total@type": "Decimal", "Total":    4 } ] }
           ]
         }

        @@ -3722,7 +3722,7 @@

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Collection(Edm.ComplexType)",
        +  "@context": "$metadata#Collection(Edm.ComplexType)",
           "value": [
             { "Products": { "Name": "Paper" }, "Sales": { "Amount": 1 } },
             { "Products": { "Name": "Sugar" }, "Sales": { "Amount": 2 } },
        @@ -3741,17 +3741,17 @@ 

        3.2.1.2

        results in

        {
        -  "@odata.context": "$metadata#Collection(Edm.ComplexType)",
        +  "@context": "$metadata#Collection(Edm.ComplexType)",
           "value": [
             { "Products": { "Name": "Coffee" },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total": 12 } },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total": 12 } },
             { "Products": { "Name": "Paper"  },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total":  8 } },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total":  8 } },
             { "Products": { "Name": "Sugar"  },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total":  4 } }
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total":  4 } }
           ]
         }

        @@ -4154,7 +4154,7 @@

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA Central",      "Name": "EMEA Central" },
             { "ID": "Sales Netherlands", "Name": "Sales Netherlands" },
        @@ -4178,7 +4178,7 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA Central", "Name": "EMEA Central" },
             { "ID": "EMEA South",   "Name": "EMEA South" },
        @@ -4196,7 +4196,7 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "Sales Office London",   "Name": "Sales Office London" },
             { "ID": "Sales Office New York", "Name": "Sales Office New York" },
        @@ -4215,7 +4215,7 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
        +  "@context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
           "value": [
             { "ID": "Sales Office London",   "Name": "Sales Office London",
               "Superordinate": { "ID": "EMEA United Kingdom" } },
        @@ -4237,7 +4237,7 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#Sales(ID)",
        +  "@context": "$metadata#Sales(ID)",
           "value": [
             { "ID": 6 },
             { "ID": 7 },
        @@ -4470,12 +4470,12 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA",  "Name": "EMEA",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US",    "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "Sales", "Name": "Sales",
               "Superordinate": null }
           ]
        @@ -4493,14 +4493,14 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "US West", "Name": "US West",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } },
             { "ID": "US",      "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US East", "Name": "US East",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } }
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
           ]
         }

        @@ -4517,7 +4517,7 @@

        5.5

        results in

        {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": "4", "Amount": 8,
               "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
        @@ -4737,12 +4737,12 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "US",      "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US East", "Name": "US East",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } }
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
           ]
         }

        @@ -4944,7 +4944,7 @@

        5.5

        results in

        {
        -  "@odata.context":
        +  "@context":
               "$metadata#SalesOrganizations(ID,Name,SubOrgCnt,Superordinate(ID))",
           "value": [
             { "ID": "US West",      "Name": "US West",
        @@ -5006,18 +5006,18 @@ 

        5.5

        results in

        {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(),
        +  "@context": "$metadata#Sales(SalesOrganization(),
                                              TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl":  7,
        -      "TotalAmountExcl@odata.type": "Decimal" ,"TotalAmountExcl":  7 },
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 },
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 19,
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
               "TotalAmountExcl": null },
             { "SalesOrganization": { "ID": "US East", "Name": "US East" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 12,
        -      "TotalAmountExcl@odata.type": "Decimal", "TotalAmountExcl": 12 }
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
        +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 }
           ]
         }

        @@ -5038,7 +5038,7 @@

        5.5

        results in

        {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
        +  "@context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
           "value": [
             { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" },
               "TotalAmount": null },
        @@ -5093,7 +5093,7 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Customers(Name)",
        +  "@context": "$metadata#Customers(Name)",
           "value": [
             { "Name": "Luc" },
             { "Name": "Joe" },
        @@ -5110,7 +5110,7 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Name))",
        +  "@context": "$metadata#Sales(Customer(Name))",
           "value": [
             { "Customer": { "Name": "Joe" } },
             { "Customer": { "Name": "Sue" } }
        @@ -5134,7 +5134,7 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Name,ID))",
        +  "@context": "$metadata#Sales(Customer(Name,ID))",
           "value": [
             { "Customer": { "Name": "Joe", "ID": "C1" } },
             { "Customer": { "Name": "Sue", "ID": "C2" } },
        @@ -5153,7 +5153,7 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer())",
        +  "@context": "$metadata#Sales(Customer())",
           "value": [
             { "Customer": { "ID": "C1", "Name": "Joe", "Country": "USA" } },
             { "Customer": { "ID": "C2", "Name": "Sue", "Country": "USA" } },
        @@ -5169,7 +5169,7 @@ 

        7

        and results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
        +  "@context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
           "value": [
             { "Customer": { "Name": "Joe", "ID": "C1" },
               "Product": { "Name": "Coffee"} },
        @@ -5196,13 +5196,13 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(SalesModel.FoodProduct/Rating,
        +  "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
                                              SalesModel.NonFoodProduct/RatingClass)",
           "value": [
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": 5 },
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": null },
        -    { "@odata.type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
        -    { "@odata.type": "#SalesModel.NonFoodProduct", "RatingClass": null }
        +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
        +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
        +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": null }
           ]
         }

        @@ -5215,10 +5215,10 @@

        7 SalesModel.NonFoodProducts:

        {
        -  "@odata.context": "$metadata#Products(@Core.AnyStructure)",
        +  "@context": "$metadata#Products(@Core.AnyStructure)",
           "value": [
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": 5 },
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": null },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
             { }
           ]
         }
        @@ -5245,12 +5245,12 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(Name,Total)",
        +  "@context": "$metadata#Products(Name,Total)",
           "value": [
        -    { "Name": "Coffee", "Total@odata.type": "Decimal", "Total":   12 },
        -    { "Name": "Paper",  "Total@odata.type": "Decimal", "Total":    8 },
        +    { "Name": "Coffee", "Total@type": "Decimal", "Total":   12 },
        +    { "Name": "Paper",  "Total@type": "Decimal", "Total":    8 },
             { "Name": "Pencil",                                "Total": null },
        -    { "Name": "Sugar",  "Total@odata.type": "Decimal", "Total":    4 }
        +    { "Name": "Sugar",  "Total@type": "Decimal", "Total":    4 }
           ]
         }

        Note that the base set of the request is Products, so @@ -5271,20 +5271,20 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(AggregatedSales())",
        +  "@context": "$metadata#Products(AggregatedSales())",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total": 12 } ] },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total": 12 } ] },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total":  8 } ] },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  8 } ] },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        +      "AggregatedSales@context": "#Sales(Total)",
               "AggregatedSales": [ {                              "Total": null } ] },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total":  4 } ] }
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  4 } ] }
           ]
         }

        @@ -5296,16 +5296,16 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(*,Total)",
        +  "@context": "$metadata#Products(*,Total)",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "Total@odata.type": "Decimal", "Total":  8 },
        +      "Total@type": "Decimal", "Total":  8 },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
                                              "Total": null },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "Total@odata.type": "Decimal", "Total":  4 }
        +      "Total@type": "Decimal", "Total":  4 }
           ]
         }

        The expression $it/Sales refers to the sales of the @@ -5322,17 +5322,17 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(Name,TotalSales())",
        +  "@context": "$metadata#Products(Name,TotalSales())",
           "value": [
             { "Name": "Coffee",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total": 12 } },
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total": 12 } },
             { "Name": "Paper",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total":  8 } },
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total":  8 } },
             { "Name": "Sugar",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total":  4 } }
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total":  4 } }
           ]
         }

        Applying outerjoin instead would return an additional @@ -5346,7 +5346,7 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),AverageAmount)",
        +  "@context": "$metadata#Sales(Customer(Country),AverageAmount)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "AverageAmount": 1.6666666666666667 },
        @@ -5364,12 +5364,12 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(Name,SalesCount)",
        +  "@context": "$metadata#Products(Name,SalesCount)",
           "value": [
        -    { "Name": "Coffee", "SalesCount@odata.type": "Decimal", "SalesCount": 2 },
        -    { "Name": "Paper",  "SalesCount@odata.type": "Decimal", "SalesCount": 4 },
        -    { "Name": "Pencil", "SalesCount@odata.type": "Decimal", "SalesCount": 0 },
        -    { "Name": "Sugar",  "SalesCount@odata.type": "Decimal", "SalesCount": 2 }
        +    { "Name": "Coffee", "SalesCount@type": "Decimal", "SalesCount": 2 },
        +    { "Name": "Paper",  "SalesCount@type": "Decimal", "SalesCount": 4 },
        +    { "Name": "Pencil", "SalesCount@type": "Decimal", "SalesCount": 0 },
        +    { "Name": "Sugar",  "SalesCount@type": "Decimal", "SalesCount": 2 }
           ]
         }

        @@ -5387,23 +5387,23 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Products(Name,AggregatedSales())",
        +  "@context": "$metadata#Products(Name,AggregatedSales())",
           "value": [
             { "Name": "Coffee",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 2,
        -          "TotalAmount@odata.type": "Decimal", "TotalAmount": 12 } ] },
        +          "TotalAmount@type": "Decimal", "TotalAmount": 12 } ] },
             { "Name": "Paper",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 4,
        -          "TotalAmount@odata.type": "Decimal", "TotalAmount":  8 } ] },
        +          "TotalAmount@type": "Decimal", "TotalAmount":  8 } ] },
             { "Name": "Pencil",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 0, "TotalAmount":  null } ] },
             { "Name": "Sugar",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 2,
        -          "TotalAmount@odata.type": "Decimal",  "TotalAmount":  4 } ] }
        +          "TotalAmount@type": "Decimal",  "TotalAmount":  4 } ] }
           ]
         }

        @@ -5417,7 +5417,7 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Products",
        +  "@context": "$metadata#Products",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06 },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14 }
        @@ -5431,7 +5431,7 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Customers",
        +  "@context": "$metadata#Customers",
           "value": [
             { "ID": "C2", "Name": "Sue", "Country": "USA" },
             { "ID": "C1", "Name": "Joe", "Country": "USA" },
        @@ -5448,23 +5448,23 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(*,Contribution)",
        +  "@context": "$metadata#Sales(*,Contribution)",
           "value": [
        -    { "ID": 1, "Amount": 1, "Contribution@odata.type": "Decimal",
        +    { "ID": 1, "Amount": 1, "Contribution@type": "Decimal",
                                     "Contribution": 0.0416666666666667 },
        -    { "ID": 2, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 2, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 },
        -    { "ID": 3, "Amount": 4, "Contribution@odata.type": "Decimal",
        +    { "ID": 3, "Amount": 4, "Contribution@type": "Decimal",
                                     "Contribution": 0.1666666666666667 },
        -    { "ID": 4, "Amount": 8, "Contribution@odata.type": "Decimal",
        +    { "ID": 4, "Amount": 8, "Contribution@type": "Decimal",
                                     "Contribution": 0.3333333333333333 },
        -    { "ID": 5, "Amount": 4, "Contribution@odata.type": "Decimal",
        +    { "ID": 5, "Amount": 4, "Contribution@type": "Decimal",
                                     "Contribution": 0.1666666666666667 },
        -    { "ID": 6, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 6, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 },
        -    { "ID": 7, "Amount": 1, "Contribution@odata.type": "Decimal",
        +    { "ID": 7, "Amount": 1, "Contribution@type": "Decimal",
                                     "Contribution": 0.0416666666666667 },
        -    { "ID": 8, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 8, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 }
           ]
         }
        @@ -5477,7 +5477,7 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Categories",
        +  "@context": "$metadata#Categories",
           "value": [
             { "ID": "PG1", "Name": "Food" }
           ]
        @@ -5495,14 +5495,14 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
        +  "@context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
           "value": [
        -    { "Customer":    { "@odata.id": "Customers('C1')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.2916667 },
        -    { "Customer":    { "@odata.id": "Customers('C2')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.5 },
        -    { "Customer":    { "@odata.id": "Customers('C3')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 }
        +    { "Customer":    { "@id": "Customers('C1')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.2916667 },
        +    { "Customer":    { "@id": "Customers('C2')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.5 },
        +    { "Customer":    { "@id": "Customers('C3')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.2083333 }
           ]
         }

        @@ -5544,18 +5544,18 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Customers(
        +  "@context": "$metadata#Customers(
                                        Addresses(AugmentedSalesOrganization())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "US",
               "Addresses": [
                 { "Locality": "Seattle",
                   "AugmentedSalesOrganization":
        -          { "@odata.context": "#SalesOrganizations/$entity",
        +          { "@context": "#SalesOrganizations/$entity",
                     "ID": "US West", "SalesRegion": "US" } },
                 { "Locality": "DC",
                   "AugmentedSalesOrganization":
        -          { "@odata.context": "#SalesOrganizations/$entity",
        +          { "@context": "#SalesOrganizations/$entity",
                     "ID": "US",      "SalesRegion": "Corporate Sales" } },
               ]
             }, ...
        @@ -5572,28 +5572,28 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Categories(FilteredProducts()",
        +  "@context": "$metadata#Categories(FilteredProducts()",
           "value": [
             { "ID": "PG1", "Name": "Food",
        -      "FilteredProducts@odata.context": "#Products(FilteredSales())",
        +      "FilteredProducts@context": "#Products(FilteredSales())",
               "FilteredProducts": [
                 { "ID": "P1", "Name": "Sugar",  "Color": "White",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [] },
                 { "ID": "P2", "Name": "Coffee", "Color": "Brown",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [ { "ID": 3, "Amount": 4 },
                                      { "ID": 4, "Amount": 8 } ] }
               ]
             },
             { "ID": "PG2", "Name": "Non-Food",
        -      "FilteredProducts@odata.context": "#Products(FilteredSales())",
        +      "FilteredProducts@context": "#Products(FilteredSales())",
               "FilteredProducts": [
                 { "ID": "P3", "Name": "Paper",  "Color": "White",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [ { "ID": 5, "Amount": 4 } ] },
                 { "ID": "P4", "Name": "Pencil", "Color": "Black",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [] }
               ]
             }
        @@ -5621,29 +5621,29 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Customers(GroupedSales())",
        +  "@context": "$metadata#Customers(GroupedSales())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "USA",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { },
                 { }
               ] },
             { "ID": "C2", "Name": "Sue", "Country": "USA",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { }
               ] },
             { "ID": "C3", "Name": "Joe", "Country": "Netherlands",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { }
               ] },
             { "ID": "C4", "Name": "Luc", "Country": "France",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [ ] }
           ]
         }
        @@ -5656,22 +5656,22 @@

        7

        returns the different combinations of products sold per country:

        {
        -  "@odata.context":"$metadata#Customers(Country,ProductSales())",
        +  "@context":"$metadata#Customers(Country,ProductSales())",
           "value": [
             { "Country": "Netherlands",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Paper"  } } },
             { "Country": "Netherlands",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Sugar"  } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Coffee" } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Paper"  } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Sugar"  } } },
             { "Country": "France", "ProductSales": null }
           ]
        @@ -5691,14 +5691,14 @@ 

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
        +  "@context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Actual@odata.type": "Decimal", "Actual":  5,
        -      "Forecast@odata.type": "Decimal", "Forecast": 4 },
        +      "Actual@type": "Decimal", "Actual":  5,
        +      "Forecast@type": "Decimal", "Forecast": 4 },
             { "Customer": { "Country": "USA" },
        -      "Actual@odata.type": "Decimal", "Actual": 19,
        -      "Forecast@odata.type": "Decimal", "Forecast": 21 }
        +      "Actual@type": "Decimal", "Actual": 19,
        +      "Forecast@type": "Decimal", "Forecast": 21 }
           ]
         }

        @@ -5714,7 +5714,7 @@

        7

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Amount)",
        +  "@context": "$metadata#Sales(Customer(Country),Amount)",
           "value": [
             { "Customer": { "Country": "Netherlands" }, "Amount":  5 },
             { "Customer": { "Country": "USA" },         "Amount": 19 }
        @@ -5770,14 +5770,14 @@ 

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
        +  "@context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5,
        -      "AvgAmt@odata.type": "Decimal", "AvgAmt": 1.6666667 },
        +      "Total@type": "Decimal", "Total":  5,
        +      "AvgAmt@type": "Decimal", "AvgAmt": 1.6666667 },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19,
        -      "AvgAmt@odata.type": "Decimal", "AvgAmt": 3.8 }
        +      "Total@type": "Decimal", "Total": 19,
        +      "AvgAmt@type": "Decimal", "AvgAmt": 3.8 }
           ]
         }

        @@ -5793,22 +5793,22 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Products(Name,Total,AggregatedSales())",
        +  "@context": "$metadata#Products(Name,Total,AggregatedSales())",
           "value": [
             { "Name": "Coffee", "Total":   12,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 6 } ] },
             { "Name": "Paper",  "Total":    8,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 2 } ] },
             { "Name": "Pencil", "Total": null,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
               "AggregatedSales": [ { "AvgAmt": null } ] },
             { "Name": "Sugar",  "Total":    4,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 2 } ] }
           ]
         }
        @@ -5823,12 +5823,12 @@

        7.4 much money was made with deals of this amount

        {
        -  "@odata.context": "$metadata#Sales(Amount,Total)",
        +  "@context": "$metadata#Sales(Amount,Total)",
           "value": [
        -    { "Amount": 1, "Total@odata.type": "Decimal", "Total": 2 },
        -    { "Amount": 2, "Total@odata.type": "Decimal", "Total": 6 },
        -    { "Amount": 4, "Total@odata.type": "Decimal", "Total": 8 },
        -    { "Amount": 8, "Total@odata.type": "Decimal", "Total": 8 }
        +    { "Amount": 1, "Total@type": "Decimal", "Total": 2 },
        +    { "Amount": 2, "Total@type": "Decimal", "Total": 6 },
        +    { "Amount": 4, "Total@type": "Decimal", "Total": 8 },
        +    { "Amount": 8, "Total@type": "Decimal", "Total": 8 }
           ]
         }

        @@ -5851,19 +5851,19 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Total)",
           "value": [
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12
        +      "Total@type": "Decimal", "Total": 12
             },
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3
        +      "Total@type": "Decimal", "Total":  3
             },
             { "Customer":{ "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19
        +      "Total@type": "Decimal", "Total": 19
             },
             { "Customer":{ "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5
        +      "Total@type": "Decimal", "Total":  5
             }
           ]
         }
        @@ -5877,22 +5877,22 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
           "value": [
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3
        +      "Total@type": "Decimal", "Total":  3
             },
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2
        +      "Total@type": "Decimal", "Total":  2
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2
        +      "Total@type": "Decimal", "Total":  2
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12
        +      "Total@type": "Decimal", "Total": 12
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  5
        +      "Total@type": "Decimal", "Total":  5
             }
           ]
         }
        @@ -5912,7 +5912,7 @@

        7.4 arises.

        {
        -  "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
        +  "@context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
           "value": [
             { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
               "ID": "3", "Amount": 4, "per": "Customer" },
        @@ -5961,18 +5961,18 @@ 

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Total)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3 },
        +      "Total@type": "Decimal", "Total":  3 },
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total":  7 }
        +      "Total@type": "Decimal", "Total":  7 }
           ]
         }

        Note that these two entities get their values for the Country @@ -6016,24 +6016,24 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(CustomerCountryAverage)",
        +  "@context": "$metadata#Sales(CustomerCountryAverage)",
           "value": [
             { "Customer": { "Country": "USA", "ID": "C1" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage":   7 },
             { "Customer": { "Country": "USA", "ID": "C2" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage":  12 },
             { "Customer": { "Country": "USA" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 9.5 },
             { "Customer": { "Country": "Netherlands", "ID": "C3" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 5 },
             { "Customer": { "Country": "Netherlands" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 5 },
        -    { "CustomerCountryAverage@odata.type":"Decimal",
        +    { "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 7.25 }
           ]
         }
        @@ -6062,17 +6062,17 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(TotalAmount,SalesOrganization())",
        +  "@context": "$metadata#Sales(TotalAmount,SalesOrganization())",
           "value": [
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount": 19,
        +    { "TotalAmount@type": "Decimal", "TotalAmount": 19,
               "SalesOrganization": { "ID": "US",      "Name": "US",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount": 12,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "TotalAmount@type": "Decimal", "TotalAmount": 12,
               "SalesOrganization": { "ID": "US East", "Name": "US East",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount":  7,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "TotalAmount@type": "Decimal", "TotalAmount":  7,
               "SalesOrganization": { "ID": "US West", "Name": "US West",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } }
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } }
           ]
         }

        Note that this example returns the actual total sums regardless of @@ -6095,24 +6095,24 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
        +  "@context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
           "value": [
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "US",           "Name": "US",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 1,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
               "SalesOrganization": { "ID": "US East",      "Name": "US East",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 1,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
               "SalesOrganization": { "ID": "US West",      "Name": "US West",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "EMEA",         "Name": "EMEA",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('EMEA')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 4,
        +        "Superordinate": { "@id": "SalesOrganizations('EMEA')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 4,
               "SalesOrganization": { "ID": "Sales",        "Name": "Sales",
                 "Superordinate": null } }
           ]
        @@ -6201,18 +6201,18 @@ 

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(ID),
        +  "@context": "$metadata#Sales(SalesOrganization(ID),
                                              TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 19,
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
               "TotalAmountExcl": null },
             { "SalesOrganization": { "ID": "US East", "Name": "US East" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 12,
        -      "TotalAmountExcl@odata.type": "Decimal", "TotalAmountExcl": 12 },
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
        +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 },
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl":  7,
        -      "TotalAmountExcl@odata.type": "Decimal" ,"TotalAmountExcl":  7 }
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 }
           ]
         }

        @@ -6236,7 +6236,7 @@

        7.4 class="math inline">\(σ(x)={}\){"Sales": [{"SalesOrganization": {"ID": "US"}}]}.

        {
        -  "@odata.context":
        +  "@context":
               "$metadata#Products(ID,Sales(SalesOrganization(ID)))",
           "value": [
             { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
        @@ -6272,7 +6272,7 @@ 

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Products(Sales(SalesOrganization(ID)),
        +  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),
                                                 SoldProducts)",
           "value": [
             { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
        @@ -6346,17 +6346,17 @@ 

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
        +  "@context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
           "value": [
             { "SalesOrganization": { "ID": "Sales",   "ProductCategories": [ ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 },
        +      "TotalAmount@type": "Decimal", "TotalAmount": 24 },
             { "SalesOrganization": { "ID": "US",      "ProductCategories": [
        -      { "@odata.id": "ProductCategories('Food')" },
        -      { "@odata.id": "ProductCategories('Cereals')" } ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 },
        +      { "@id": "ProductCategories('Food')" },
        +      { "@id": "ProductCategories('Cereals')" } ] },
        +      "TotalAmount@type": "Decimal", "TotalAmount": 19 },
             { "SalesOrganization": { "ID": "US West", "ProductCategories": [
        -      { "@odata.id": "ProductCategories('Organic cereals')" } ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount":  7 }
        +      { "@id": "ProductCategories('Organic cereals')" } ] },
        +      "TotalAmount@type": "Decimal", "TotalAmount":  7 }
           ]
         }

        traverse acts here as a filter, hence @@ -6392,9 +6392,9 @@

        7.4

        means "filter first, then aggregate", and results in

        {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 2 }
        +    { "Total@type": "Decimal", "Total": 2 }
           ]
         }

        @@ -6408,12 +6408,12 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
        +  "@context": "$metadata#Sales(Product(Name),Total)",
           "value": [
             { "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total": 4 },
        +      "Total@type": "Decimal", "Total": 4 },
             { "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total": 4 }
        +      "Total@type": "Decimal", "Total": 4 }
           ]
         }

        @@ -6438,13 +6438,13 @@

        7.4

        results in

        {
        -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
        +  "@context": "$metadata#Cities(Continent(Name),Country(Name),
                                               TotalPopulation)",
           "value": [
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
        -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
        +      "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
        -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
        +      "TotalPopulation@type": "Int32", "TotalPopulation": 1408000000 },
             ...
           ]
         }
        @@ -6522,15 +6522,15 @@

        7.4

        results in

        {
        -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
        +  "@context":"$metadata#Sales(Product(Category(ID)),Customers())",
           "value": [
             { "Product": { "Category": { "ID": "PG1" } },
        -      "Customers@odata.context": "#Sales(Customer(ID))",
        +      "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] },
             { "Product": { "Category": { "ID": "PG2" } },
        -      "Customers@odata.context": "#Sales(Customer(ID))",
        +      "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] }
        diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
        index 016cd7e81..bed978ba7 100644
        --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
        +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
        @@ -1143,10 +1143,10 @@ GET /service/Sales?$apply=aggregate(Amount with sum as Total,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total, MxA)",
        +  "@context": "$metadata#Sales(Total, MxA)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 24,
        -      "MxA@odata.type": "Decimal", "MxA": 8 }
        +    { "Total@type": "Decimal", "Total": 24,
        +      "MxA@type": "Decimal", "MxA": 8 }
           ]
         }
         ```
        @@ -1161,9 +1161,9 @@ GET /service/Sales?$apply=aggregate(Amount mul Product/TaxRate
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Tax)",
        +  "@context": "$metadata#Sales(Tax)",
           "value": [
        -    { "Tax@odata.type": "Decimal", "Tax": 2.08 }
        +    { "Tax@type": "Decimal", "Tax": 2.08 }
           ]
         }
         ```
        @@ -1187,9 +1187,9 @@ GET /service/Sales?$apply=aggregate(Amount with sum as Total)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 24 }
        +    { "Total@type": "Decimal", "Total": 24 }
           ]
         }
         ```
        @@ -1209,9 +1209,9 @@ GET /service/Sales?$apply=aggregate(Amount with min as MinAmount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(MinAmount)",
        +  "@context": "$metadata#Sales(MinAmount)",
           "value": [
        -    { "MinAmount@odata.type": "Decimal", "MinAmount": 1 }
        +    { "MinAmount@type": "Decimal", "MinAmount": 1 }
           ]
         }
         ```
        @@ -1231,9 +1231,9 @@ GET /service/Sales?$apply=aggregate(Amount with max as MaxAmount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(MaxAmount)",
        +  "@context": "$metadata#Sales(MaxAmount)",
           "value": [
        -    { "MaxAmount@odata.type": "Decimal", "MaxAmount": 8 }
        +    { "MaxAmount@type": "Decimal", "MaxAmount": 8 }
           ]
         }
         ```
        @@ -1253,9 +1253,9 @@ GET /service/Sales?$apply=aggregate(Amount with average as AverageAmount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(AverageAmount)",
        +  "@context": "$metadata#Sales(AverageAmount)",
           "value": [
        -    { "AverageAmount@odata.type": "Decimal", "AverageAmount": 3.0 }
        +    { "AverageAmount@type": "Decimal", "AverageAmount": 3.0 }
           ]
         }
         ```
        @@ -1276,9 +1276,9 @@ GET /service/Sales?$apply=aggregate(Product with countdistinct
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(DistinctProducts)",
        +  "@context": "$metadata#Sales(DistinctProducts)",
           "value": [
        -    { "DistinctProducts@odata.type": "Decimal", "DistinctProducts": 3 }
        +    { "DistinctProducts@type": "Decimal", "DistinctProducts": 3 }
           ]
         }
         ```
        @@ -1302,13 +1302,13 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total,ProductNames)",
        +  "@context": "$metadata#Sales(Customer(Country),Total,ProductNames)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5,
        +      "Total@type": "Decimal", "Total":  5,
               "ProductNames": "Paper,Sugar" },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19,
        +      "Total@type": "Decimal", "Total": 19,
               "ProductNames": "Coffee,Paper,Sugar" }
           ]
         }
        @@ -1329,9 +1329,9 @@ GET /service/Sales?$apply=aggregate($count as SalesCount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesCount)",
        +  "@context": "$metadata#Sales(SalesCount)",
           "value": [
        -    { "SalesCount@odata.type": "Decimal", "SalesCount": 8 }
        +    { "SalesCount@type": "Decimal", "SalesCount": 8 }
           ]
         }
         ```
        @@ -1368,9 +1368,9 @@ GET /service/Sales?$apply=groupby((Time),aggregate(Amount with sum as Total))
         and results in the average sales volume per day
         ```json
         {
        -  "@odata.context": "$metadata#Sales(DailyAverage)",
        +  "@context": "$metadata#Sales(DailyAverage)",
           "value": [
        -    { "DailyAverage@odata.type": "Decimal", "DailyAverage": 3.428571428571429 }
        +    { "DailyAverage@type": "Decimal", "DailyAverage": 3.428571428571429 }
           ]
         }
         ```
        @@ -1404,7 +1404,7 @@ GET /service/Sales?$apply=concat(topcount(2,Amount),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Amount)",
        +  "@context": "$metadata#Sales(Amount)",
           "value": [
             { "ID": 4, "Amount": 8 },
             { "ID": 3, "Amount": 4 },
        @@ -1462,23 +1462,23 @@ GET /service/Sales?$apply=groupby((Customer/Country,Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3 },
        +      "Total@type": "Decimal", "Total":  3 },
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  5 },
        +      "Total@type": "Decimal", "Total":  5 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 }
        +      "Total@type": "Decimal", "Total":  2 }
           ]
         }
         ```
        @@ -1494,7 +1494,7 @@ GET /service/Sales?$apply=groupby((Product/Name,Amount))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Product(Name),Amount)",
        +  "@context": "$metadata#Sales(Product(Name),Amount)",
           "value": [
             { "Product": { "Name": "Coffee" }, "Amount": 4 },
             { "Product": { "Name": "Coffee" }, "Amount": 8 },
        @@ -1546,33 +1546,33 @@ GET /service/Sales?$apply=groupby((rollup(Customer/Country,Customer/Name),
         results in seven entities for the finest grouping level
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),
        +  "@context": "$metadata#Sales(Customer(Country),
                                              Product(Category(Name)),Total)",
           "value": [
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total": 1 },
        +      "Total@type": "Decimal", "Total": 1 },
             ...
         ```
         plus additional fifteen rollup entities for subtotals: five without customer name
         ```json
             { "Customer": { "Country": "USA" },
               "Product":  { "Category": { "Name": "Food" }, "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             ...
         ```
         six without product name
         ```json
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Food" } },
        -      "Total@odata.type": "Decimal", "Total":  6 },
        +      "Total@type": "Decimal", "Total":  6 },
             ...
         ```
         and four with neither customer nor product name
         ```json
             { "Customer": { "Country": "USA" },
               "Product":  { "Category": { "Name": "Food" } },
        -      "Total@odata.type": "Decimal", "Total": 14 },
        +      "Total@type": "Decimal", "Total": 14 },
             ...
           ]
         }
        @@ -1628,7 +1628,7 @@ GET /service/Sales?$apply=bottomcount(2,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 7, "Amount": 1 }
        @@ -1645,7 +1645,7 @@ GET /service/Sales?$apply=topcount(2,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -1667,7 +1667,7 @@ GET /service/Sales?$apply=bottompercent(50,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 2, "Amount": 2 },
        @@ -1688,7 +1688,7 @@ GET /service/Sales?$apply=toppercent(50,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -1709,7 +1709,7 @@ GET /service/Sales?$apply=bottomsum(7,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 2, "Amount": 2 },
        @@ -1729,7 +1729,7 @@ GET /service/Sales?$apply=topsum(15,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 },
        @@ -1751,7 +1751,7 @@ GET /service/Sales?$apply=filter(Amount gt 3)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 },
        @@ -1786,14 +1786,14 @@ GET /service/Sales?$apply=groupby((Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
        +  "@context": "$metadata#Sales(Product(Name),Total)",
           "value": [
             { "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  8 },
        +      "Total@type": "Decimal", "Total":  8 },
             { "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  4 }
        +      "Total@type": "Decimal", "Total":  4 }
           ]
         }
         ```
        @@ -1811,7 +1811,7 @@ GET /service/Sales?$apply=search(coffee)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -1834,7 +1834,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/skip(2)/top(2)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 6, "Amount": 2 },
             { "ID": 7, "Amount": 1 }
        @@ -1859,7 +1859,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/top(2)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 4, "Amount": 8 },
             { "ID": 5, "Amount": 4 }
        @@ -1894,16 +1894,16 @@ GET /service/Sales?$apply=compute(Amount mul Product/TaxRate as Tax)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(*,Tax)",
        +  "@context": "$metadata#Sales(*,Tax)",
           "value": [
        -    { "ID": 1, "Amount": 1, "Tax@odata.type": "Decimal", "Tax": 0.14 },
        -    { "ID": 2, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.12 },
        -    { "ID": 3, "Amount": 4, "Tax@odata.type": "Decimal", "Tax": 0.24 },
        -    { "ID": 4, "Amount": 8, "Tax@odata.type": "Decimal", "Tax": 0.48 },
        -    { "ID": 5, "Amount": 4, "Tax@odata.type": "Decimal", "Tax": 0.56 },
        -    { "ID": 6, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.12 },
        -    { "ID": 7, "Amount": 1, "Tax@odata.type": "Decimal", "Tax": 0.14 },
        -    { "ID": 8, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.28 }
        +    { "ID": 1, "Amount": 1, "Tax@type": "Decimal", "Tax": 0.14 },
        +    { "ID": 2, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.12 },
        +    { "ID": 3, "Amount": 4, "Tax@type": "Decimal", "Tax": 0.24 },
        +    { "ID": 4, "Amount": 8, "Tax@type": "Decimal", "Tax": 0.48 },
        +    { "ID": 5, "Amount": 4, "Tax@type": "Decimal", "Tax": 0.56 },
        +    { "ID": 6, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.12 },
        +    { "ID": 7, "Amount": 1, "Tax@type": "Decimal", "Tax": 0.14 },
        +    { "ID": 8, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.28 }
           ]
         }
         ```
        @@ -1931,45 +1931,45 @@ GET /service/Products?$apply=join(Sales as Sale)&$select=ID&$expand=Sale
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(ID,Sale())",
        +  "@context": "$metadata#Products(ID,Sale())",
           "value": [
             { "ID": "P1",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 2, "Amount": 2 } },
             { "ID": "P1",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 6, "Amount": 2 } },
             { "ID": "P2",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 3, "Amount": 4 } },
             { "ID": "P2",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 4, "Amount": 8 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 1, "Amount": 1 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 5, "Amount": 4 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 7, "Amount": 1 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 8, "Amount": 2 } }
           ]
         }
         ```
         
        -In this example, `$expand=Sale` is used to include the target entities in the result. There are no subsequent transformations like `groupby` that would cause it to be expanded by default. If the first parameter `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the complex property `Sale` would be in the result regardless, and its context would be `"@odata.context": "#SalesModel.SalesComplexType"`.
        +In this example, `$expand=Sale` is used to include the target entities in the result. There are no subsequent transformations like `groupby` that would cause it to be expanded by default. If the first parameter `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the complex property `Sale` would be in the result regardless, and its context would be `"@context": "#SalesModel.SalesComplexType"`.
         
         Applying `outerjoin` instead would return an additional instance for product with `"ID": "P4"` and `Sale` having a null value.
         :::
        @@ -1988,9 +1988,9 @@ GET /service/Sales?$apply=nest(groupby((Customer/ID)) as Customers))
         results in
         ```json
         {
        -  "@odata.context":"$metadata#Sales(Customers())",
        +  "@context":"$metadata#Sales(Customers())",
           "value": [
        -    { "Customers@odata.context": "#Sales(Customer(ID))",
        +    { "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] }
        @@ -2025,26 +2025,26 @@ GET /service/Customers?$apply=addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(FilteredSales())",
        +  "@context": "$metadata#Customers(FilteredSales())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "USA",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": [{ "ID": "3", "Amount": 4 }]},
             { "ID": "C2", "Name": "Sue", "Country": "USA",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": [{ "ID": "4", "Amount": 8 },
                                 { "ID": "5", "Amount": 4 }]},
             { "ID": "C3", "Name": "Sue", "Country": "Netherlands",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": []},
             { "ID": "C4", "Name": "Luc", "Country": "France",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": []}
           ]
         }
         ```
         
        -If `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the context would be `"FilteredSales@odata.context": "#Collection(SalesModel.SalesComplexType)"`.
        +If `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the context would be `"FilteredSales@context": "#Collection(SalesModel.SalesComplexType)"`.
         :::
         
         ## 3.5 Expressions Evaluable on a Collection
        @@ -2072,7 +2072,7 @@ GET /service/Sales?$filter=Amount mul 3 ge $these/aggregate(Amount with sum)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": "4", "Amount": 8 }
           ]
        @@ -2097,7 +2097,7 @@ GET /service/Products?$filter=Sales/any(s:s/Amount ge
         Both examples result in
         ```json
         {
        -  "@odata.context": "$metadata#Products",
        +  "@context": "$metadata#Products",
           "value": [
             { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
           ]
        @@ -2117,7 +2117,7 @@ GET /service/Sales?$apply=topcount($these/$count div 3,Amount)
         results in 2 (a third of 8, rounded down) entities. (This differs from `toppercent(33.3,Amount)`, which returns only the sales entity with `ID` 4, because that already makes up a third of the total amount.)
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -2143,7 +2143,7 @@ GET /service/Sales?$apply=aggregate(Amount with sum as Total)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": []
         }
         ```
        @@ -2162,16 +2162,16 @@ GET /service/Products
         results in
         ```json
         {
        -  "@odata.context":"$metadata#Products(Sales(Total))",
        +  "@context":"$metadata#Products(Sales(Total))",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":   12 } ] },
        +      "Sales": [ { "Total@type": "Decimal", "Total":   12 } ] },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":    8 } ] },
        +      "Sales": [ { "Total@type": "Decimal", "Total":    8 } ] },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
               "Sales": [ { "Total": null } ] },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":    4 } ] }
        +      "Sales": [ { "Total@type": "Decimal", "Total":    4 } ] }
           ]
         }
         ```
        @@ -2201,7 +2201,7 @@ GET /service/$crossjoin(Products,Sales)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Collection(Edm.ComplexType)",
        +  "@context": "$metadata#Collection(Edm.ComplexType)",
           "value": [
             { "Products": { "Name": "Paper" }, "Sales": { "Amount": 1 } },
             { "Products": { "Name": "Sugar" }, "Sales": { "Amount": 2 } },
        @@ -2223,17 +2223,17 @@ GET /service/$crossjoin(Products,Sales)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Collection(Edm.ComplexType)",
        +  "@context": "$metadata#Collection(Edm.ComplexType)",
           "value": [
             { "Products": { "Name": "Coffee" },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total": 12 } },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total": 12 } },
             { "Products": { "Name": "Paper"  },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total":  8 } },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total":  8 } },
             { "Products": { "Name": "Sugar"  },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total":  4 } }
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total":  4 } }
           ]
         }
         ```
        @@ -2505,7 +2505,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isdescendant(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA Central",      "Name": "EMEA Central" },
             { "ID": "Sales Netherlands", "Name": "Sales Netherlands" },
        @@ -2532,7 +2532,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isdescendant(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA Central", "Name": "EMEA Central" },
             { "ID": "EMEA South",   "Name": "EMEA South" },
        @@ -2554,7 +2554,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isleaf(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "Sales Office London",   "Name": "Sales Office London" },
             { "ID": "Sales Office New York", "Name": "Sales Office New York" },
        @@ -2576,7 +2576,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isleaf(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
        +  "@context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
           "value": [
             { "ID": "Sales Office London",   "Name": "Sales Office London",
               "Superordinate": { "ID": "EMEA United Kingdom" } },
        @@ -2601,7 +2601,7 @@ GET /service/Sales?$select=ID&$filter=Aggregation.isdescendant(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(ID)",
        +  "@context": "$metadata#Sales(ID)",
           "value": [
             { "ID": 6 },
             { "ID": 7 },
        @@ -2687,12 +2687,12 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA",  "Name": "EMEA",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US",    "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "Sales", "Name": "Sales",
               "Superordinate": null }
           ]
        @@ -2711,14 +2711,14 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "US West", "Name": "US West",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } },
             { "ID": "US",      "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US East", "Name": "US East",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } }
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
           ]
         }
         ```
        @@ -2738,7 +2738,7 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": "4", "Amount": 8,
               "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
        @@ -2828,12 +2828,12 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "US",      "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US East", "Name": "US East",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } }
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
           ]
         }
         ```
        @@ -2910,7 +2910,7 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context":
        +  "@context":
               "$metadata#SalesOrganizations(ID,Name,SubOrgCnt,Superordinate(ID))",
           "value": [
             { "ID": "US West",      "Name": "US West",
        @@ -2951,18 +2951,18 @@ GET /service/Sales?$apply=groupby(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(),
        +  "@context": "$metadata#Sales(SalesOrganization(),
                                              TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl":  7,
        -      "TotalAmountExcl@odata.type": "Decimal" ,"TotalAmountExcl":  7 },
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 },
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 19,
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
               "TotalAmountExcl": null },
             { "SalesOrganization": { "ID": "US East", "Name": "US East" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 12,
        -      "TotalAmountExcl@odata.type": "Decimal", "TotalAmountExcl": 12 }
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
        +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 }
           ]
         }
         ```
        @@ -2979,7 +2979,7 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
        +  "@context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
           "value": [
             { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" },
               "TotalAmount": null },
        @@ -3021,7 +3021,7 @@ GET /service/Customers?$apply=groupby((Name))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(Name)",
        +  "@context": "$metadata#Customers(Name)",
           "value": [
             { "Name": "Luc" },
             { "Name": "Joe" },
        @@ -3043,7 +3043,7 @@ GET /service/Sales?$apply=groupby((Customer/Name))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Name))",
        +  "@context": "$metadata#Sales(Customer(Name))",
           "value": [
             { "Customer": { "Name": "Joe" } },
             { "Customer": { "Name": "Sue" } }
        @@ -3066,7 +3066,7 @@ GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Name,ID))",
        +  "@context": "$metadata#Sales(Customer(Name,ID))",
           "value": [
             { "Customer": { "Name": "Joe", "ID": "C1" } },
             { "Customer": { "Name": "Sue", "ID": "C2" } },
        @@ -3090,7 +3090,7 @@ GET /service/Sales?$apply=groupby((Customer))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer())",
        +  "@context": "$metadata#Sales(Customer())",
           "value": [
             { "Customer": { "ID": "C1", "Name": "Joe", "Country": "USA" } },
             { "Customer": { "ID": "C2", "Name": "Sue", "Country": "USA" } },
        @@ -3108,7 +3108,7 @@ GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID,Product/Name))
         and results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
        +  "@context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
           "value": [
             { "Customer": { "Name": "Joe", "ID": "C1" },
               "Product": { "Name": "Coffee"} },
        @@ -3138,13 +3138,13 @@ GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(SalesModel.FoodProduct/Rating,
        +  "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
                                              SalesModel.NonFoodProduct/RatingClass)",
           "value": [
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": 5 },
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": null },
        -    { "@odata.type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
        -    { "@odata.type": "#SalesModel.NonFoodProduct", "RatingClass": null }
        +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
        +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
        +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": null }
           ]
         }
         ```
        @@ -3158,10 +3158,10 @@ GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating))
         results in a third group representing entities with no `SalesModel.FoodProduct/Rating`, including the `SalesModel.NonFoodProduct`s:
         ```json
         {
        -  "@odata.context": "$metadata#Products(@Core.AnyStructure)",
        +  "@context": "$metadata#Products(@Core.AnyStructure)",
           "value": [
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": 5 },
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": null },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
             { }
           ]
         }
        @@ -3181,12 +3181,12 @@ GET /service/Products?$apply=groupby((Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,Total)",
        +  "@context": "$metadata#Products(Name,Total)",
           "value": [
        -    { "Name": "Coffee", "Total@odata.type": "Decimal", "Total":   12 },
        -    { "Name": "Paper",  "Total@odata.type": "Decimal", "Total":    8 },
        +    { "Name": "Coffee", "Total@type": "Decimal", "Total":   12 },
        +    { "Name": "Paper",  "Total@type": "Decimal", "Total":    8 },
             { "Name": "Pencil",                                "Total": null },
        -    { "Name": "Sugar",  "Total@odata.type": "Decimal", "Total":    4 }
        +    { "Name": "Sugar",  "Total@type": "Decimal", "Total":    4 }
           ]
         }
         ```
        @@ -3203,20 +3203,20 @@ GET /service/Products?$apply=addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(AggregatedSales())",
        +  "@context": "$metadata#Products(AggregatedSales())",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total": 12 } ] },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total": 12 } ] },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total":  8 } ] },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  8 } ] },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        +      "AggregatedSales@context": "#Sales(Total)",
               "AggregatedSales": [ {                              "Total": null } ] },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total":  4 } ] }
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  4 } ] }
           ]
         }
         ```
        @@ -3230,16 +3230,16 @@ GET /service/Products?$compute=Sales/aggregate(Amount with sum) as Total
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(*,Total)",
        +  "@context": "$metadata#Products(*,Total)",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "Total@odata.type": "Decimal", "Total":  8 },
        +      "Total@type": "Decimal", "Total":  8 },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
                                              "Total": null },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "Total@odata.type": "Decimal", "Total":  4 }
        +      "Total@type": "Decimal", "Total":  4 }
           ]
         }
         ```
        @@ -3257,17 +3257,17 @@ GET /service/Products?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,TotalSales())",
        +  "@context": "$metadata#Products(Name,TotalSales())",
           "value": [
             { "Name": "Coffee",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total": 12 } },
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total": 12 } },
             { "Name": "Paper",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total":  8 } },
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total":  8 } },
             { "Name": "Sugar",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total":  4 } }
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total":  4 } }
           ]
         }
         ```
        @@ -3284,7 +3284,7 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),AverageAmount)",
        +  "@context": "$metadata#Sales(Customer(Country),AverageAmount)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "AverageAmount": 1.6666666666666667 },
        @@ -3305,12 +3305,12 @@ GET /service/Products?$apply=groupby((Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,SalesCount)",
        +  "@context": "$metadata#Products(Name,SalesCount)",
           "value": [
        -    { "Name": "Coffee", "SalesCount@odata.type": "Decimal", "SalesCount": 2 },
        -    { "Name": "Paper",  "SalesCount@odata.type": "Decimal", "SalesCount": 4 },
        -    { "Name": "Pencil", "SalesCount@odata.type": "Decimal", "SalesCount": 0 },
        -    { "Name": "Sugar",  "SalesCount@odata.type": "Decimal", "SalesCount": 2 }
        +    { "Name": "Coffee", "SalesCount@type": "Decimal", "SalesCount": 2 },
        +    { "Name": "Paper",  "SalesCount@type": "Decimal", "SalesCount": 4 },
        +    { "Name": "Pencil", "SalesCount@type": "Decimal", "SalesCount": 0 },
        +    { "Name": "Sugar",  "SalesCount@type": "Decimal", "SalesCount": 2 }
           ]
         }
         ```
        @@ -3328,23 +3328,23 @@ GET /service/Products?$apply=groupby((Name),addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,AggregatedSales())",
        +  "@context": "$metadata#Products(Name,AggregatedSales())",
           "value": [
             { "Name": "Coffee",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 2,
        -          "TotalAmount@odata.type": "Decimal", "TotalAmount": 12 } ] },
        +          "TotalAmount@type": "Decimal", "TotalAmount": 12 } ] },
             { "Name": "Paper",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 4,
        -          "TotalAmount@odata.type": "Decimal", "TotalAmount":  8 } ] },
        +          "TotalAmount@type": "Decimal", "TotalAmount":  8 } ] },
             { "Name": "Pencil",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 0, "TotalAmount":  null } ] },
             { "Name": "Sugar",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 2,
        -          "TotalAmount@odata.type": "Decimal",  "TotalAmount":  4 } ] }
        +          "TotalAmount@type": "Decimal",  "TotalAmount":  4 } ] }
           ]
         }
         ```
        @@ -3360,7 +3360,7 @@ GET /service/Products?$filter=Sales/aggregate(Amount with sum) ge 10
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products",
        +  "@context": "$metadata#Products",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06 },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14 }
        @@ -3377,7 +3377,7 @@ GET /service/Customers?$orderby=Sales/aggregate(Amount with sum) desc
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers",
        +  "@context": "$metadata#Customers",
           "value": [
             { "ID": "C2", "Name": "Sue", "Country": "USA" },
             { "ID": "C1", "Name": "Joe", "Country": "USA" },
        @@ -3397,23 +3397,23 @@ GET /service/Sales?$compute=Amount divby $these/aggregate(Amount with sum)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(*,Contribution)",
        +  "@context": "$metadata#Sales(*,Contribution)",
           "value": [
        -    { "ID": 1, "Amount": 1, "Contribution@odata.type": "Decimal",
        +    { "ID": 1, "Amount": 1, "Contribution@type": "Decimal",
                                     "Contribution": 0.0416666666666667 },
        -    { "ID": 2, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 2, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 },
        -    { "ID": 3, "Amount": 4, "Contribution@odata.type": "Decimal",
        +    { "ID": 3, "Amount": 4, "Contribution@type": "Decimal",
                                     "Contribution": 0.1666666666666667 },
        -    { "ID": 4, "Amount": 8, "Contribution@odata.type": "Decimal",
        +    { "ID": 4, "Amount": 8, "Contribution@type": "Decimal",
                                     "Contribution": 0.3333333333333333 },
        -    { "ID": 5, "Amount": 4, "Contribution@odata.type": "Decimal",
        +    { "ID": 5, "Amount": 4, "Contribution@type": "Decimal",
                                     "Contribution": 0.1666666666666667 },
        -    { "ID": 6, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 6, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 },
        -    { "ID": 7, "Amount": 1, "Contribution@odata.type": "Decimal",
        +    { "ID": 7, "Amount": 1, "Contribution@type": "Decimal",
                                     "Contribution": 0.0416666666666667 },
        -    { "ID": 8, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 8, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 }
           ]
         }
        @@ -3429,7 +3429,7 @@ GET /service/Categories?$filter=Products/any(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Categories",
        +  "@context": "$metadata#Categories",
           "value": [
             { "ID": "PG1", "Name": "Food" }
           ]
        @@ -3451,14 +3451,14 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
        +  "@context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
           "value": [
        -    { "Customer":    { "@odata.id": "Customers('C1')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.2916667 },
        -    { "Customer":    { "@odata.id": "Customers('C2')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.5 },
        -    { "Customer":    { "@odata.id": "Customers('C3')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 }
        +    { "Customer":    { "@id": "Customers('C1')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.2916667 },
        +    { "Customer":    { "@id": "Customers('C2')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.5 },
        +    { "Customer":    { "@id": "Customers('C3')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.2083333 }
           ]
         }
         ```
        @@ -3502,18 +3502,18 @@ GET /service/Customers?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(
        +  "@context": "$metadata#Customers(
                                        Addresses(AugmentedSalesOrganization())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "US",
               "Addresses": [
                 { "Locality": "Seattle",
                   "AugmentedSalesOrganization":
        -          { "@odata.context": "#SalesOrganizations/$entity",
        +          { "@context": "#SalesOrganizations/$entity",
                     "ID": "US West", "SalesRegion": "US" } },
                 { "Locality": "DC",
                   "AugmentedSalesOrganization":
        -          { "@odata.context": "#SalesOrganizations/$entity",
        +          { "@context": "#SalesOrganizations/$entity",
                     "ID": "US",      "SalesRegion": "Corporate Sales" } },
               ]
             }, ...
        @@ -3535,28 +3535,28 @@ GET /service/Categories?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Categories(FilteredProducts()",
        +  "@context": "$metadata#Categories(FilteredProducts()",
           "value": [
             { "ID": "PG1", "Name": "Food",
        -      "FilteredProducts@odata.context": "#Products(FilteredSales())",
        +      "FilteredProducts@context": "#Products(FilteredSales())",
               "FilteredProducts": [
                 { "ID": "P1", "Name": "Sugar",  "Color": "White",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [] },
                 { "ID": "P2", "Name": "Coffee", "Color": "Brown",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [ { "ID": 3, "Amount": 4 },
                                      { "ID": 4, "Amount": 8 } ] }
               ]
             },
             { "ID": "PG2", "Name": "Non-Food",
        -      "FilteredProducts@odata.context": "#Products(FilteredSales())",
        +      "FilteredProducts@context": "#Products(FilteredSales())",
               "FilteredProducts": [
                 { "ID": "P3", "Name": "Paper",  "Color": "White",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [ { "ID": 5, "Amount": 4 } ] },
                 { "ID": "P4", "Name": "Pencil", "Color": "Black",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [] }
               ]
             }
        @@ -3585,29 +3585,29 @@ GET /service/Customers?$apply=addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(GroupedSales())",
        +  "@context": "$metadata#Customers(GroupedSales())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "USA",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { },
                 { }
               ] },
             { "ID": "C2", "Name": "Sue", "Country": "USA",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { }
               ] },
             { "ID": "C3", "Name": "Joe", "Country": "Netherlands",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { }
               ] },
             { "ID": "C4", "Name": "Luc", "Country": "France",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [ ] }
           ]
         }
        @@ -3623,22 +3623,22 @@ GET /service/Customers?$apply=outerjoin(Sales as ProductSales)
         returns the different combinations of products sold per country:
         ```json
         {
        -  "@odata.context":"$metadata#Customers(Country,ProductSales())",
        +  "@context":"$metadata#Customers(Country,ProductSales())",
           "value": [
             { "Country": "Netherlands",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Paper"  } } },
             { "Country": "Netherlands",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Sugar"  } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Coffee" } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Paper"  } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Sugar"  } } },
             { "Country": "France", "ProductSales": null }
           ]
        @@ -3660,14 +3660,14 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
        +  "@context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Actual@odata.type": "Decimal", "Actual":  5,
        -      "Forecast@odata.type": "Decimal", "Forecast": 4 },
        +      "Actual@type": "Decimal", "Actual":  5,
        +      "Forecast@type": "Decimal", "Forecast": 4 },
             { "Customer": { "Country": "USA" },
        -      "Actual@odata.type": "Decimal", "Actual": 19,
        -      "Forecast@odata.type": "Decimal", "Forecast": 21 }
        +      "Actual@type": "Decimal", "Actual": 19,
        +      "Forecast@type": "Decimal", "Forecast": 21 }
           ]
         }
         ```
        @@ -3683,7 +3683,7 @@ GET /service/Sales?$apply=groupby((Customer/Country),aggregate(Amount))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Amount)",
        +  "@context": "$metadata#Sales(Customer(Country),Amount)",
           "value": [
             { "Customer": { "Country": "Netherlands" }, "Amount":  5 },
             { "Customer": { "Country": "USA" },         "Amount": 19 }
        @@ -3750,14 +3750,14 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
        +  "@context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5,
        -      "AvgAmt@odata.type": "Decimal", "AvgAmt": 1.6666667 },
        +      "Total@type": "Decimal", "Total":  5,
        +      "AvgAmt@type": "Decimal", "AvgAmt": 1.6666667 },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19,
        -      "AvgAmt@odata.type": "Decimal", "AvgAmt": 3.8 }
        +      "Total@type": "Decimal", "Total": 19,
        +      "AvgAmt@type": "Decimal", "AvgAmt": 3.8 }
           ]
         }
         ```
        @@ -3777,22 +3777,22 @@ GET /service/Products?$apply=groupby((Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,Total,AggregatedSales())",
        +  "@context": "$metadata#Products(Name,Total,AggregatedSales())",
           "value": [
             { "Name": "Coffee", "Total":   12,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 6 } ] },
             { "Name": "Paper",  "Total":    8,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 2 } ] },
             { "Name": "Pencil", "Total": null,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
               "AggregatedSales": [ { "AvgAmt": null } ] },
             { "Name": "Sugar",  "Total":    4,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 2 } ] }
           ]
         }
        @@ -3809,12 +3809,12 @@ GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total))
         will return all distinct amounts appearing in sales orders and how much money was made with deals of this amount
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Amount,Total)",
        +  "@context": "$metadata#Sales(Amount,Total)",
           "value": [
        -    { "Amount": 1, "Total@odata.type": "Decimal", "Total": 2 },
        -    { "Amount": 2, "Total@odata.type": "Decimal", "Total": 6 },
        -    { "Amount": 4, "Total@odata.type": "Decimal", "Total": 8 },
        -    { "Amount": 8, "Total@odata.type": "Decimal", "Total": 8 }
        +    { "Amount": 1, "Total@type": "Decimal", "Total": 2 },
        +    { "Amount": 2, "Total@type": "Decimal", "Total": 6 },
        +    { "Amount": 4, "Total@type": "Decimal", "Total": 8 },
        +    { "Amount": 8, "Total@type": "Decimal", "Total": 8 }
           ]
         }
         ```
        @@ -3837,19 +3837,19 @@ GET /service/Sales?$apply=concat(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Total)",
           "value": [
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12
        +      "Total@type": "Decimal", "Total": 12
             },
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3
        +      "Total@type": "Decimal", "Total":  3
             },
             { "Customer":{ "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19
        +      "Total@type": "Decimal", "Total": 19
             },
             { "Customer":{ "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5
        +      "Total@type": "Decimal", "Total":  5
             }
           ]
         }
        @@ -3865,22 +3865,22 @@ GET /service/Sales?$apply=groupby((Customer/Country,Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
           "value": [
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3
        +      "Total@type": "Decimal", "Total":  3
             },
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2
        +      "Total@type": "Decimal", "Total":  2
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2
        +      "Total@type": "Decimal", "Total":  2
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12
        +      "Total@type": "Decimal", "Total": 12
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  5
        +      "Total@type": "Decimal", "Total":  5
             }
           ]
         }
        @@ -3898,7 +3898,7 @@ GET /service/Sales?$apply=concat(
         In the result, `Sales` entities 4 and 6 occur twice each with contradictory values of the dynamic property `per`. If a UI consuming the response presents the two groupings in separate columns based on the `per` property, no contradiction effectively arises.
         ```json
         {
        -  "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
        +  "@context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
           "value": [
             { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
               "ID": "3", "Amount": 4, "per": "Customer" },
        @@ -3944,18 +3944,18 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Total)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3 },
        +      "Total@type": "Decimal", "Total":  3 },
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total":  7 }
        +      "Total@type": "Decimal", "Total":  7 }
           ]
         }
         ```
        @@ -4003,24 +4003,24 @@ GET /service/Sales?$apply=concat(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(CustomerCountryAverage)",
        +  "@context": "$metadata#Sales(CustomerCountryAverage)",
           "value": [
             { "Customer": { "Country": "USA", "ID": "C1" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage":   7 },
             { "Customer": { "Country": "USA", "ID": "C2" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage":  12 },
             { "Customer": { "Country": "USA" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 9.5 },
             { "Customer": { "Country": "Netherlands", "ID": "C3" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 5 },
             { "Customer": { "Country": "Netherlands" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 5 },
        -    { "CustomerCountryAverage@odata.type":"Decimal",
        +    { "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 7.25 }
           ]
         }
        @@ -4049,17 +4049,17 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(TotalAmount,SalesOrganization())",
        +  "@context": "$metadata#Sales(TotalAmount,SalesOrganization())",
           "value": [
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount": 19,
        +    { "TotalAmount@type": "Decimal", "TotalAmount": 19,
               "SalesOrganization": { "ID": "US",      "Name": "US",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount": 12,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "TotalAmount@type": "Decimal", "TotalAmount": 12,
               "SalesOrganization": { "ID": "US East", "Name": "US East",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount":  7,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "TotalAmount@type": "Decimal", "TotalAmount":  7,
               "SalesOrganization": { "ID": "US West", "Name": "US West",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } }
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } }
           ]
         }
         ```
        @@ -4082,24 +4082,24 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
        +  "@context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
           "value": [
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "US",           "Name": "US",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 1,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
               "SalesOrganization": { "ID": "US East",      "Name": "US East",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 1,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
               "SalesOrganization": { "ID": "US West",      "Name": "US West",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "EMEA",         "Name": "EMEA",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('EMEA')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 4,
        +        "Superordinate": { "@id": "SalesOrganizations('EMEA')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 4,
               "SalesOrganization": { "ID": "Sales",        "Name": "Sales",
                 "Superordinate": null } }
           ]
        @@ -4195,18 +4195,18 @@ GET /service/Sales?$apply=groupby(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(ID),
        +  "@context": "$metadata#Sales(SalesOrganization(ID),
                                              TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 19,
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
               "TotalAmountExcl": null },
             { "SalesOrganization": { "ID": "US East", "Name": "US East" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 12,
        -      "TotalAmountExcl@odata.type": "Decimal", "TotalAmountExcl": 12 },
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
        +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 },
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl":  7,
        -      "TotalAmountExcl@odata.type": "Decimal" ,"TotalAmountExcl":  7 }
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 }
           ]
         }
         ```
        @@ -4227,7 +4227,7 @@ GET /service/Products?$apply=traverse(
         The result contains multiple instances of the same `Product` that differ in their `Sales` navigation property even though they agree in their `ID` key property. The node $x$ with $x/{\tt ID}={}$`"US"` has $σ(x)={}$`{"Sales": [{"SalesOrganization": {"ID": "US"}}]}`.
         ```json
         {
        -  "@odata.context":
        +  "@context":
               "$metadata#Products(ID,Sales(SalesOrganization(ID)))",
           "value": [
             { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
        @@ -4265,7 +4265,7 @@ GET /service/Products?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Sales(SalesOrganization(ID)),
        +  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),
                                                 SoldProducts)",
           "value": [
             { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
        @@ -4319,17 +4319,17 @@ GET /service/Sales?$apply=groupby((rolluprecursive(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
        +  "@context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
           "value": [
             { "SalesOrganization": { "ID": "Sales",   "ProductCategories": [ ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 },
        +      "TotalAmount@type": "Decimal", "TotalAmount": 24 },
             { "SalesOrganization": { "ID": "US",      "ProductCategories": [
        -      { "@odata.id": "ProductCategories('Food')" },
        -      { "@odata.id": "ProductCategories('Cereals')" } ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 },
        +      { "@id": "ProductCategories('Food')" },
        +      { "@id": "ProductCategories('Cereals')" } ] },
        +      "TotalAmount@type": "Decimal", "TotalAmount": 19 },
             { "SalesOrganization": { "ID": "US West", "ProductCategories": [
        -      { "@odata.id": "ProductCategories('Organic cereals')" } ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount":  7 }
        +      { "@id": "ProductCategories('Organic cereals')" } ] },
        +      "TotalAmount@type": "Decimal", "TotalAmount":  7 }
           ]
         }
         ```
        @@ -4362,9 +4362,9 @@ GET /service/Sales?$apply=filter(Amount le 1)
         means "filter first, then aggregate", and results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 2 }
        +    { "Total@type": "Decimal", "Total": 2 }
           ]
         }
         ```
        @@ -4382,12 +4382,12 @@ GET /service/Sales?$apply=filter(Amount le 2)/groupby((Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
        +  "@context": "$metadata#Sales(Product(Name),Total)",
           "value": [
             { "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total": 4 },
        +      "Total@type": "Decimal", "Total": 4 },
             { "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total": 4 }
        +      "Total@type": "Decimal", "Total": 4 }
           ]
         }
         ```
        @@ -4416,13 +4416,13 @@ GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
        +  "@context": "$metadata#Cities(Continent(Name),Country(Name),
                                               TotalPopulation)",
           "value": [
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
        -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
        +      "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
        -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
        +      "TotalPopulation@type": "Int32", "TotalPopulation": 1408000000 },
             ...
           ]
         }
        @@ -4512,15 +4512,15 @@ GET /service/Sales?$apply=groupby((Product/Category/ID),
         results in
         ```json
         {
        -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
        +  "@context":"$metadata#Sales(Product(Category(ID)),Customers())",
           "value": [
             { "Product": { "Category": { "ID": "PG1" } },
        -      "Customers@odata.context": "#Sales(Customer(ID))",
        +      "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] },
             { "Product": { "Category": { "ID": "PG2" } },
        -      "Customers@odata.context": "#Sales(Customer(ID))",
        +      "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] }
        diff --git a/odata-data-aggregation-ext/3.2 Basic Aggregation.md b/odata-data-aggregation-ext/3.2 Basic Aggregation.md
        index 2a7f86d00..4180c15c8 100644
        --- a/odata-data-aggregation-ext/3.2 Basic Aggregation.md	
        +++ b/odata-data-aggregation-ext/3.2 Basic Aggregation.md	
        @@ -43,10 +43,10 @@ GET /service/Sales?$apply=aggregate(Amount with sum as Total,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total, MxA)",
        +  "@context": "$metadata#Sales(Total, MxA)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 24,
        -      "MxA@odata.type": "Decimal", "MxA": 8 }
        +    { "Total@type": "Decimal", "Total": 24,
        +      "MxA@type": "Decimal", "MxA": 8 }
           ]
         }
         ```
        @@ -61,9 +61,9 @@ GET /service/Sales?$apply=aggregate(Amount mul Product/TaxRate
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Tax)",
        +  "@context": "$metadata#Sales(Tax)",
           "value": [
        -    { "Tax@odata.type": "Decimal", "Tax": 2.08 }
        +    { "Tax@type": "Decimal", "Tax": 2.08 }
           ]
         }
         ```
        @@ -87,9 +87,9 @@ GET /service/Sales?$apply=aggregate(Amount with sum as Total)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 24 }
        +    { "Total@type": "Decimal", "Total": 24 }
           ]
         }
         ```
        @@ -109,9 +109,9 @@ GET /service/Sales?$apply=aggregate(Amount with min as MinAmount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(MinAmount)",
        +  "@context": "$metadata#Sales(MinAmount)",
           "value": [
        -    { "MinAmount@odata.type": "Decimal", "MinAmount": 1 }
        +    { "MinAmount@type": "Decimal", "MinAmount": 1 }
           ]
         }
         ```
        @@ -131,9 +131,9 @@ GET /service/Sales?$apply=aggregate(Amount with max as MaxAmount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(MaxAmount)",
        +  "@context": "$metadata#Sales(MaxAmount)",
           "value": [
        -    { "MaxAmount@odata.type": "Decimal", "MaxAmount": 8 }
        +    { "MaxAmount@type": "Decimal", "MaxAmount": 8 }
           ]
         }
         ```
        @@ -153,9 +153,9 @@ GET /service/Sales?$apply=aggregate(Amount with average as AverageAmount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(AverageAmount)",
        +  "@context": "$metadata#Sales(AverageAmount)",
           "value": [
        -    { "AverageAmount@odata.type": "Decimal", "AverageAmount": 3.0 }
        +    { "AverageAmount@type": "Decimal", "AverageAmount": 3.0 }
           ]
         }
         ```
        @@ -176,9 +176,9 @@ GET /service/Sales?$apply=aggregate(Product with countdistinct
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(DistinctProducts)",
        +  "@context": "$metadata#Sales(DistinctProducts)",
           "value": [
        -    { "DistinctProducts@odata.type": "Decimal", "DistinctProducts": 3 }
        +    { "DistinctProducts@type": "Decimal", "DistinctProducts": 3 }
           ]
         }
         ```
        @@ -202,13 +202,13 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total,ProductNames)",
        +  "@context": "$metadata#Sales(Customer(Country),Total,ProductNames)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5,
        +      "Total@type": "Decimal", "Total":  5,
               "ProductNames": "Paper,Sugar" },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19,
        +      "Total@type": "Decimal", "Total": 19,
               "ProductNames": "Coffee,Paper,Sugar" }
           ]
         }
        @@ -229,9 +229,9 @@ GET /service/Sales?$apply=aggregate($count as SalesCount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesCount)",
        +  "@context": "$metadata#Sales(SalesCount)",
           "value": [
        -    { "SalesCount@odata.type": "Decimal", "SalesCount": 8 }
        +    { "SalesCount@type": "Decimal", "SalesCount": 8 }
           ]
         }
         ```
        @@ -268,9 +268,9 @@ GET /service/Sales?$apply=groupby((Time),aggregate(Amount with sum as Total))
         and results in the average sales volume per day
         ```json
         {
        -  "@odata.context": "$metadata#Sales(DailyAverage)",
        +  "@context": "$metadata#Sales(DailyAverage)",
           "value": [
        -    { "DailyAverage@odata.type": "Decimal", "DailyAverage": 3.428571428571429 }
        +    { "DailyAverage@type": "Decimal", "DailyAverage": 3.428571428571429 }
           ]
         }
         ```
        @@ -304,7 +304,7 @@ GET /service/Sales?$apply=concat(topcount(2,Amount),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Amount)",
        +  "@context": "$metadata#Sales(Amount)",
           "value": [
             { "ID": 4, "Amount": 8 },
             { "ID": 3, "Amount": 4 },
        @@ -362,23 +362,23 @@ GET /service/Sales?$apply=groupby((Customer/Country,Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3 },
        +      "Total@type": "Decimal", "Total":  3 },
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  5 },
        +      "Total@type": "Decimal", "Total":  5 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 }
        +      "Total@type": "Decimal", "Total":  2 }
           ]
         }
         ```
        @@ -394,7 +394,7 @@ GET /service/Sales?$apply=groupby((Product/Name,Amount))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Product(Name),Amount)",
        +  "@context": "$metadata#Sales(Product(Name),Amount)",
           "value": [
             { "Product": { "Name": "Coffee" }, "Amount": 4 },
             { "Product": { "Name": "Coffee" }, "Amount": 8 },
        @@ -463,33 +463,33 @@ GET /service/Sales?$apply=groupby((rollup(Customer/Country,Customer/Name),
         results in seven entities for the finest grouping level
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),
        +  "@context": "$metadata#Sales(Customer(Country),
                                              Product(Category(Name)),Total)",
           "value": [
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total": 1 },
        +      "Total@type": "Decimal", "Total": 1 },
             ...
         ```
         plus additional fifteen rollup entities for subtotals: five without customer name
         ```json
             { "Customer": { "Country": "USA" },
               "Product":  { "Category": { "Name": "Food" }, "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             ...
         ```
         six without product name
         ```json
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Food" } },
        -      "Total@odata.type": "Decimal", "Total":  6 },
        +      "Total@type": "Decimal", "Total":  6 },
             ...
         ```
         and four with neither customer nor product name
         ```json
             { "Customer": { "Country": "USA" },
               "Product":  { "Category": { "Name": "Food" } },
        -      "Total@odata.type": "Decimal", "Total": 14 },
        +      "Total@type": "Decimal", "Total": 14 },
             ...
           ]
         }
        diff --git a/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md b/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md
        index 7e65f7e40..82fbdd4f8 100644
        --- a/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md	
        +++ b/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md	
        @@ -41,7 +41,7 @@ GET /service/Sales?$apply=bottomcount(2,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 7, "Amount": 1 }
        @@ -58,7 +58,7 @@ GET /service/Sales?$apply=topcount(2,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -80,7 +80,7 @@ GET /service/Sales?$apply=bottompercent(50,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 2, "Amount": 2 },
        @@ -101,7 +101,7 @@ GET /service/Sales?$apply=toppercent(50,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -122,7 +122,7 @@ GET /service/Sales?$apply=bottomsum(7,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 1, "Amount": 1 },
             { "ID": 2, "Amount": 2 },
        @@ -142,7 +142,7 @@ GET /service/Sales?$apply=topsum(15,Amount)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 },
        @@ -164,7 +164,7 @@ GET /service/Sales?$apply=filter(Amount gt 3)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 },
        @@ -199,14 +199,14 @@ GET /service/Sales?$apply=groupby((Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
        +  "@context": "$metadata#Sales(Product(Name),Total)",
           "value": [
             { "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  8 },
        +      "Total@type": "Decimal", "Total":  8 },
             { "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  4 }
        +      "Total@type": "Decimal", "Total":  4 }
           ]
         }
         ```
        @@ -224,7 +224,7 @@ GET /service/Sales?$apply=search(coffee)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -247,7 +247,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/skip(2)/top(2)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 6, "Amount": 2 },
             { "ID": 7, "Amount": 1 }
        @@ -272,7 +272,7 @@ GET /service/Sales?$apply=orderby(Customer/Name desc)/top(2)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 4, "Amount": 8 },
             { "ID": 5, "Amount": 4 }
        diff --git a/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md b/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md
        index 58debcadd..5fd31523f 100644
        --- a/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md	
        +++ b/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md	
        @@ -20,16 +20,16 @@ GET /service/Sales?$apply=compute(Amount mul Product/TaxRate as Tax)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(*,Tax)",
        +  "@context": "$metadata#Sales(*,Tax)",
           "value": [
        -    { "ID": 1, "Amount": 1, "Tax@odata.type": "Decimal", "Tax": 0.14 },
        -    { "ID": 2, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.12 },
        -    { "ID": 3, "Amount": 4, "Tax@odata.type": "Decimal", "Tax": 0.24 },
        -    { "ID": 4, "Amount": 8, "Tax@odata.type": "Decimal", "Tax": 0.48 },
        -    { "ID": 5, "Amount": 4, "Tax@odata.type": "Decimal", "Tax": 0.56 },
        -    { "ID": 6, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.12 },
        -    { "ID": 7, "Amount": 1, "Tax@odata.type": "Decimal", "Tax": 0.14 },
        -    { "ID": 8, "Amount": 2, "Tax@odata.type": "Decimal", "Tax": 0.28 }
        +    { "ID": 1, "Amount": 1, "Tax@type": "Decimal", "Tax": 0.14 },
        +    { "ID": 2, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.12 },
        +    { "ID": 3, "Amount": 4, "Tax@type": "Decimal", "Tax": 0.24 },
        +    { "ID": 4, "Amount": 8, "Tax@type": "Decimal", "Tax": 0.48 },
        +    { "ID": 5, "Amount": 4, "Tax@type": "Decimal", "Tax": 0.56 },
        +    { "ID": 6, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.12 },
        +    { "ID": 7, "Amount": 1, "Tax@type": "Decimal", "Tax": 0.14 },
        +    { "ID": 8, "Amount": 2, "Tax@type": "Decimal", "Tax": 0.28 }
           ]
         }
         ```
        @@ -57,45 +57,45 @@ GET /service/Products?$apply=join(Sales as Sale)&$select=ID&$expand=Sale
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(ID,Sale())",
        +  "@context": "$metadata#Products(ID,Sale())",
           "value": [
             { "ID": "P1",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 2, "Amount": 2 } },
             { "ID": "P1",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 6, "Amount": 2 } },
             { "ID": "P2",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 3, "Amount": 4 } },
             { "ID": "P2",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 4, "Amount": 8 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 1, "Amount": 1 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 5, "Amount": 4 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 7, "Amount": 1 } },
             { "ID": "P3",
               "Sale": {
        -        "@odata.context": "#Sales/$entity",
        +        "@context": "#Sales/$entity",
                 "ID": 8, "Amount": 2 } }
           ]
         }
         ```
         
        -In this example, `$expand=Sale` is used to include the target entities in the result. There are no subsequent transformations like `groupby` that would cause it to be expanded by default. If the first parameter `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the complex property `Sale` would be in the result regardless, and its context would be `"@odata.context": "#SalesModel.SalesComplexType"`.
        +In this example, `$expand=Sale` is used to include the target entities in the result. There are no subsequent transformations like `groupby` that would cause it to be expanded by default. If the first parameter `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the complex property `Sale` would be in the result regardless, and its context would be `"@context": "#SalesModel.SalesComplexType"`.
         
         Applying `outerjoin` instead would return an additional instance for product with `"ID": "P4"` and `Sale` having a null value.
         :::
        @@ -114,9 +114,9 @@ GET /service/Sales?$apply=nest(groupby((Customer/ID)) as Customers))
         results in
         ```json
         {
        -  "@odata.context":"$metadata#Sales(Customers())",
        +  "@context":"$metadata#Sales(Customers())",
           "value": [
        -    { "Customers@odata.context": "#Sales(Customer(ID))",
        +    { "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] }
        @@ -151,24 +151,24 @@ GET /service/Customers?$apply=addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(FilteredSales())",
        +  "@context": "$metadata#Customers(FilteredSales())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "USA",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": [{ "ID": "3", "Amount": 4 }]},
             { "ID": "C2", "Name": "Sue", "Country": "USA",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": [{ "ID": "4", "Amount": 8 },
                                 { "ID": "5", "Amount": 4 }]},
             { "ID": "C3", "Name": "Sue", "Country": "Netherlands",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": []},
             { "ID": "C4", "Name": "Luc", "Country": "France",
        -      "FilteredSales@odata.context": "#Sales",
        +      "FilteredSales@context": "#Sales",
               "FilteredSales": []}
           ]
         }
         ```
         
        -If `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the context would be `"FilteredSales@odata.context": "#Collection(SalesModel.SalesComplexType)"`.
        +If `Sales` was a collection-valued complex property of type `SalesModel.SalesComplexType`, the context would be `"FilteredSales@context": "#Collection(SalesModel.SalesComplexType)"`.
         :::
        diff --git a/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md b/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md
        index 2acc32795..93ac51e25 100644
        --- a/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md	
        +++ b/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md	
        @@ -23,7 +23,7 @@ GET /service/Sales?$filter=Amount mul 3 ge $these/aggregate(Amount with sum)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": "4", "Amount": 8 }
           ]
        @@ -48,7 +48,7 @@ GET /service/Products?$filter=Sales/any(s:s/Amount ge
         Both examples result in
         ```json
         {
        -  "@odata.context": "$metadata#Products",
        +  "@context": "$metadata#Products",
           "value": [
             { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
           ]
        @@ -68,7 +68,7 @@ GET /service/Sales?$apply=topcount($these/$count div 3,Amount)
         results in 2 (a third of 8, rounded down) entities. (This differs from `toppercent(33.3,Amount)`, which returns only the sales entity with `ID` 4, because that already makes up a third of the total amount.)
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": 3, "Amount": 4 },
             { "ID": 4, "Amount": 8 }
        @@ -94,7 +94,7 @@ GET /service/Sales?$apply=aggregate(Amount with sum as Total)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": []
         }
         ```
        @@ -113,16 +113,16 @@ GET /service/Products
         results in
         ```json
         {
        -  "@odata.context":"$metadata#Products(Sales(Total))",
        +  "@context":"$metadata#Products(Sales(Total))",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":   12 } ] },
        +      "Sales": [ { "Total@type": "Decimal", "Total":   12 } ] },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":    8 } ] },
        +      "Sales": [ { "Total@type": "Decimal", "Total":    8 } ] },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
               "Sales": [ { "Total": null } ] },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "Sales": [ { "Total@odata.type": "Decimal", "Total":    4 } ] }
        +      "Sales": [ { "Total@type": "Decimal", "Total":    4 } ] }
           ]
         }
         ```
        diff --git a/odata-data-aggregation-ext/4 Cross-Joins and Aggregation.md b/odata-data-aggregation-ext/4 Cross-Joins and Aggregation.md
        index f4626b187..48b9612c3 100644
        --- a/odata-data-aggregation-ext/4 Cross-Joins and Aggregation.md	
        +++ b/odata-data-aggregation-ext/4 Cross-Joins and Aggregation.md	
        @@ -18,7 +18,7 @@ GET /service/$crossjoin(Products,Sales)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Collection(Edm.ComplexType)",
        +  "@context": "$metadata#Collection(Edm.ComplexType)",
           "value": [
             { "Products": { "Name": "Paper" }, "Sales": { "Amount": 1 } },
             { "Products": { "Name": "Sugar" }, "Sales": { "Amount": 2 } },
        @@ -40,17 +40,17 @@ GET /service/$crossjoin(Products,Sales)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Collection(Edm.ComplexType)",
        +  "@context": "$metadata#Collection(Edm.ComplexType)",
           "value": [
             { "Products": { "Name": "Coffee" },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total": 12 } },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total": 12 } },
             { "Products": { "Name": "Paper"  },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total":  8 } },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total":  8 } },
             { "Products": { "Name": "Sugar"  },
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": { "Total@odata.type": "Decimal", "Total":  4 } }
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": { "Total@type": "Decimal", "Total":  4 } }
           ]
         }
         ```
        diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md
        index 1da4fc207..80f719f9c 100644
        --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md	
        +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md	
        @@ -262,7 +262,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isdescendant(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA Central",      "Name": "EMEA Central" },
             { "ID": "Sales Netherlands", "Name": "Sales Netherlands" },
        @@ -289,7 +289,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isdescendant(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA Central", "Name": "EMEA Central" },
             { "ID": "EMEA South",   "Name": "EMEA South" },
        @@ -311,7 +311,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isleaf(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "Sales Office London",   "Name": "Sales Office London" },
             { "ID": "Sales Office New York", "Name": "Sales Office New York" },
        @@ -333,7 +333,7 @@ GET /service/SalesOrganizations?$filter=Aggregation.isleaf(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
        +  "@context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
           "value": [
             { "ID": "Sales Office London",   "Name": "Sales Office London",
               "Superordinate": { "ID": "EMEA United Kingdom" } },
        @@ -358,7 +358,7 @@ GET /service/Sales?$select=ID&$filter=Aggregation.isdescendant(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(ID)",
        +  "@context": "$metadata#Sales(ID)",
           "value": [
             { "ID": 6 },
             { "ID": 7 },
        diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md
        index 540920d96..7cd072b9b 100644
        --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md	
        +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md	
        @@ -110,12 +110,12 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "EMEA",  "Name": "EMEA",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US",    "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "Sales", "Name": "Sales",
               "Superordinate": null }
           ]
        @@ -134,14 +134,14 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "US West", "Name": "US West",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } },
             { "ID": "US",      "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US East", "Name": "US East",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } }
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
           ]
         }
         ```
        @@ -161,7 +161,7 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales",
        +  "@context": "$metadata#Sales",
           "value": [
             { "ID": "4", "Amount": 8,
               "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
        @@ -263,12 +263,12 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#SalesOrganizations",
        +  "@context": "$metadata#SalesOrganizations",
           "value": [
             { "ID": "US",      "Name": "US",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } },
        +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
             { "ID": "US East", "Name": "US East",
        -      "Superordinate": { "@odata.id": "SalesOrganizations('US')" } }
        +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
           ]
         }
         ```
        @@ -374,7 +374,7 @@ GET /service/SalesOrganizations?$apply=
         results in
         ```json
         {
        -  "@odata.context":
        +  "@context":
               "$metadata#SalesOrganizations(ID,Name,SubOrgCnt,Superordinate(ID))",
           "value": [
             { "ID": "US West",      "Name": "US West",
        @@ -415,18 +415,18 @@ GET /service/Sales?$apply=groupby(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(),
        +  "@context": "$metadata#Sales(SalesOrganization(),
                                              TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl":  7,
        -      "TotalAmountExcl@odata.type": "Decimal" ,"TotalAmountExcl":  7 },
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 },
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 19,
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
               "TotalAmountExcl": null },
             { "SalesOrganization": { "ID": "US East", "Name": "US East" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 12,
        -      "TotalAmountExcl@odata.type": "Decimal", "TotalAmountExcl": 12 }
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
        +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 }
           ]
         }
         ```
        @@ -443,7 +443,7 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
        +  "@context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
           "value": [
             { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" },
               "TotalAmount": null },
        diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md
        index cb2a3c5ac..25e4db50e 100644
        --- a/odata-data-aggregation-ext/7 Examples.md	
        +++ b/odata-data-aggregation-ext/7 Examples.md	
        @@ -16,7 +16,7 @@ GET /service/Customers?$apply=groupby((Name))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(Name)",
        +  "@context": "$metadata#Customers(Name)",
           "value": [
             { "Name": "Luc" },
             { "Name": "Joe" },
        @@ -38,7 +38,7 @@ GET /service/Sales?$apply=groupby((Customer/Name))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Name))",
        +  "@context": "$metadata#Sales(Customer(Name))",
           "value": [
             { "Customer": { "Name": "Joe" } },
             { "Customer": { "Name": "Sue" } }
        @@ -61,7 +61,7 @@ GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Name,ID))",
        +  "@context": "$metadata#Sales(Customer(Name,ID))",
           "value": [
             { "Customer": { "Name": "Joe", "ID": "C1" } },
             { "Customer": { "Name": "Sue", "ID": "C2" } },
        @@ -85,7 +85,7 @@ GET /service/Sales?$apply=groupby((Customer))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer())",
        +  "@context": "$metadata#Sales(Customer())",
           "value": [
             { "Customer": { "ID": "C1", "Name": "Joe", "Country": "USA" } },
             { "Customer": { "ID": "C2", "Name": "Sue", "Country": "USA" } },
        @@ -103,7 +103,7 @@ GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID,Product/Name))
         and results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
        +  "@context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
           "value": [
             { "Customer": { "Name": "Joe", "ID": "C1" },
               "Product": { "Name": "Coffee"} },
        @@ -133,13 +133,13 @@ GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(SalesModel.FoodProduct/Rating,
        +  "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
                                              SalesModel.NonFoodProduct/RatingClass)",
           "value": [
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": 5 },
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": null },
        -    { "@odata.type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
        -    { "@odata.type": "#SalesModel.NonFoodProduct", "RatingClass": null }
        +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
        +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
        +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": null }
           ]
         }
         ```
        @@ -153,10 +153,10 @@ GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating))
         results in a third group representing entities with no `SalesModel.FoodProduct/Rating`, including the `SalesModel.NonFoodProduct`s:
         ```json
         {
        -  "@odata.context": "$metadata#Products(@Core.AnyStructure)",
        +  "@context": "$metadata#Products(@Core.AnyStructure)",
           "value": [
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": 5 },
        -    { "@odata.type": "#SalesModel.FoodProduct", "Rating": null },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
        +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
             { }
           ]
         }
        @@ -176,12 +176,12 @@ GET /service/Products?$apply=groupby((Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,Total)",
        +  "@context": "$metadata#Products(Name,Total)",
           "value": [
        -    { "Name": "Coffee", "Total@odata.type": "Decimal", "Total":   12 },
        -    { "Name": "Paper",  "Total@odata.type": "Decimal", "Total":    8 },
        +    { "Name": "Coffee", "Total@type": "Decimal", "Total":   12 },
        +    { "Name": "Paper",  "Total@type": "Decimal", "Total":    8 },
             { "Name": "Pencil",                                "Total": null },
        -    { "Name": "Sugar",  "Total@odata.type": "Decimal", "Total":    4 }
        +    { "Name": "Sugar",  "Total@type": "Decimal", "Total":    4 }
           ]
         }
         ```
        @@ -198,20 +198,20 @@ GET /service/Products?$apply=addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(AggregatedSales())",
        +  "@context": "$metadata#Products(AggregatedSales())",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total": 12 } ] },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total": 12 } ] },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total":  8 } ] },
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  8 } ] },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        +      "AggregatedSales@context": "#Sales(Total)",
               "AggregatedSales": [ {                              "Total": null } ] },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "AggregatedSales@odata.context": "#Sales(Total)",
        -      "AggregatedSales": [ { "Total@odata.type": "Decimal", "Total":  4 } ] }
        +      "AggregatedSales@context": "#Sales(Total)",
        +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  4 } ] }
           ]
         }
         ```
        @@ -225,16 +225,16 @@ GET /service/Products?$compute=Sales/aggregate(Amount with sum) as Total
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(*,Total)",
        +  "@context": "$metadata#Products(*,Total)",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
        -      "Total@odata.type": "Decimal", "Total":  8 },
        +      "Total@type": "Decimal", "Total":  8 },
             { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
                                              "Total": null },
             { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
        -      "Total@odata.type": "Decimal", "Total":  4 }
        +      "Total@type": "Decimal", "Total":  4 }
           ]
         }
         ```
        @@ -252,17 +252,17 @@ GET /service/Products?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,TotalSales())",
        +  "@context": "$metadata#Products(Name,TotalSales())",
           "value": [
             { "Name": "Coffee",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total": 12 } },
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total": 12 } },
             { "Name": "Paper",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total":  8 } },
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total":  8 } },
             { "Name": "Sugar",
        -      "TotalSales@odata.context": "#Sales(Total)/$entity",
        -      "TotalSales": { "Total@odata.type": "Decimal", "Total":  4 } }
        +      "TotalSales@context": "#Sales(Total)/$entity",
        +      "TotalSales": { "Total@type": "Decimal", "Total":  4 } }
           ]
         }
         ```
        @@ -279,7 +279,7 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),AverageAmount)",
        +  "@context": "$metadata#Sales(Customer(Country),AverageAmount)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "AverageAmount": 1.6666666666666667 },
        @@ -300,12 +300,12 @@ GET /service/Products?$apply=groupby((Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,SalesCount)",
        +  "@context": "$metadata#Products(Name,SalesCount)",
           "value": [
        -    { "Name": "Coffee", "SalesCount@odata.type": "Decimal", "SalesCount": 2 },
        -    { "Name": "Paper",  "SalesCount@odata.type": "Decimal", "SalesCount": 4 },
        -    { "Name": "Pencil", "SalesCount@odata.type": "Decimal", "SalesCount": 0 },
        -    { "Name": "Sugar",  "SalesCount@odata.type": "Decimal", "SalesCount": 2 }
        +    { "Name": "Coffee", "SalesCount@type": "Decimal", "SalesCount": 2 },
        +    { "Name": "Paper",  "SalesCount@type": "Decimal", "SalesCount": 4 },
        +    { "Name": "Pencil", "SalesCount@type": "Decimal", "SalesCount": 0 },
        +    { "Name": "Sugar",  "SalesCount@type": "Decimal", "SalesCount": 2 }
           ]
         }
         ```
        @@ -323,23 +323,23 @@ GET /service/Products?$apply=groupby((Name),addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,AggregatedSales())",
        +  "@context": "$metadata#Products(Name,AggregatedSales())",
           "value": [
             { "Name": "Coffee",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 2,
        -          "TotalAmount@odata.type": "Decimal", "TotalAmount": 12 } ] },
        +          "TotalAmount@type": "Decimal", "TotalAmount": 12 } ] },
             { "Name": "Paper",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 4,
        -          "TotalAmount@odata.type": "Decimal", "TotalAmount":  8 } ] },
        +          "TotalAmount@type": "Decimal", "TotalAmount":  8 } ] },
             { "Name": "Pencil",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 0, "TotalAmount":  null } ] },
             { "Name": "Sugar",
        -      "AggregatedSales@odata.context": "#Sales(SalesCount,TotalAmount)",
        +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
               "AggregatedSales": [ { "SalesCount": 2,
        -          "TotalAmount@odata.type": "Decimal",  "TotalAmount":  4 } ] }
        +          "TotalAmount@type": "Decimal",  "TotalAmount":  4 } ] }
           ]
         }
         ```
        @@ -355,7 +355,7 @@ GET /service/Products?$filter=Sales/aggregate(Amount with sum) ge 10
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products",
        +  "@context": "$metadata#Products",
           "value": [
             { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06 },
             { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14 }
        @@ -372,7 +372,7 @@ GET /service/Customers?$orderby=Sales/aggregate(Amount with sum) desc
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers",
        +  "@context": "$metadata#Customers",
           "value": [
             { "ID": "C2", "Name": "Sue", "Country": "USA" },
             { "ID": "C1", "Name": "Joe", "Country": "USA" },
        @@ -392,23 +392,23 @@ GET /service/Sales?$compute=Amount divby $these/aggregate(Amount with sum)
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(*,Contribution)",
        +  "@context": "$metadata#Sales(*,Contribution)",
           "value": [
        -    { "ID": 1, "Amount": 1, "Contribution@odata.type": "Decimal",
        +    { "ID": 1, "Amount": 1, "Contribution@type": "Decimal",
                                     "Contribution": 0.0416666666666667 },
        -    { "ID": 2, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 2, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 },
        -    { "ID": 3, "Amount": 4, "Contribution@odata.type": "Decimal",
        +    { "ID": 3, "Amount": 4, "Contribution@type": "Decimal",
                                     "Contribution": 0.1666666666666667 },
        -    { "ID": 4, "Amount": 8, "Contribution@odata.type": "Decimal",
        +    { "ID": 4, "Amount": 8, "Contribution@type": "Decimal",
                                     "Contribution": 0.3333333333333333 },
        -    { "ID": 5, "Amount": 4, "Contribution@odata.type": "Decimal",
        +    { "ID": 5, "Amount": 4, "Contribution@type": "Decimal",
                                     "Contribution": 0.1666666666666667 },
        -    { "ID": 6, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 6, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 },
        -    { "ID": 7, "Amount": 1, "Contribution@odata.type": "Decimal",
        +    { "ID": 7, "Amount": 1, "Contribution@type": "Decimal",
                                     "Contribution": 0.0416666666666667 },
        -    { "ID": 8, "Amount": 2, "Contribution@odata.type": "Decimal",
        +    { "ID": 8, "Amount": 2, "Contribution@type": "Decimal",
                                     "Contribution": 0.0833333333333333 }
           ]
         }
        @@ -424,7 +424,7 @@ GET /service/Categories?$filter=Products/any(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Categories",
        +  "@context": "$metadata#Categories",
           "value": [
             { "ID": "PG1", "Name": "Food" }
           ]
        @@ -446,14 +446,14 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
        +  "@context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
           "value": [
        -    { "Customer":    { "@odata.id": "Customers('C1')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.2916667 },
        -    { "Customer":    { "@odata.id": "Customers('C2')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.5 },
        -    { "Customer":    { "@odata.id": "Customers('C3')" },
        -      "Contribution@odata.type": "Decimal", "Contribution": 0.2083333 }
        +    { "Customer":    { "@id": "Customers('C1')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.2916667 },
        +    { "Customer":    { "@id": "Customers('C2')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.5 },
        +    { "Customer":    { "@id": "Customers('C3')" },
        +      "Contribution@type": "Decimal", "Contribution": 0.2083333 }
           ]
         }
         ```
        @@ -497,18 +497,18 @@ GET /service/Customers?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(
        +  "@context": "$metadata#Customers(
                                        Addresses(AugmentedSalesOrganization())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "US",
               "Addresses": [
                 { "Locality": "Seattle",
                   "AugmentedSalesOrganization":
        -          { "@odata.context": "#SalesOrganizations/$entity",
        +          { "@context": "#SalesOrganizations/$entity",
                     "ID": "US West", "SalesRegion": "US" } },
                 { "Locality": "DC",
                   "AugmentedSalesOrganization":
        -          { "@odata.context": "#SalesOrganizations/$entity",
        +          { "@context": "#SalesOrganizations/$entity",
                     "ID": "US",      "SalesRegion": "Corporate Sales" } },
               ]
             }, ...
        @@ -530,28 +530,28 @@ GET /service/Categories?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Categories(FilteredProducts()",
        +  "@context": "$metadata#Categories(FilteredProducts()",
           "value": [
             { "ID": "PG1", "Name": "Food",
        -      "FilteredProducts@odata.context": "#Products(FilteredSales())",
        +      "FilteredProducts@context": "#Products(FilteredSales())",
               "FilteredProducts": [
                 { "ID": "P1", "Name": "Sugar",  "Color": "White",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [] },
                 { "ID": "P2", "Name": "Coffee", "Color": "Brown",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [ { "ID": 3, "Amount": 4 },
                                      { "ID": 4, "Amount": 8 } ] }
               ]
             },
             { "ID": "PG2", "Name": "Non-Food",
        -      "FilteredProducts@odata.context": "#Products(FilteredSales())",
        +      "FilteredProducts@context": "#Products(FilteredSales())",
               "FilteredProducts": [
                 { "ID": "P3", "Name": "Paper",  "Color": "White",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [ { "ID": 5, "Amount": 4 } ] },
                 { "ID": "P4", "Name": "Pencil", "Color": "Black",
        -          "FilteredSales@odata.context": "#Sales",
        +          "FilteredSales@context": "#Sales",
                   "FilteredSales": [] }
               ]
             }
        @@ -580,29 +580,29 @@ GET /service/Customers?$apply=addnested(Sales,
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Customers(GroupedSales())",
        +  "@context": "$metadata#Customers(GroupedSales())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "USA",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { },
                 { }
               ] },
             { "ID": "C2", "Name": "Sue", "Country": "USA",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { }
               ] },
             { "ID": "C3", "Name": "Joe", "Country": "Netherlands",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [
                 { },
                 { }
               ] },
             { "ID": "C4", "Name": "Luc", "Country": "France",
        -      "GroupedSales@odata.context": "#Sales(@Core.AnyStructure)",
        +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
               "GroupedSales": [ ] }
           ]
         }
        @@ -618,22 +618,22 @@ GET /service/Customers?$apply=outerjoin(Sales as ProductSales)
         returns the different combinations of products sold per country:
         ```json
         {
        -  "@odata.context":"$metadata#Customers(Country,ProductSales())",
        +  "@context":"$metadata#Customers(Country,ProductSales())",
           "value": [
             { "Country": "Netherlands",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Paper"  } } },
             { "Country": "Netherlands",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Sugar"  } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Coffee" } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Paper"  } } },
             { "Country": "USA",
        -      "ProductSales@odata.context": "#Sales(Product(Name))/$entity",
        +      "ProductSales@context": "#Sales(Product(Name))/$entity",
               "ProductSales": { "Product": { "Name": "Sugar"  } } },
             { "Country": "France", "ProductSales": null }
           ]
        @@ -655,14 +655,14 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
        +  "@context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Actual@odata.type": "Decimal", "Actual":  5,
        -      "Forecast@odata.type": "Decimal", "Forecast": 4 },
        +      "Actual@type": "Decimal", "Actual":  5,
        +      "Forecast@type": "Decimal", "Forecast": 4 },
             { "Customer": { "Country": "USA" },
        -      "Actual@odata.type": "Decimal", "Actual": 19,
        -      "Forecast@odata.type": "Decimal", "Forecast": 21 }
        +      "Actual@type": "Decimal", "Actual": 19,
        +      "Forecast@type": "Decimal", "Forecast": 21 }
           ]
         }
         ```
        @@ -678,7 +678,7 @@ GET /service/Sales?$apply=groupby((Customer/Country),aggregate(Amount))
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Amount)",
        +  "@context": "$metadata#Sales(Customer(Country),Amount)",
           "value": [
             { "Customer": { "Country": "Netherlands" }, "Amount":  5 },
             { "Customer": { "Country": "USA" },         "Amount": 19 }
        @@ -745,14 +745,14 @@ GET /service/Sales?$apply=groupby((Customer/Country),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
        +  "@context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5,
        -      "AvgAmt@odata.type": "Decimal", "AvgAmt": 1.6666667 },
        +      "Total@type": "Decimal", "Total":  5,
        +      "AvgAmt@type": "Decimal", "AvgAmt": 1.6666667 },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19,
        -      "AvgAmt@odata.type": "Decimal", "AvgAmt": 3.8 }
        +      "Total@type": "Decimal", "Total": 19,
        +      "AvgAmt@type": "Decimal", "AvgAmt": 3.8 }
           ]
         }
         ```
        @@ -772,22 +772,22 @@ GET /service/Products?$apply=groupby((Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Name,Total,AggregatedSales())",
        +  "@context": "$metadata#Products(Name,Total,AggregatedSales())",
           "value": [
             { "Name": "Coffee", "Total":   12,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 6 } ] },
             { "Name": "Paper",  "Total":    8,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 2 } ] },
             { "Name": "Pencil", "Total": null,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
               "AggregatedSales": [ { "AvgAmt": null } ] },
             { "Name": "Sugar",  "Total":    4,
        -      "AggregatedSales@odata.context": "#Sales(AvgAmt)",
        -      "AggregatedSales": [ { "AvgAmt@odata.type": "Decimal",
        +      "AggregatedSales@context": "#Sales(AvgAmt)",
        +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
                                      "AvgAmt": 2 } ] }
           ]
         }
        @@ -804,12 +804,12 @@ GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total))
         will return all distinct amounts appearing in sales orders and how much money was made with deals of this amount
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Amount,Total)",
        +  "@context": "$metadata#Sales(Amount,Total)",
           "value": [
        -    { "Amount": 1, "Total@odata.type": "Decimal", "Total": 2 },
        -    { "Amount": 2, "Total@odata.type": "Decimal", "Total": 6 },
        -    { "Amount": 4, "Total@odata.type": "Decimal", "Total": 8 },
        -    { "Amount": 8, "Total@odata.type": "Decimal", "Total": 8 }
        +    { "Amount": 1, "Total@type": "Decimal", "Total": 2 },
        +    { "Amount": 2, "Total@type": "Decimal", "Total": 6 },
        +    { "Amount": 4, "Total@type": "Decimal", "Total": 8 },
        +    { "Amount": 8, "Total@type": "Decimal", "Total": 8 }
           ]
         }
         ```
        @@ -832,19 +832,19 @@ GET /service/Sales?$apply=concat(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Total)",
           "value": [
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12
        +      "Total@type": "Decimal", "Total": 12
             },
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3
        +      "Total@type": "Decimal", "Total":  3
             },
             { "Customer":{ "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total": 19
        +      "Total@type": "Decimal", "Total": 19
             },
             { "Customer":{ "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  5
        +      "Total@type": "Decimal", "Total":  5
             }
           ]
         }
        @@ -860,22 +860,22 @@ GET /service/Sales?$apply=groupby((Customer/Country,Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
           "value": [
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3
        +      "Total@type": "Decimal", "Total":  3
             },
             { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2
        +      "Total@type": "Decimal", "Total":  2
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total":  2
        +      "Total@type": "Decimal", "Total":  2
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12
        +      "Total@type": "Decimal", "Total": 12
             },
             { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  5
        +      "Total@type": "Decimal", "Total":  5
             }
           ]
         }
        @@ -893,7 +893,7 @@ GET /service/Sales?$apply=concat(
         In the result, `Sales` entities 4 and 6 occur twice each with contradictory values of the dynamic property `per`. If a UI consuming the response presents the two groupings in separate columns based on the `per` property, no contradiction effectively arises.
         ```json
         {
        -  "@odata.context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
        +  "@context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
           "value": [
             { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
               "ID": "3", "Amount": 4, "per": "Customer" },
        @@ -939,18 +939,18 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Customer(Country),Total)",
        +  "@context": "$metadata#Sales(Customer(Country),Total)",
           "value": [
             { "Customer": { "Country": "Netherlands" },
               "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total":  3 },
        +      "Total@type": "Decimal", "Total":  3 },
             { "Customer": { "Country": "Netherlands" },
        -      "Total@odata.type": "Decimal", "Total":  2 },
        +      "Total@type": "Decimal", "Total":  2 },
             { "Customer": { "Country": "USA" },
               "Product": { "Name": "Coffee" },
        -      "Total@odata.type": "Decimal", "Total": 12 },
        +      "Total@type": "Decimal", "Total": 12 },
             { "Customer": { "Country": "USA" },
        -      "Total@odata.type": "Decimal", "Total":  7 }
        +      "Total@type": "Decimal", "Total":  7 }
           ]
         }
         ```
        @@ -998,24 +998,24 @@ GET /service/Sales?$apply=concat(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(CustomerCountryAverage)",
        +  "@context": "$metadata#Sales(CustomerCountryAverage)",
           "value": [
             { "Customer": { "Country": "USA", "ID": "C1" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage":   7 },
             { "Customer": { "Country": "USA", "ID": "C2" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage":  12 },
             { "Customer": { "Country": "USA" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 9.5 },
             { "Customer": { "Country": "Netherlands", "ID": "C3" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 5 },
             { "Customer": { "Country": "Netherlands" },
        -      "CustomerCountryAverage@odata.type":"Decimal",
        +      "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 5 },
        -    { "CustomerCountryAverage@odata.type":"Decimal",
        +    { "CustomerCountryAverage@type":"Decimal",
               "CustomerCountryAverage": 7.25 }
           ]
         }
        @@ -1044,17 +1044,17 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(TotalAmount,SalesOrganization())",
        +  "@context": "$metadata#Sales(TotalAmount,SalesOrganization())",
           "value": [
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount": 19,
        +    { "TotalAmount@type": "Decimal", "TotalAmount": 19,
               "SalesOrganization": { "ID": "US",      "Name": "US",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount": 12,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "TotalAmount@type": "Decimal", "TotalAmount": 12,
               "SalesOrganization": { "ID": "US East", "Name": "US East",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "TotalAmount@odata.type": "Decimal", "TotalAmount":  7,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "TotalAmount@type": "Decimal", "TotalAmount":  7,
               "SalesOrganization": { "ID": "US West", "Name": "US West",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } }
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } }
           ]
         }
         ```
        @@ -1077,24 +1077,24 @@ GET /service/Sales?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
        +  "@context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
           "value": [
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "US",           "Name": "US",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 1,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
               "SalesOrganization": { "ID": "US East",      "Name": "US East",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 1,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
               "SalesOrganization": { "ID": "US West",      "Name": "US West",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('US')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "EMEA",         "Name": "EMEA",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('Sales')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 2,
        +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
               "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central",
        -        "Superordinate": { "@odata.id": "SalesOrganizations('EMEA')" } } },
        -    { "PaperSalesCount@odata.type": "Decimal", "PaperSalesCount": 4,
        +        "Superordinate": { "@id": "SalesOrganizations('EMEA')" } } },
        +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 4,
               "SalesOrganization": { "ID": "Sales",        "Name": "Sales",
                 "Superordinate": null } }
           ]
        @@ -1190,18 +1190,18 @@ GET /service/Sales?$apply=groupby(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(ID),
        +  "@context": "$metadata#Sales(SalesOrganization(ID),
                                              TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 19,
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
               "TotalAmountExcl": null },
             { "SalesOrganization": { "ID": "US East", "Name": "US East" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl": 12,
        -      "TotalAmountExcl@odata.type": "Decimal", "TotalAmountExcl": 12 },
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
        +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 },
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
        -      "TotalAmountIncl@odata.type": "Decimal", "TotalAmountIncl":  7,
        -      "TotalAmountExcl@odata.type": "Decimal" ,"TotalAmountExcl":  7 }
        +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 }
           ]
         }
         ```
        @@ -1222,7 +1222,7 @@ GET /service/Products?$apply=traverse(
         The result contains multiple instances of the same `Product` that differ in their `Sales` navigation property even though they agree in their `ID` key property. The node $x$ with $x/{\tt ID}={}$`"US"` has $σ(x)={}$`{"Sales": [{"SalesOrganization": {"ID": "US"}}]}`.
         ```json
         {
        -  "@odata.context":
        +  "@context":
               "$metadata#Products(ID,Sales(SalesOrganization(ID)))",
           "value": [
             { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
        @@ -1260,7 +1260,7 @@ GET /service/Products?$apply=
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Products(Sales(SalesOrganization(ID)),
        +  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),
                                                 SoldProducts)",
           "value": [
             { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
        @@ -1314,17 +1314,17 @@ GET /service/Sales?$apply=groupby((rolluprecursive(
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
        +  "@context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
           "value": [
             { "SalesOrganization": { "ID": "Sales",   "ProductCategories": [ ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount": 24 },
        +      "TotalAmount@type": "Decimal", "TotalAmount": 24 },
             { "SalesOrganization": { "ID": "US",      "ProductCategories": [
        -      { "@odata.id": "ProductCategories('Food')" },
        -      { "@odata.id": "ProductCategories('Cereals')" } ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount": 19 },
        +      { "@id": "ProductCategories('Food')" },
        +      { "@id": "ProductCategories('Cereals')" } ] },
        +      "TotalAmount@type": "Decimal", "TotalAmount": 19 },
             { "SalesOrganization": { "ID": "US West", "ProductCategories": [
        -      { "@odata.id": "ProductCategories('Organic cereals')" } ] },
        -      "TotalAmount@odata.type": "Decimal", "TotalAmount":  7 }
        +      { "@id": "ProductCategories('Organic cereals')" } ] },
        +      "TotalAmount@type": "Decimal", "TotalAmount":  7 }
           ]
         }
         ```
        @@ -1357,9 +1357,9 @@ GET /service/Sales?$apply=filter(Amount le 1)
         means "filter first, then aggregate", and results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Total)",
        +  "@context": "$metadata#Sales(Total)",
           "value": [
        -    { "Total@odata.type": "Decimal", "Total": 2 }
        +    { "Total@type": "Decimal", "Total": 2 }
           ]
         }
         ```
        @@ -1377,12 +1377,12 @@ GET /service/Sales?$apply=filter(Amount le 2)/groupby((Product/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
        +  "@context": "$metadata#Sales(Product(Name),Total)",
           "value": [
             { "Product": { "Name": "Paper" },
        -      "Total@odata.type": "Decimal", "Total": 4 },
        +      "Total@type": "Decimal", "Total": 4 },
             { "Product": { "Name": "Sugar" },
        -      "Total@odata.type": "Decimal", "Total": 4 }
        +      "Total@type": "Decimal", "Total": 4 }
           ]
         }
         ```
        @@ -1411,13 +1411,13 @@ GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
         results in
         ```json
         {
        -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
        +  "@context": "$metadata#Cities(Continent(Name),Country(Name),
                                               TotalPopulation)",
           "value": [
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
        -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
        +      "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
        -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
        +      "TotalPopulation@type": "Int32", "TotalPopulation": 1408000000 },
             ...
           ]
         }
        @@ -1507,15 +1507,15 @@ GET /service/Sales?$apply=groupby((Product/Category/ID),
         results in
         ```json
         {
        -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
        +  "@context":"$metadata#Sales(Product(Category(ID)),Customers())",
           "value": [
             { "Product": { "Category": { "ID": "PG1" } },
        -      "Customers@odata.context": "#Sales(Customer(ID))",
        +      "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] },
             { "Product": { "Category": { "ID": "PG2" } },
        -      "Customers@odata.context": "#Sales(Customer(ID))",
        +      "Customers@context": "#Sales(Customer(ID))",
               "Customers": [ { "Customer": { "ID": "C1" } },
                              { "Customer": { "ID": "C2" } },
                              { "Customer": { "ID": "C3" } } ] }
        
        From a60efb96c44b504cbddb09ed99f6b674df3a8c7e Mon Sep 17 00:00:00 2001
        From: D024504 
        Date: Fri, 26 May 2023 11:56:28 +0200
        Subject: [PATCH 021/116] indentation
        
        ---
         .../odata-data-aggregation-ext.html           | 76 +++++++++----------
         .../odata-data-aggregation-ext.md             | 16 ++--
         .../3.2 Basic Aggregation.md                  |  2 +-
         .../6 Hierarchical Transformations.md         |  2 +-
         odata-data-aggregation-ext/7 Examples.md      | 12 ++-
         5 files changed, 51 insertions(+), 57 deletions(-)
        
        diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
        index 654f22160..27e2d55a1 100644
        --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
        +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
        @@ -2844,7 +2844,7 @@ 

        3.2.1.2
        {
           "@context": "$metadata#Sales(Customer(Country),
        -                                     Product(Category(Name)),Total)",
        +                               Product(Category(Name)),Total)",
           "value": [
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
        @@ -5007,7 +5007,7 @@ 

        5.5
        {
           "@context": "$metadata#Sales(SalesOrganization(),
        -                                     TotalAmountIncl,TotalAmountExcl)",
        +                               TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
               "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        @@ -5197,7 +5197,7 @@ 

        7
        {
           "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
        -                                     SalesModel.NonFoodProduct/RatingClass)",
        +                                  SalesModel.NonFoodProduct/RatingClass)",
           "value": [
             { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
             { "@type": "#SalesModel.FoodProduct", "Rating": null },
        @@ -5544,23 +5544,22 @@ 

        7

        results in

        {
        -  "@context": "$metadata#Customers(
        -                               Addresses(AugmentedSalesOrganization())",
        -  "value": [
        -    { "ID": "C1", "Name": "Joe", "Country": "US",
        -      "Addresses": [
        -        { "Locality": "Seattle",
        -          "AugmentedSalesOrganization":
        -          { "@context": "#SalesOrganizations/$entity",
        -            "ID": "US West", "SalesRegion": "US" } },
        -        { "Locality": "DC",
        -          "AugmentedSalesOrganization":
        -          { "@context": "#SalesOrganizations/$entity",
        -            "ID": "US",      "SalesRegion": "Corporate Sales" } },
        -      ]
        -    }, ...
        -  ]
        -}
        + "@context": "$metadata#Customers(Addresses(AugmentedSalesOrganization())", + "value": [ + { "ID": "C1", "Name": "Joe", "Country": "US", + "Addresses": [ + { "Locality": "Seattle", + "AugmentedSalesOrganization": + { "@context": "#SalesOrganizations/$entity", + "ID": "US West", "SalesRegion": "US" } }, + { "Locality": "DC", + "AugmentedSalesOrganization": + { "@context": "#SalesOrganizations/$entity", + "ID": "US", "SalesRegion": "Corporate Sales" } }, + ] + }, ... + ] +}

        addnested transformations can be nested.

        @@ -6202,7 +6201,7 @@

        7.4
        {
           "@context": "$metadata#Sales(SalesOrganization(ID),
        -                                     TotalAmountIncl,TotalAmountExcl)",
        +                               TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
               "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
        @@ -6272,23 +6271,22 @@ 

        7.4

        results in

        {
        -  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),
        -                                        SoldProducts)",
        -  "value": [
        -    { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
        -      "SoldProducts": "P1,P2,P3" },
        -    { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ],
        -      "SoldProducts": "P1,P3" },
        -    { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ],
        -      "SoldProducts": "P1,P3" },
        -    { "Sales": [ { "SalesOrganization": { "ID": "US" } } ],
        -      "SoldProducts": "P1,P2,P3" },
        -    { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ],
        -      "SoldProducts": "P2,P3" },
        -    { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ],
        -      "SoldProducts": "P1,P2,P3" }
        -  ]
        -}
        + "@context": "$metadata#Products(Sales(SalesOrganization(ID)),SoldProducts)", + "value": [ + { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ], + "SoldProducts": "P1,P2,P3" }, + { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ], + "SoldProducts": "P1,P3" }, + { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ], + "SoldProducts": "P1,P3" }, + { "Sales": [ { "SalesOrganization": { "ID": "US" } } ], + "SoldProducts": "P1,P2,P3" }, + { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ], + "SoldProducts": "P2,P3" }, + { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ], + "SoldProducts": "P1,P2,P3" } + ] +}

        ⚠ Example 109: Assume an extension of the data model where a @@ -6439,7 +6437,7 @@

        7.4
        {
           "@context": "$metadata#Cities(Continent(Name),Country(Name),
        -                                      TotalPopulation)",
        +                                TotalPopulation)",
           "value": [
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
               "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
        diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
        index bed978ba7..e8f828e75 100644
        --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
        +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md
        @@ -1547,7 +1547,7 @@ results in seven entities for the finest grouping level
         ```json
         {
           "@context": "$metadata#Sales(Customer(Country),
        -                                     Product(Category(Name)),Total)",
        +                               Product(Category(Name)),Total)",
           "value": [
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
        @@ -2952,7 +2952,7 @@ results in
         ```json
         {
           "@context": "$metadata#Sales(SalesOrganization(),
        -                                     TotalAmountIncl,TotalAmountExcl)",
        +                               TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
               "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        @@ -3139,7 +3139,7 @@ results in
         ```json
         {
           "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
        -                                     SalesModel.NonFoodProduct/RatingClass)",
        +                                  SalesModel.NonFoodProduct/RatingClass)",
           "value": [
             { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
             { "@type": "#SalesModel.FoodProduct", "Rating": null },
        @@ -3502,8 +3502,7 @@ GET /service/Customers?$apply=
         results in
         ```json
         {
        -  "@context": "$metadata#Customers(
        -                               Addresses(AugmentedSalesOrganization())",
        +  "@context": "$metadata#Customers(Addresses(AugmentedSalesOrganization())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "US",
               "Addresses": [
        @@ -4196,7 +4195,7 @@ results in
         ```json
         {
           "@context": "$metadata#Sales(SalesOrganization(ID),
        -                                     TotalAmountIncl,TotalAmountExcl)",
        +                               TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
               "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
        @@ -4265,8 +4264,7 @@ GET /service/Products?$apply=
         results in
         ```json
         {
        -  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),
        -                                        SoldProducts)",
        +  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),SoldProducts)",
           "value": [
             { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
               "SoldProducts": "P1,P2,P3" },
        @@ -4417,7 +4415,7 @@ results in
         ```json
         {
           "@context": "$metadata#Cities(Continent(Name),Country(Name),
        -                                      TotalPopulation)",
        +                                TotalPopulation)",
           "value": [
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
               "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
        diff --git a/odata-data-aggregation-ext/3.2 Basic Aggregation.md b/odata-data-aggregation-ext/3.2 Basic Aggregation.md
        index 4180c15c8..71f5bddd5 100644
        --- a/odata-data-aggregation-ext/3.2 Basic Aggregation.md	
        +++ b/odata-data-aggregation-ext/3.2 Basic Aggregation.md	
        @@ -464,7 +464,7 @@ results in seven entities for the finest grouping level
         ```json
         {
           "@context": "$metadata#Sales(Customer(Country),
        -                                     Product(Category(Name)),Total)",
        +                               Product(Category(Name)),Total)",
           "value": [
             { "Customer": { "Country": "USA", "Name": "Joe" },
               "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
        diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md
        index 7cd072b9b..08d6c894a 100644
        --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md	
        +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md	
        @@ -416,7 +416,7 @@ results in
         ```json
         {
           "@context": "$metadata#Sales(SalesOrganization(),
        -                                     TotalAmountIncl,TotalAmountExcl)",
        +                               TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US West", "Name": "US West" },
               "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
        diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md
        index 25e4db50e..14cfc7dac 100644
        --- a/odata-data-aggregation-ext/7 Examples.md	
        +++ b/odata-data-aggregation-ext/7 Examples.md	
        @@ -134,7 +134,7 @@ results in
         ```json
         {
           "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
        -                                     SalesModel.NonFoodProduct/RatingClass)",
        +                                  SalesModel.NonFoodProduct/RatingClass)",
           "value": [
             { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
             { "@type": "#SalesModel.FoodProduct", "Rating": null },
        @@ -497,8 +497,7 @@ GET /service/Customers?$apply=
         results in
         ```json
         {
        -  "@context": "$metadata#Customers(
        -                               Addresses(AugmentedSalesOrganization())",
        +  "@context": "$metadata#Customers(Addresses(AugmentedSalesOrganization())",
           "value": [
             { "ID": "C1", "Name": "Joe", "Country": "US",
               "Addresses": [
        @@ -1191,7 +1190,7 @@ results in
         ```json
         {
           "@context": "$metadata#Sales(SalesOrganization(ID),
        -                                     TotalAmountIncl,TotalAmountExcl)",
        +                               TotalAmountIncl,TotalAmountExcl)",
           "value": [
             { "SalesOrganization": { "ID": "US",      "Name": "US" },
               "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
        @@ -1260,8 +1259,7 @@ GET /service/Products?$apply=
         results in
         ```json
         {
        -  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),
        -                                        SoldProducts)",
        +  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),SoldProducts)",
           "value": [
             { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
               "SoldProducts": "P1,P2,P3" },
        @@ -1412,7 +1410,7 @@ results in
         ```json
         {
           "@context": "$metadata#Cities(Continent(Name),Country(Name),
        -                                      TotalPopulation)",
        +                                TotalPopulation)",
           "value": [
             { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
               "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
        
        From 6de8c2ee99237c53df5f3b53f9bcf94052bf19f6 Mon Sep 17 00:00:00 2001
        From: D024504 
        Date: Fri, 26 May 2023 14:52:23 +0200
        Subject: [PATCH 022/116] typos
        
        ---
         .../odata-data-aggregation-ext.html                         | 6 +++---
         .../odata-data-aggregation-ext.md                           | 2 +-
         .../3.1 Fundamentals of Input and Output Sets.md            | 2 +-
         3 files changed, 5 insertions(+), 5 deletions(-)
        
        diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
        index 27e2d55a1..1a3cdc9ec 100644
        --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
        +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html
        @@ -1981,9 +1981,9 @@ 

        2.2 class="math inline">\(u_1\) is processed before \(u_2\) whenever \(u_1\) precedes \(u_2\). Likewise, in an order-preserving -sequence \(u_1,…,u_n\) we have \(i<j\) whenever \(u_2\). Likewise, in an order-preserving +sequence \(u_1,…,u_n\) we have +\(i<j\) whenever \(u_i\) precedes \(u_j\).

        The transformation Date: Fri, 26 May 2023 17:16:12 +0200 Subject: [PATCH 023/116] TC 2023-05-31 - additions Ralf (#12) --- .../odata-data-aggregation-ext.html | 2982 ++++++++--------- .../odata-data-aggregation-ext.md | 340 +- odata-data-aggregation-ext/1 Introduction.md | 3 +- ...1 Fundamentals of Input and Output Sets.md | 12 +- .../3.2 Basic Aggregation.md | 45 +- ...ions Preserving the Input Set Structure.md | 2 +- ...ations Changing the Input Set Structure.md | 2 +- ...5 Expressions Evaluable on a Collection.md | 4 +- .../6 Hierarchical Transformations.md | 4 +- odata-data-aggregation-ext/7 Examples.md | 14 +- odata-data-aggregation-ext/8 Conformance.md | 28 +- 11 files changed, 1731 insertions(+), 1705 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 27e2d55a1..36b3804c2 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -483,6 +483,11 @@

        1.1 Expression – an expression resulting in a value of an aggregatable primitive type +
      • Aggregate Expression – +argument of the aggregate transformation or function defined in section 3.2.1.1
      • Aggregatable Primitive Type – a primitive type other than Edm.Stream or subtypes of Edm.Geography or Edm.Geometry
      • @@ -518,7 +523,7 @@

        1.1
      • \(S,T\) – transformation sequences
      • \(α\) – aggregate expression, -defined below
      • +defined in section 3.2.1.1
      • \(\Gamma(A,p)\) – the collection that results from evaluating a data aggregation path \(p\) relative to @@ -1841,13 +1846,13 @@

        2.2 determined by the entity model element identified within the metadata document by the context URL of that resource OData-Protocol, section 10. Individual -instances in an input or output set can have a subtype of the input -type. (See example 69.) The transformation -sequence given as the $apply system query option is applied -to the resource addressed by the resource path. The transformations -defined below can have nested transformation sequences as parameters, -these are then applied to resources that can differ from the current -input set.

        +structured instances in an input or output set can have a subtype of the +input type. (See example 70.) The +transformation sequence given as the $apply system query +option is applied to the resource addressed by the resource path. The +transformations defined below can have nested transformation sequences +as parameters, these are then applied to resources that can differ from +the current input set.

        The structure of an instance that occurs in an input or output set is defined by the names of the structural and navigation properties that the instance contains. Instances of an input type can @@ -1913,21 +1918,21 @@

        2.2 properties, the {select-list} MUST consist of just the instance annotation AnyStructure defined in the Core vocabulary OData-VocCore. -(See example 70.)

        +(See example 71.)

        3.1.2 Sameness and Precedence

        Input sets and output sets are not sets of instances in the mathematical sense but collections, because the same instance can occur multiple times in them. In other words: A collection contains values -(which can be instances or primitive values), possibly with repetitions. -The occurrences in the collection form a set in the mathematical sense. -The cardinality of a collection is the total number of -occurrences in it. When this text describes a transformation -algorithmically and stipulates that certain steps are carried out -for each instance in a collection, this means that the steps -are carried out multiple times for the same instance if it occurs -multiple times in the collection.

        +(which can be structured instances or primitive values), possibly with +repetitions. The occurrences in the collection form a set in the +mathematical sense. The cardinality of a collection is the +total number of occurrences in it. When this text describes a +transformation algorithmically and stipulates that certain steps are +carried out for each instance in a collection, this means that +the steps are carried out multiple times for the same instance if it +occurs multiple times in the collection.

        A collection addressed by the resource path is returned by the service either as an ordered collection OData-Protocol, section 11.4.10 or as an @@ -2008,28 +2013,27 @@

        2.2 collection may still contain two occurrences whose relative order does not matter.

        The output set of a basic aggregation -transformation can contain instances of an entity type without -entity-id. After a concat transformation, -different occurrences of the same entity can differ in individual -non-declared properties. To account for such cases, the definition of -sameness given in OData-URL, section 5.1.1.1.1 -is refined here. Instances are the same if

        +transformation can contain instances of an entity type without entity +id. After a concat +transformation, different occurrences of the same entity can differ in +individual non-declared properties. To account for such cases, the +definition of sameness given in OData-URL, section +5.1.1.1.1 is refined here. Instances are the same if

        • both are instances of complex types and both are null or both have the same structure and same values with null considered different from absent or
        • -
        • both are instances of entity types without entity-id (transient +
        • both are instances of entity types without entity id (transient entities, see OData-Protocol, section 4.3) and both are null or both have the same structure and same values with null considered different from absent (informally speaking, they are compared like complex instances) or
        • -
        • (1) both are instances of the same entity type with the same -entity-id (non-transient entities, see OData-Protocol, section 4.1) and (2) the -structural and navigation properties contained in both have the same -values (for non-primitive properties the sameness of values is decided -by a recursive invocation of this definition). +
        • (1) both are instances of the same entity type with the same entity +id (non-transient entities, see OData-Protocol, +section 4.1) and (2) the structural and navigation properties +contained in both have the same values (for non-primitive properties the +sameness of values is decided by a recursive invocation of this +definition).

        Collections are the same if there is a one-to-one @@ -2072,10 +2076,11 @@

        2.2

        For a data aggregation path to be a common expression according to OData-URL, section 5.1.1, its segments must be single-valued with the possible exception of the last segment, and it -can then be evaluated relative to an instance. For the transformations -defined in this document, a data aggregation path can also be evaluated -relative to a collection \(A\), even if -it has arbitrary collection-valued segments itself.

        +can then be evaluated relative to a structured instance. For the +transformations defined in this document, a data aggregation path can +also be evaluated relative to a collection \(A\), even if it has arbitrary +collection-valued segments itself.

        To this end, the following notation is used in the subsequent sections: If \(A\) is a collection and \(p\) a data aggregation path, @@ -2154,23 +2159,25 @@

        2.2 id="3211-aggregation-algorithm">3.2.1.1 Aggregation Algorithm

        The aggregate transformation takes a comma-separated -list of one or more aggregate expressions as parameters and -returns an output set with a single instance of the input type without entity-id -containing one property per aggregate expression, representing the -aggregated value of the input set.

        -

        An aggregate expression MUST have one of the types listed below. To -compute the value of the property for a given aggregate expression, the -aggregate transformation first determines a collection -\(A\) of instances or primitive values, -based on the input set of the aggregate transformation, and -a path \(p\) that occurs in the -aggregate expression. Let \(p_1\) -denote a data aggregation path with -single- or collection-valued segments and \(p_2\) a type-cast segment. Depending on the -type of aggregate expression, \(p=p_1\) -or \(p=p_2\) or aggregate +expressions as parameters and returns an output set with a +single instance of the input +type without entity id containing one property per aggregate +expression, representing the aggregated value of the input set.

        +

        An aggregate expression MUST have one of the types listed below or be +constructed with the from +keyword. To compute the value of the property for a given aggregate +expression, the aggregate transformation first determines a +collection \(A\) of structured +instances or primitive values, based on the input set of the +aggregate transformation, and a path \(p\) that occurs in the aggregate +expression. Let \(p_1\) denote a data aggregation path with single- or +collection-valued segments and \(p_2\) +a type-cast segment. Depending on its type, the aggregate expression +contains a path \(p=p_1\) or \(p=p_2\) or \(p=p_1/p_2\). Each type of aggregate expression defines a function \(f(A)\) which the aggregate transformation evaluates to obtain the property @@ -2179,13 +2186,11 @@

        2.2 4. In types 1 and 2, the aggregate expression MUST end with the keyword with and an aggregation method \(g\). The aggregation method also determines -the type of the dynamic property. In types 1, 2 and 3 the aggregate +the type of the dynamic property. In types 1, 2, and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword as and an alias, which is then the name of -the dynamic property. More aggregate expressions can be constructed with -the from keyword, see later -section.

        +the dynamic property.

        Types of aggregate expressions:

        1. A path \(p=p_1\) or 2.2 href="#CustomAggregates">custom aggregate \(c\) defined on the collection addressed by \(p\).
          -Let \(f(A)=c(A)\), if computation of -the custom aggregate fails, the service MUST reject the request. In the -absence of an alias, the name of the property MUST be the name of the -custom aggregate, this is a dynamic property unless there is a declared -property with that name, which is allowed by the +Let \(f(A)=c(A)\), and if computation +of the custom aggregate fails, the service MUST reject the request. In +the absence of an alias, the name of the property MUST be the name of +the custom aggregate, this is a dynamic property unless there is a +declared property with that name, which is allowed by the CustomAggregate annotation. The custom aggregate also determines the type of the dynamic property.
        @@ -2257,7 +2262,7 @@

        2.2 service MUST merge them into one occurrence in \(E\) if they are complementary and MUST reject the request if they are contradictory. (See example 119.) If example 120.) If multiple occurrences of the same transient entity are reached, the service MUST keep only one occurrence in \(E\).

      • @@ -2266,8 +2271,8 @@

        2.2

      Then, if \(r\) is empty, let \(A=E\), otherwise let \(A=\Gamma(E,r)\), this consists of instances -or primitive values, possibly with repetitions.

      +class="math inline">\(A=\Gamma(E,r)\), this consists of +structured instances or primitive values, possibly with repetitions.

      3.2.1.2 Keyword as

      Aggregate expressions can define an 3.2.1.2

      The standard aggregation method max can be applied to values with a totally ordered domain to return the largest of the values, or null if there are no values to be aggregated.

      -

      The result property will have the same type as the input property

      +

      The result property will have the same type as the input +property.

      Example 11:

      GET /service/Sales?$apply=aggregate(Amount with max as MaxAmount)
      @@ -2382,9 +2388,8 @@

      3.2.1.2 id="32134-standard-aggregation-method-average">3.2.1.3.4 Standard Aggregation Method average

      The standard aggregation method average can be applied -to numeric values to return the sum of the non-null values divided by -the count of the values, or null if there are no values to be -aggregated.

      +to numeric values to return the sum of the values divided by the count +of the values, or null if there are no values to be aggregated.

      The provider MUST choose a single type for the property across all instances of that type in the result that is capable of representing the aggregated values; either Edm.Double or @@ -2486,7 +2491,7 @@

      3.2.1.2 href="Transformationgroupby">groupby and aggregate transformations with the pattern \({\tt -groupby}(…,{\tt aggregate}(…{\tt\ as\ }A_1))/{\tt aggregate}(A_1{\tt\ +groupby}(…,{\tt aggregate}(…{\tt\ as\ }D_1))/{\tt aggregate}(D_1{\tt\ with\ }…)\).

      In the following \(p_1,…,p_n\) are data aggregation paths that are @@ -2500,7 +2505,7 @@

      3.2.1.2 property \(D\) in the single instance in the output set of the following transformation sequence: \[{\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ -as\ }A_1))/{\tt aggregate}(A_1{\tt\ with\ }g{\tt\ as\ }D).\]

    • +as\ }D_1))/{\tt aggregate}(D_1{\tt\ with\ }g{\tt\ as\ }D).\]
    • If \(α=p/c{\tt\ from\ }…\) is an aggregate expression that starts with a custom aggregate \(c\), optionally prefixed with a path 3.2.1.2 value of property \(c\) in the single instance in the output set of the following transformation sequence: \[{\tt groupby}((p_1,…,p_n),{\tt -aggregate}(α{\tt\ as\ }A_1))/{\tt aggregate}(p/c).\]
    • +aggregate}(α{\tt\ as\ }D_1))/{\tt aggregate}(p/c).\]

      Aggregate expressions constructed by these rules MUST be followed in the aggregate transformation by the keyword as @@ -2550,6 +2555,19 @@

      3.2.1.2
      GET /service/Sales?$apply=groupby((Time),aggregate(Forecast))
                         /aggregate(Forecast with average as DailyAverage)
      +
      +

      ⚠ Example 18: the maximal daily average for sales of a product

      +
      GET /service/Sales?$apply=aggregate(Amount with average from Time,Product/Name
      +                                           with max as MaxDailyAverage)
      +

      results in

      +
      {
      +  "@context": "$metadata#Sales(MaxDailyAverage)",
      +  "value": [
      +    { "MaxDailyAverage@type": "Decimal", "MaxDailyAverage": 8 }
      +  ]
      +}
      +

      3.2.2 Transformation concat

      @@ -2564,19 +2582,19 @@

      3.2.1.2 same alias, clients SHOULD ensure they have the same type and meaning in each intermediate output set.

      -

      ⚠ Example 18:

      +

      ⚠ Example 19:

      GET /service/Sales?$apply=concat(topcount(2,Amount),
                                        aggregate(Amount))

      results in

      -
      {
      -  "@context": "$metadata#Sales(Amount)",
      -  "value": [
      -    { "ID": 4, "Amount": 8 },
      -    { "ID": 3, "Amount": 4 },
      -    { "Amount": 24 }
      -  ]
      -}
      +
      {
      +  "@context": "$metadata#Sales(Amount)",
      +  "value": [
      +    { "ID": 4, "Amount": 8 },
      +    { "ID": 3, "Amount": 4 },
      +    { "Amount": 24 }
      +  ]
      +}

      Note that two Sales entities with the second highest amount 4 exist in the input set; the entity with ID 3 is included in the result, because the service chose to use the ID property @@ -2593,7 +2611,7 @@

      3.2.1.2 parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the input type without properties and -without entity-id.

      +without entity id.

      3.2.3.1 Simple Grouping

      @@ -2605,7 +2623,7 @@

      3.2.1.2 redundant property paths MAY be considered valid, but MUST NOT alter the meaning of the request. Navigation properties and stream properties specified in grouping properties are expanded by default (see example 67).

      +href="#groupbynav">example 68).

      The algorithmic description of this transformation makes use of the following definitions: Let \(u[q]\) denote the value of a structural or navigation property 3.2.1.2

      • Let \(v\) be an instance of the type of \(u\) without properties and -without entity-id.
      • +without entity id.
      • For each structural or navigation property \(q\) of \(u\): @@ -2721,51 +2739,51 @@

        3.2.1.2
      • Return \(u\).
      • -

        Example 19:

        +

        Example 20:

        GET /service/Sales?$apply=groupby((Customer/Country,Product/Name),
                                           aggregate(Amount with sum as Total))

        results in

        -
        {
        -  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        -  "value": [
        -    { "Customer": { "Country": "Netherlands" },
        -      "Product": { "Name": "Paper" },
        -      "Total@type": "Decimal", "Total":  3 },
        -    { "Customer": { "Country": "Netherlands" },
        -      "Product": { "Name": "Sugar" },
        -      "Total@type": "Decimal", "Total":  2 },
        -    { "Customer": { "Country": "USA" },
        -      "Product": { "Name": "Coffee" },
        -      "Total@type": "Decimal", "Total": 12 },
        -    { "Customer": { "Country": "USA" },
        -      "Product": { "Name": "Paper" },
        -      "Total@type": "Decimal", "Total":  5 },
        -    { "Customer": { "Country": "USA" },
        -      "Product": { "Name": "Sugar" },
        -      "Total@type": "Decimal", "Total":  2 }
        -  ]
        -}
        +
        {
        +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
        +  "value": [
        +    { "Customer": { "Country": "Netherlands" },
        +      "Product": { "Name": "Paper" },
        +      "Total@type": "Decimal", "Total":  3 },
        +    { "Customer": { "Country": "Netherlands" },
        +      "Product": { "Name": "Sugar" },
        +      "Total@type": "Decimal", "Total":  2 },
        +    { "Customer": { "Country": "USA" },
        +      "Product": { "Name": "Coffee" },
        +      "Total@type": "Decimal", "Total": 12 },
        +    { "Customer": { "Country": "USA" },
        +      "Product": { "Name": "Paper" },
        +      "Total@type": "Decimal", "Total":  5 },
        +    { "Customer": { "Country": "USA" },
        +      "Product": { "Name": "Sugar" },
        +      "Total@type": "Decimal", "Total":  2 }
        +  ]
        +}

        If the second parameter is omitted, steps 2 and 3 above produce one instance containing only the grouping properties per distinct value combination.

        -

        ⚠ Example 20:

        +

        ⚠ Example 21:

        GET /service/Sales?$apply=groupby((Product/Name,Amount))

        results in

        -
        {
        -  "@context": "$metadata#Sales(Product(Name),Amount)",
        -  "value": [
        -    { "Product": { "Name": "Coffee" }, "Amount": 4 },
        -    { "Product": { "Name": "Coffee" }, "Amount": 8 },
        -    { "Product": { "Name": "Paper"  }, "Amount": 1 },
        -    { "Product": { "Name": "Paper"  }, "Amount": 2 },
        -    { "Product": { "Name": "Paper"  }, "Amount": 4 },
        -    { "Product": { "Name": "Sugar"  }, "Amount": 2 }
        -  ]
        -}
        +
        {
        +  "@context": "$metadata#Sales(Product(Name),Amount)",
        +  "value": [
        +    { "Product": { "Name": "Coffee" }, "Amount": 4 },
        +    { "Product": { "Name": "Coffee" }, "Amount": 8 },
        +    { "Product": { "Name": "Paper"  }, "Amount": 1 },
        +    { "Product": { "Name": "Paper"  }, "Amount": 2 },
        +    { "Product": { "Name": "Paper"  }, "Amount": 4 },
        +    { "Product": { "Name": "Sugar"  }, "Amount": 2 }
        +  ]
        +}

        Note that the result has the same structure, but not the same content as

        GET /service/Sales?$expand=Product($select=Name)&$select=Amount
        @@ -2819,7 +2837,7 @@

        3.2.1.2 groupby}((P_1,p_1,P_2),T)\hfill\\ ).\hskip25pc\\ }\]

      -

      Example 22: answering the second question in Example 23: answering the second question in section 2.3

      GET /service/Sales?$apply=groupby((rollup(Customer/Country,Customer/Name),
      -                            rollup(Product/Category/Name,Product/Name)),
      -                           aggregate(Amount with sum as Total))
      + rollup(Product/Category/Name,Product/Name)), + aggregate(Amount with sum as Total))

      results in seven entities for the finest grouping level

      -
      {
      -  "@context": "$metadata#Sales(Customer(Country),
      -                               Product(Category(Name)),Total)",
      -  "value": [
      -    { "Customer": { "Country": "USA", "Name": "Joe" },
      -      "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
      -      "Total@type": "Decimal", "Total": 1 },
      -    ...
      +
      {
      +  "@context": "$metadata#Sales(Customer(Country),
      +                               Product(Category(Name)),Total)",
      +  "value": [
      +    { "Customer": { "Country": "USA", "Name": "Joe" },
      +      "Product":  { "Category": { "Name": "Non-Food" }, "Name": "Paper" },
      +      "Total@type": "Decimal", "Total": 1 },
      +    ...

      plus additional fifteen rollup entities for subtotals: five without customer name

      -
          { "Customer": { "Country": "USA" },
      -      "Product":  { "Category": { "Name": "Food" }, "Name": "Sugar" },
      -      "Total@type": "Decimal", "Total":  2 },
      -    ...
      -

      six without product name

      -
          { "Customer": { "Country": "USA", "Name": "Joe" },
      -      "Product":  { "Category": { "Name": "Food" } },
      -      "Total@type": "Decimal", "Total":  6 },
      -    ...
      -

      and four with neither customer nor product name

          { "Customer": { "Country": "USA" },
      -      "Product":  { "Category": { "Name": "Food" } },
      -      "Total@type": "Decimal", "Total": 14 },
      -    ...
      -  ]
      -}
      + "Product": { "Category": { "Name": "Food" }, "Name": "Sugar" }, + "Total@type": "Decimal", "Total": 2 }, + ...
      +

      six without product name

      +
          { "Customer": { "Country": "USA", "Name": "Joe" },
      +      "Product":  { "Category": { "Name": "Food" } },
      +      "Total@type": "Decimal", "Total":  6 },
      +    ...
      +

      and four with neither customer nor product name

      +
          { "Customer": { "Country": "USA" },
      +      "Product":  { "Category": { "Name": "Food" } },
      +      "Total@type": "Decimal", "Total": 14 },
      +    ...
      +  ]
      +}

      Note that the absence of one or more properties of the output structure declared by the surrounding OData context allows @@ -2900,7 +2918,7 @@

      3.2.1.2 construct the collection by executing the same resource path and transformations, possibly nested, on the same underlying data.

      -

      ⚠ Example 23: A stable total order is required for the input set of a +

      ⚠ Example 24: A stable total order is required for the input set of a skip transformation. The following request constructs that input set by executing the Sales resource path and the groupby @@ -2954,7 +2972,7 @@

      3.2.1.2

      For example, if the input set consists of non-transient entities and the datastore contains an index ordered by the second parameter and then -the entity-id, a service may implement this algorithm with \(A=B\) ordered like this index.

      The order of the output set can be influenced with a subsequent orderby @@ -2969,30 +2987,30 @@

      3.2.1.2 loop if the cardinality of the output set equals \(c\).

      -

      Example 24:

      -
      GET /service/Sales?$apply=bottomcount(2,Amount)
      -

      results in

      -
      {
      -  "@context": "$metadata#Sales",
      -  "value": [
      -    { "ID": 1, "Amount": 1 },
      -    { "ID": 7, "Amount": 1 }
      -  ]
      -}
      -
      -

      Example 25:

      -
      GET /service/Sales?$apply=topcount(2,Amount)
      +
      GET /service/Sales?$apply=bottomcount(2,Amount)

      results in

      {
         "@context": "$metadata#Sales",
         "value": [
      -    { "ID": 3, "Amount": 4 },
      -    { "ID": 4, "Amount": 8 }
      +    { "ID": 1, "Amount": 1 },
      +    { "ID": 7, "Amount": 1 }
         ]
       }
      +
      +
      +

      Example 26:

      +
      GET /service/Sales?$apply=topcount(2,Amount)
      +

      results in

      +
      {
      +  "@context": "$metadata#Sales",
      +  "value": [
      +    { "ID": 3, "Amount": 4 },
      +    { "ID": 4, "Amount": 8 }
      +  ]
      +}

      Note that two Sales entities with the second highest amount 4 exist in the input set; the entity with ID 3 is included in the result, because the service chose to use the @@ -3012,34 +3030,34 @@

      3.2.1.2 output set to their sum in the input set equals or exceeds \(p\)%.

      -

      Example 26:

      -
      GET /service/Sales?$apply=bottompercent(50,Amount)
      -

      results in

      -
      {
      -  "@context": "$metadata#Sales",
      -  "value": [
      -    { "ID": 1, "Amount": 1 },
      -    { "ID": 2, "Amount": 2 },
      -    { "ID": 5, "Amount": 4 },
      -    { "ID": 6, "Amount": 2 },
      -    { "ID": 7, "Amount": 1 },
      -    { "ID": 8, "Amount": 2 }
      -  ]
      -}
      -
      -

      Example 27:

      -
      GET /service/Sales?$apply=toppercent(50,Amount)
      +
      GET /service/Sales?$apply=bottompercent(50,Amount)

      results in

      {
         "@context": "$metadata#Sales",
         "value": [
      -    { "ID": 3, "Amount": 4 },
      -    { "ID": 4, "Amount": 8 }
      -  ]
      -}
      + { "ID": 1, "Amount": 1 }, + { "ID": 2, "Amount": 2 }, + { "ID": 5, "Amount": 4 }, + { "ID": 6, "Amount": 2 }, + { "ID": 7, "Amount": 1 }, + { "ID": 8, "Amount": 2 } + ] +}
      +

      +
      +

      Example 28:

      +
      GET /service/Sales?$apply=toppercent(50,Amount)
      +

      results in

      +
      {
      +  "@context": "$metadata#Sales",
      +  "value": [
      +    { "ID": 3, "Amount": 4 },
      +    { "ID": 4, "Amount": 8 }
      +  ]
      +}

      3.3.1.3 @@ -3053,47 +3071,24 @@

      3.2.1.2 or is less than or equal to a negative \(s\).

      -

      Example 28:

      -
      GET /service/Sales?$apply=bottomsum(7,Amount)
      -

      results in

      -
      {
      -  "@context": "$metadata#Sales",
      -  "value": [
      -    { "ID": 1, "Amount": 1 },
      -    { "ID": 2, "Amount": 2 },
      -    { "ID": 6, "Amount": 2 },
      -    { "ID": 7, "Amount": 1 },
      -    { "ID": 8, "Amount": 2 }
      -  ]
      -}
      -
      -

      Example 29:

      -
      GET /service/Sales?$apply=topsum(15,Amount)
      +
      GET /service/Sales?$apply=bottomsum(7,Amount)

      results in

      {
         "@context": "$metadata#Sales",
         "value": [
      -    { "ID": 3, "Amount": 4 },
      -    { "ID": 4, "Amount": 8 },
      -    { "ID": 5, "Amount": 4 }
      -  ]
      -}
      + { "ID": 1, "Amount": 1 }, + { "ID": 2, "Amount": 2 }, + { "ID": 6, "Amount": 2 }, + { "ID": 7, "Amount": 1 }, + { "ID": 8, "Amount": 2 } + ] +}

      -

      3.3.2 -Transformation filter

      -

      The filter transformation takes a Boolean expression -that could also be passed as a $filter system query option. -Its output set is the subset of the input set containing all instances -(possibly with repetitions) for which this expression, evaluated -relative to the instance, yields true. No order is defined on the output -set.

      Example 30:

      -
      GET /service/Sales?$apply=filter(Amount gt 3)
      +
      GET /service/Sales?$apply=topsum(15,Amount)

      results in

      {
      @@ -3106,12 +3101,35 @@ 

      3.2.1.2 }

      3.3.2 +Transformation filter

      +

      The filter transformation takes a Boolean expression +that could also be passed as a $filter system query option. +Its output set is the subset of the input set containing all instances +(possibly with repetitions) for which this expression, evaluated +relative to the instance, yields true. No order is defined on the output +set.

      +
      +

      Example 31:

      +
      GET /service/Sales?$apply=filter(Amount gt 3)
      +

      results in

      +
      {
      +  "@context": "$metadata#Sales",
      +  "value": [
      +    { "ID": 3, "Amount": 4 },
      +    { "ID": 4, "Amount": 8 },
      +    { "ID": 5, "Amount": 4 }
      +  ]
      +}
      +
      +

      3.3.3 Transformation identity

      The output set of the identity transformation is its input set in unchanged order.

      -

      Example 31: Add a grand total row to the Sales result +

      Example 32: Add a grand total row to the Sales result set

      GET /service/Sales?$apply=concat(identity,aggregate(Amount with sum as Total))
      @@ -3130,23 +3148,23 @@

      3.2.1.2 property paths, including dynamic properties, with both suffixes asc and desc.

      -

      Example 32:

      +

      Example 33:

      GET /service/Sales?$apply=groupby((Product/Name),
                                  aggregate(Amount with sum as Total))
                          /orderby(Total desc)

      results in

      -
      {
      -  "@context": "$metadata#Sales(Product(Name),Total)",
      -  "value": [
      -    { "Product": { "Name": "Coffee" },
      -      "Total@type": "Decimal", "Total": 12 },
      -    { "Product": { "Name": "Paper" },
      -      "Total@type": "Decimal", "Total":  8 },
      -    { "Product": { "Name": "Sugar" },
      -      "Total@type": "Decimal", "Total":  4 }
      -  ]
      -}
      +
      {
      +  "@context": "$metadata#Sales(Product(Name),Total)",
      +  "value": [
      +    { "Product": { "Name": "Coffee" },
      +      "Total@type": "Decimal", "Total": 12 },
      +    { "Product": { "Name": "Paper" },
      +      "Total@type": "Decimal", "Total":  8 },
      +    { "Product": { "Name": "Sugar" },
      +      "Total@type": "Decimal", "Total":  4 }
      +  ]
      +}

  • -

    Example 37: all links between products and sales instances

    +

    Example 38: all links between products and sales instances

    GET /service/Products?$apply=join(Sales as Sale)&$select=ID&$expand=Sale

    results in

    -
    {
    -  "@context": "$metadata#Products(ID,Sale())",
    -  "value": [
    -    { "ID": "P1",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 2, "Amount": 2 } },
    -    { "ID": "P1",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 6, "Amount": 2 } },
    -    { "ID": "P2",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 3, "Amount": 4 } },
    -    { "ID": "P2",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 4, "Amount": 8 } },
    -    { "ID": "P3",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 1, "Amount": 1 } },
    -    { "ID": "P3",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 5, "Amount": 4 } },
    -    { "ID": "P3",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 7, "Amount": 1 } },
    -    { "ID": "P3",
    -      "Sale": {
    -        "@context": "#Sales/$entity",
    -        "ID": 8, "Amount": 2 } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(ID,Sale())",
    +  "value": [
    +    { "ID": "P1",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 2, "Amount": 2 } },
    +    { "ID": "P1",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 6, "Amount": 2 } },
    +    { "ID": "P2",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 3, "Amount": 4 } },
    +    { "ID": "P2",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 4, "Amount": 8 } },
    +    { "ID": "P3",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 1, "Amount": 1 } },
    +    { "ID": "P3",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 5, "Amount": 4 } },
    +    { "ID": "P3",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 7, "Amount": 1 } },
    +    { "ID": "P3",
    +      "Sale": {
    +        "@context": "#Sales/$entity",
    +        "ID": 8, "Amount": 2 } }
    +  ]
    +}

    In this example, $expand=Sale is used to include the target entities in the result. There are no subsequent transformations like groupby that would cause it to be expanded by default. @@ -3397,7 +3415,7 @@

    3.2.1.2 aliases MUST NOT collide with names of properties in the input set or with other aliases introduced in the same nest transformation.

    The output set consists of a single instance of the input type without entity-id +href="#TypeStructureandContextURL">input type without entity id having one dynamic property per transformation sequence. The name of the dynamic property is the alias for this transformation sequence. The value of the dynamic property is the collection resulting from the @@ -3405,19 +3423,19 @@

    3.2.1.2 carries as control information the context URL of the transformed input set.

    -

    Example 38:

    +

    Example 39:

    GET /service/Sales?$apply=nest(groupby((Customer/ID)) as Customers))

    results in

    -
    {
    -  "@context":"$metadata#Sales(Customers())",
    -  "value": [
    -    { "Customers@context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] }
    -  ]
    -}
    +
    {
    +  "@context":"$metadata#Sales(Customers())",
    +  "value": [
    +    { "Customers@context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] }
    +  ]
    +}

    3.4.4 @@ -3484,29 +3502,29 @@

    3.2.1.2 control information the context URL of \(v\).

    -

    Example 39:

    +

    Example 40:

    GET /service/Customers?$apply=addnested(Sales,
                                             filter(Amount gt 3) as FilteredSales)

    results in

    -
    {
    -  "@context": "$metadata#Customers(FilteredSales())",
    -  "value": [
    -    { "ID": "C1", "Name": "Joe", "Country": "USA",
    -      "FilteredSales@context": "#Sales",
    -      "FilteredSales": [{ "ID": "3", "Amount": 4 }]},
    -    { "ID": "C2", "Name": "Sue", "Country": "USA",
    -      "FilteredSales@context": "#Sales",
    -      "FilteredSales": [{ "ID": "4", "Amount": 8 },
    -                        { "ID": "5", "Amount": 4 }]},
    -    { "ID": "C3", "Name": "Sue", "Country": "Netherlands",
    -      "FilteredSales@context": "#Sales",
    -      "FilteredSales": []},
    -    { "ID": "C4", "Name": "Luc", "Country": "France",
    -      "FilteredSales@context": "#Sales",
    -      "FilteredSales": []}
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Customers(FilteredSales())",
    +  "value": [
    +    { "ID": "C1", "Name": "Joe", "Country": "USA",
    +      "FilteredSales@context": "#Sales",
    +      "FilteredSales": [{ "ID": "3", "Amount": 4 }]},
    +    { "ID": "C2", "Name": "Sue", "Country": "USA",
    +      "FilteredSales@context": "#Sales",
    +      "FilteredSales": [{ "ID": "4", "Amount": 8 },
    +                        { "ID": "5", "Amount": 4 }]},
    +    { "ID": "C3", "Name": "Sue", "Country": "Netherlands",
    +      "FilteredSales@context": "#Sales",
    +      "FilteredSales": []},
    +    { "ID": "C4", "Name": "Luc", "Country": "France",
    +      "FilteredSales@context": "#Sales",
    +      "FilteredSales": []}
    +  ]
    +}

    If Sales was a collection-valued complex property of type SalesModel.SalesComplexType, the context would be "FilteredSales@context": "#Collection(SalesModel.SalesComplexType)".

    @@ -3544,54 +3562,51 @@

    3.2.1.2 Function aggregate

    The aggregate function allows to use aggregated values in expressions. It takes a single parameter -accepting an aggregate-function expression and returns the aggregated -value of type Edm.PrimitiveType as the result from applying -the aggregate-function expression on its input collection.

    -

    An aggregate-function expression offers the same capabilities as an -aggregate expression defined for the aggregate -transformation except that it never includes an alias for introducing a -dynamic property containing the aggregated value. More precisely, if -\(α\) is an aggregate expression, the -function \(p/{\tt aggregate}(α)\) or -\({\tt\$these}/{\tt aggregate}(α)\) -evaluates to the value of the property aggregate expression and +returns the aggregated value of type Edm.PrimitiveType as +the result from applying the aggregate expression on its input +collection.

    +

    More precisely, if \(α\) is an +aggregate expression, the function \(p/{\tt +aggregate}(α)\) or \({\tt\$these}/{\tt +aggregate}(α)\) evaluates to the value of the property \(D\) in the single instance of the output set that is produced when the transformation \({\tt aggregate}(α{\tt\ as\ }D)\) is applied with the input collection as input set.

    -

    Example 40: Sales making up at least a third of the total sales +

    Example 41: Sales making up at least a third of the total sales amount.

    GET /service/Sales?$filter=Amount mul 3 ge $these/aggregate(Amount with sum)

    results in

    -
    {
    -  "@context": "$metadata#Sales",
    -  "value": [
    -    { "ID": "4", "Amount": 8 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales",
    +  "value": [
    +    { "ID": "4", "Amount": 8 }
    +  ]
    +}
    -

    Example 41: Products with more than 1.00 sales tax. The aggregate +

    Example 42: Products with more than 1.00 sales tax. The aggregate expression of type 2 combines paths with and without $it prefix (compare this with example 8).

    GET /service/Products?$filter=Sales/aggregate(Amount mul $it/TaxRate with sum)
                                   gt 1
    -

    ⚠ Example 42: products with a single sale of at least twice the +

    ⚠ Example 43: products with a single sale of at least twice the average sales amount

    GET /service/Products?$filter=Sales/any(s:s/Amount ge
                                   Sales/aggregate(Amount with average) mul 2)

    Both examples result in

    -
    {
    -  "@context": "$metadata#Products",
    -  "value": [
    -    { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products",
    +  "value": [
    +    { "ID": "P3", "Name": "Paper", "Color": "White", "TaxRate": 0.14 }
    +  ]
    +}

    3.5.2 @@ -3599,7 +3614,7 @@

    3.2.1.2

    The expression $count evaluates to the cardinality of the input collection.

    -

    Example 43: The input +

    Example 44: The input collection for $count consists of all sales entities, the top third of sales entities by amount form the result.

    GET /service/Sales?$apply=topcount($these/$count div 3,Amount)
    @@ -3607,14 +3622,14 @@

    3.2.1.2 from toppercent(33.3,Amount), which returns only the sales entity with ID 4, because that already makes up a third of the total amount.)

    -
    {
    -  "@context": "$metadata#Sales",
    -  "value": [
    -    { "ID": 3, "Amount": 4 },
    -    { "ID": 4, "Amount": 8 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales",
    +  "value": [
    +    { "ID": 3, "Amount": 4 },
    +    { "ID": 4, "Amount": 8 }
    +  ]
    +}

    A definition that is equivalent to a $count expression after a collection-valued path was made in 3.2.1.2 value; it can represent a grouping of null values, or an aggregation that results in a null value.

    3.7 @@ -3663,24 +3678,24 @@

    3.2.1.2 and other expand or select options on the same (navigation) property are evaluated on the result of $apply.

    -

    Example 45: products with aggregated sales

    +

    Example 46: products with aggregated sales

    GET /service/Products
       ?$expand=Sales($apply=aggregate(Amount with sum as Total))

    results in

    -
    {
    -  "@context":"$metadata#Products(Sales(Total))",
    -  "value": [
    -    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
    -      "Sales": [ { "Total@type": "Decimal", "Total":   12 } ] },
    -    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
    -      "Sales": [ { "Total@type": "Decimal", "Total":    8 } ] },
    -    { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
    -      "Sales": [ { "Total": null } ] },
    -    { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
    -      "Sales": [ { "Total@type": "Decimal", "Total":    4 } ] }
    -  ]
    -}
    +
    {
    +  "@context":"$metadata#Products(Sales(Total))",
    +  "value": [
    +    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
    +      "Sales": [ { "Total@type": "Decimal", "Total":   12 } ] },
    +    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
    +      "Sales": [ { "Total@type": "Decimal", "Total":    8 } ] },
    +    { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
    +      "Sales": [ { "Total": null } ] },
    +    { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
    +      "Sales": [ { "Total@type": "Decimal", "Total":    4 } ] }
    +  ]
    +}

    3.8 @@ -3711,7 +3726,7 @@

    3.2.1.2 mechanism for advanced consumers to use more flexible join conditions.

    -

    Example 46: if Sale had a string property +

    Example 47: if Sale had a string property ProductID instead of the navigation property Product, a "join" between Sales and Products could be accessed via the $crossjoin @@ -3720,18 +3735,18 @@

    3.2.1.2 ?$expand=Products($select=Name),Sales($select=Amount) &$filter=Products/ID eq Sales/ProductID

    results in

    -
    {
    -  "@context": "$metadata#Collection(Edm.ComplexType)",
    -  "value": [
    -    { "Products": { "Name": "Paper" }, "Sales": { "Amount": 1 } },
    -    { "Products": { "Name": "Sugar" }, "Sales": { "Amount": 2 } },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Collection(Edm.ComplexType)",
    +  "value": [
    +    { "Products": { "Name": "Paper" }, "Sales": { "Amount": 1 } },
    +    { "Products": { "Name": "Sugar" }, "Sales": { "Amount": 2 } },
    +    ...
    +  ]
    +}

    -

    Example 47: using the $crossjoin resource for aggregate +

    Example 48: using the $crossjoin resource for aggregate queries

    GET /service/$crossjoin(Products,Sales)
         ?$apply=filter(Products/ID eq Sales/ProductID)
    @@ -3739,21 +3754,21 @@ 

    3.2.1.2 addnested(Sales,aggregate(Amount with sum as Total) as AggregatedSales))

    results in

    -
    {
    -  "@context": "$metadata#Collection(Edm.ComplexType)",
    -  "value": [
    -    { "Products": { "Name": "Coffee" },
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": { "Total@type": "Decimal", "Total": 12 } },
    -    { "Products": { "Name": "Paper"  },
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": { "Total@type": "Decimal", "Total":  8 } },
    -    { "Products": { "Name": "Sugar"  },
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": { "Total@type": "Decimal", "Total":  4 } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Collection(Edm.ComplexType)",
    +  "value": [
    +    { "Products": { "Name": "Coffee" },
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": { "Total@type": "Decimal", "Total": 12 } },
    +    { "Products": { "Name": "Paper"  },
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": { "Total@type": "Decimal", "Total":  8 } },
    +    { "Products": { "Name": "Sugar"  },
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": { "Total@type": "Decimal", "Total":  4 } }
    +  ]
    +}

    The entity container may be annotated in the same way as entity sets to express which aggregate queries are supported, see 3.2.1.2 ApplySupportedDefault have their default value.

    -

    Example 48: an entity container with default support for everything +

    Example 49: an entity container with default support for everything defined in this specification

    -
    <EntityContainer Name="SalesData">
    -  <Annotation Term="Aggregation.ApplySupportedDefaults" />
    -  ...
    -</EntityContainer>
    +
    <EntityContainer Name="SalesData">
    +  <Annotation Term="Aggregation.ApplySupportedDefaults" />
    +  ...
    +</EntityContainer>

    5.2 @@ -3864,19 +3879,19 @@

    3.2.1.2 value of the Qualifier attribute MUST NOT collide with the names of any entity container children.

    -

    Example 49: Sales forecasts are modeled as a custom aggregate of the +

    Example 50: Sales forecasts are modeled as a custom aggregate of the Sale entity type because it belongs there. For the budget, there is no appropriate structured type, so it is modeled as a custom aggregate of the SalesData entity container.

    -
    <Annotations Target="SalesModel.SalesData/Sales">
    -  <Annotation Term="Aggregation.CustomAggregate" Qualifier="Forecast"
    -              String="Edm.Decimal" />
    -</Annotations>
    -<Annotations Target="SalesModel.SalesData">
    -  <Annotation Term="Aggregation.CustomAggregate" Qualifier="Budget"
    -              String="Edm.Decimal" />
    -</Annotations>
    +
    <Annotations Target="SalesModel.SalesData/Sales">
    +  <Annotation Term="Aggregation.CustomAggregate" Qualifier="Forecast"
    +              String="Edm.Decimal" />
    +</Annotations>
    +<Annotations Target="SalesModel.SalesData">
    +  <Annotation Term="Aggregation.CustomAggregate" Qualifier="Budget"
    +              String="Edm.Decimal" />
    +</Annotations>

    These custom aggregates can be used in the aggregate transformation:

    GET /service/Sales?$apply=groupby((Time/Month),aggregate(Forecast))
    @@ -3902,85 +3917,85 @@

    3.2.1.2 id="54-annotation-example">5.4 Annotation Example

    -

    Example 50: This simplified Sales entity set has a +

    Example 51: This simplified Sales entity set has a single aggregatable property Amount whose context is defined by the Code property of the related Currency, and a custom aggregate Forecast with the same context. The Code property of Currencies is groupable. All other properties are neither groupable nor aggregatable.

    -
    <EntityType Name="Currency">
    -  <Key>
    -    <PropertyRef Name="Code" />
    -  </Key>
    -  <Property Name="Code" Type="Edm.String" />
    -  <Property Name="Name" Type="Edm.String">
    -    <Annotation Term="Core.IsLanguageDependent" />
    -  </Property>
    -</EntityType>
    -
    -<EntityType Name="Sale">
    -  <Key>
    -    <PropertyRef Name="ID" />
    -  </Key>
    -  <Property Name="ID" Type="Edm.String" Nullable="false" />
    -  <Property Name="Amount" Type="Edm.Decimal" Scale="variable">
    -    <Annotation Term="Aggregation.ContextDefiningProperties">
    -      <Collection>
    -        <PropertyPath>Currency/Code</PropertyPath>
    -      </Collection>
    -    </Annotation>
    -  </Property>
    -  <NavigationProperty Name="Currency" Type="SalesModel.Currency"
    -                      Nullable="false" />
    -</EntityType>
    -
    -<EntityContainer Name="SalesData">
    -  <EntitySet Name="Sales" EntityType="SalesModel.Sale">
    -    <Annotation Term="Aggregation.ApplySupported">
    -      <Record>
    -        <PropertyValue Property="AggregatableProperties">
    -          <Collection>
    -            <Record>
    -              <PropertyValue Property="Property" PropertyPath="Amount" />
    -            </Record>
    -          </Collection>
    -        </PropertyValue>
    -        <PropertyValue Property="GroupableProperties">
    -          <Collection>
    -            <Record>
    -              <PropertyValue Property="Property" PropertyPath="Currency" />
    -            </Record>
    -          </Collection>
    -        </PropertyValue>
    -      </Record>
    -    </Annotation>
    -
    -    <Annotation Term="Aggregation.CustomAggregate" Qualifier="Forecast"
    -                String="Edm.Decimal">
    -      <Annotation Term="Aggregation.ContextDefiningProperties">
    -        <Collection>
    -          <PropertyPath>Currency/Code</PropertyPath>
    -        </Collection>
    -      </Annotation>
    -    </Annotation>
    -  </EntitySet>
    -
    -  <EntitySet Name="Currencies" EntityType="SalesModel.Currency">
    -    <Annotation Term="Aggregation.ApplySupported">
    -      <Record>
    -        <PropertyValue Property="GroupableProperties">
    -          <Collection>
    -            <Record>
    -              <PropertyValue Property="Property" PropertyPath="Code" />
    -            </Record>
    -          </Collection>
    -        </PropertyValue>
    -      </Record>
    -    </Annotation>
    -  </EntitySet>
    -</EntityContainer>
    +
    <EntityType Name="Currency">
    +  <Key>
    +    <PropertyRef Name="Code" />
    +  </Key>
    +  <Property Name="Code" Type="Edm.String" />
    +  <Property Name="Name" Type="Edm.String">
    +    <Annotation Term="Core.IsLanguageDependent" />
    +  </Property>
    +</EntityType>
    +
    +<EntityType Name="Sale">
    +  <Key>
    +    <PropertyRef Name="ID" />
    +  </Key>
    +  <Property Name="ID" Type="Edm.String" Nullable="false" />
    +  <Property Name="Amount" Type="Edm.Decimal" Scale="variable">
    +    <Annotation Term="Aggregation.ContextDefiningProperties">
    +      <Collection>
    +        <PropertyPath>Currency/Code</PropertyPath>
    +      </Collection>
    +    </Annotation>
    +  </Property>
    +  <NavigationProperty Name="Currency" Type="SalesModel.Currency"
    +                      Nullable="false" />
    +</EntityType>
    +
    +<EntityContainer Name="SalesData">
    +  <EntitySet Name="Sales" EntityType="SalesModel.Sale">
    +    <Annotation Term="Aggregation.ApplySupported">
    +      <Record>
    +        <PropertyValue Property="AggregatableProperties">
    +          <Collection>
    +            <Record>
    +              <PropertyValue Property="Property" PropertyPath="Amount" />
    +            </Record>
    +          </Collection>
    +        </PropertyValue>
    +        <PropertyValue Property="GroupableProperties">
    +          <Collection>
    +            <Record>
    +              <PropertyValue Property="Property" PropertyPath="Currency" />
    +            </Record>
    +          </Collection>
    +        </PropertyValue>
    +      </Record>
    +    </Annotation>
    +
    +    <Annotation Term="Aggregation.CustomAggregate" Qualifier="Forecast"
    +                String="Edm.Decimal">
    +      <Annotation Term="Aggregation.ContextDefiningProperties">
    +        <Collection>
    +          <PropertyPath>Currency/Code</PropertyPath>
    +        </Collection>
    +      </Annotation>
    +    </Annotation>
    +  </EntitySet>
    +
    +  <EntitySet Name="Currencies" EntityType="SalesModel.Currency">
    +    <Annotation Term="Aggregation.ApplySupported">
    +      <Record>
    +        <PropertyValue Property="GroupableProperties">
    +          <Collection>
    +            <Record>
    +              <PropertyValue Property="Property" PropertyPath="Code" />
    +            </Record>
    +          </Collection>
    +        </PropertyValue>
    +      </Record>
    +    </Annotation>
    +  </EntitySet>
    +</EntityContainer>

    5.5 Hierarchies

    @@ -4094,80 +4109,80 @@

    5.5 Hierarchy Examples

    The hierarchy terms can be applied to the Example Data Model.

    -

    Example 51: leveled hierarchies for products and time, and a +

    Example 52: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations

    -
    <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"
    -           Version="4.0">
    - <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/vocabularies/Org.OData.Aggregation.V1.xml">
    -  <edmx:Include Alias="Aggregation"
    -                Namespace="Org.OData.Aggregation.V1" />
    - </edmx:Reference>
    - <edmx:DataServices>
    -  <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm"
    -          Alias="SalesModel" Namespace="org.example.odata.salesservice">
    -   <Annotations Target="SalesModel.Product">
    -    <Annotation Term="Aggregation.LeveledHierarchy"
    -                Qualifier="ProductHierarchy">
    -        <Collection>
    -          <PropertyPath>Category/Name</PropertyPath>
    -          <PropertyPath>Name</PropertyPath>
    -        </Collection>
    -    </Annotation>
    -   </Annotations>
    -
    -   <Annotations Target="SalesModel.Time">
    -    <Annotation Term="Aggregation.LeveledHierarchy"
    -                Qualifier="TimeHierarchy">
    -       <Collection>
    -         <PropertyPath>Year</PropertyPath>
    -         <PropertyPath>Quarter</PropertyPath>
    -         <PropertyPath>Month</PropertyPath>
    -       </Collection>
    -    </Annotation>
    -   </Annotations>
    -
    -   <Annotations Target="SalesModel.SalesOrganization">
    -    <Annotation Term="Aggregation.RecursiveHierarchy"
    -                Qualifier="SalesOrgHierarchy">
    -     <Record>
    -      <PropertyValue Property="NodeProperty"
    -                     PropertyPath="ID" />
    -      <PropertyValue Property="ParentNavigationProperty"
    -                     PropertyPath="Superordinate" />
    -     </Record>
    -    </Annotation>
    -   </Annotations>
    -  </Schema>
    - </edmx:DataServices>
    -</edmx:Edmx>
    +
    <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"
    +           Version="4.0">
    + <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/vocabularies/Org.OData.Aggregation.V1.xml">
    +  <edmx:Include Alias="Aggregation"
    +                Namespace="Org.OData.Aggregation.V1" />
    + </edmx:Reference>
    + <edmx:DataServices>
    +  <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm"
    +          Alias="SalesModel" Namespace="org.example.odata.salesservice">
    +   <Annotations Target="SalesModel.Product">
    +    <Annotation Term="Aggregation.LeveledHierarchy"
    +                Qualifier="ProductHierarchy">
    +        <Collection>
    +          <PropertyPath>Category/Name</PropertyPath>
    +          <PropertyPath>Name</PropertyPath>
    +        </Collection>
    +    </Annotation>
    +   </Annotations>
    +
    +   <Annotations Target="SalesModel.Time">
    +    <Annotation Term="Aggregation.LeveledHierarchy"
    +                Qualifier="TimeHierarchy">
    +       <Collection>
    +         <PropertyPath>Year</PropertyPath>
    +         <PropertyPath>Quarter</PropertyPath>
    +         <PropertyPath>Month</PropertyPath>
    +       </Collection>
    +    </Annotation>
    +   </Annotations>
    +
    +   <Annotations Target="SalesModel.SalesOrganization">
    +    <Annotation Term="Aggregation.RecursiveHierarchy"
    +                Qualifier="SalesOrgHierarchy">
    +     <Record>
    +      <PropertyValue Property="NodeProperty"
    +                     PropertyPath="ID" />
    +      <PropertyValue Property="ParentNavigationProperty"
    +                     PropertyPath="Superordinate" />
    +     </Record>
    +    </Annotation>
    +   </Annotations>
    +  </Schema>
    + </edmx:DataServices>
    +</edmx:Edmx>

    The recursive hierarchy SalesOrgHierarchy can be used in functions with the $filter system query option.

    -

    Example 52: requesting all organizations below EMEA

    +

    Example 53: requesting all organizations below EMEA

    GET /service/SalesOrganizations?$filter=Aggregation.isdescendant(
       HierarchyNodes=$root/SalesOrganizations,
       HierarchyQualifier='SalesOrgHierarchy',
       Node=ID,
       Ancestor='EMEA')

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations",
    -  "value": [
    -    { "ID": "EMEA Central",      "Name": "EMEA Central" },
    -    { "ID": "Sales Netherlands", "Name": "Sales Netherlands" },
    -    { "ID": "Sales Germany",     "Name": "Sales Germany" },
    -    { "ID": "EMEA South",        "Name": "EMEA South" },
    -    ...
    -    { "ID": "EMEA North",        "Name": "EMEA North" },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations",
    +  "value": [
    +    { "ID": "EMEA Central",      "Name": "EMEA Central" },
    +    { "ID": "Sales Netherlands", "Name": "Sales Netherlands" },
    +    { "ID": "Sales Germany",     "Name": "Sales Germany" },
    +    { "ID": "EMEA South",        "Name": "EMEA South" },
    +    ...
    +    { "ID": "EMEA North",        "Name": "EMEA North" },
    +    ...
    +  ]
    +}
    -

    Example 53: requesting just those organizations directly below +

    Example 54: requesting just those organizations directly below EMEA

    GET /service/SalesOrganizations?$filter=Aggregation.isdescendant(
       HierarchyNodes=$root/SalesOrganizations,
    @@ -4176,36 +4191,36 @@ 

    5.5 Ancestor='EMEA', MaxDistance=1)

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations",
    -  "value": [
    -    { "ID": "EMEA Central", "Name": "EMEA Central" },
    -    { "ID": "EMEA South",   "Name": "EMEA South" },
    -    { "ID": "EMEA North",   "Name": "EMEA North" },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations",
    +  "value": [
    +    { "ID": "EMEA Central", "Name": "EMEA Central" },
    +    { "ID": "EMEA South",   "Name": "EMEA South" },
    +    { "ID": "EMEA North",   "Name": "EMEA North" },
    +    ...
    +  ]
    +}
    -

    Example 54: just the lowest-level organizations

    +

    Example 55: just the lowest-level organizations

    GET /service/SalesOrganizations?$filter=Aggregation.isleaf(
       HierarchyNodes=$root/SalesOrganizations,
       HierarchyQualifier='SalesOrgHierarchy',
       Node=ID)

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations",
    -  "value": [
    -    { "ID": "Sales Office London",   "Name": "Sales Office London" },
    -    { "ID": "Sales Office New York", "Name": "Sales Office New York" },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations",
    +  "value": [
    +    { "ID": "Sales Office London",   "Name": "Sales Office London" },
    +    { "ID": "Sales Office New York", "Name": "Sales Office New York" },
    +    ...
    +  ]
    +}
    -

    Example 55: the lowest-level organizations including their +

    Example 56: the lowest-level organizations including their superordinate's ID

    GET /service/SalesOrganizations?$filter=Aggregation.isleaf(
       HierarchyNodes=$root/SalesOrganizations,
    @@ -4213,20 +4228,20 @@ 

    5.5 Node=ID) &$expand=Superordinate($select=ID)

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
    -  "value": [
    -    { "ID": "Sales Office London",   "Name": "Sales Office London",
    -      "Superordinate": { "ID": "EMEA United Kingdom" } },
    -    { "ID": "Sales Office New York", "Name": "Sales Office New York",
    -      "Superordinate": { "ID": "US East" } },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations(*,Superordinate(ID))",
    +  "value": [
    +    { "ID": "Sales Office London",   "Name": "Sales Office London",
    +      "Superordinate": { "ID": "EMEA United Kingdom" } },
    +    { "ID": "Sales Office New York", "Name": "Sales Office New York",
    +      "Superordinate": { "ID": "US East" } },
    +    ...
    +  ]
    +}
    -

    Example 56: retrieving the sales IDs involving sales +

    Example 57: retrieving the sales IDs involving sales organizations from EMEA can be requested by

    
     GET /service/Sales?$select=ID&$filter=Aggregation.isdescendant(
    @@ -4235,15 +4250,15 @@ 

    5.5 Node=SalesOrganization/ID, Ancestor='EMEA')

    results in

    -
    {
    -  "@context": "$metadata#Sales(ID)",
    -  "value": [
    -    { "ID": 6 },
    -    { "ID": 7 },
    -    { "ID": 8 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(ID)",
    +  "value": [
    +    { "ID": 6 },
    +    { "ID": 7 },
    +    { "ID": 8 }
    +  ]
    +}

    Further examples for recursive hierarchies using transformations operating on the hierarchy structure are provided in 5.5 one of its ancestor organizations. For descendants, analogously.

    -

    Example 57: Request based on the SalesOrgHierarchy +

    Example 58: Request based on the SalesOrgHierarchy defined in Hierarchy Examples, with Superordinate/$ref expanded to illustrate the hierarchy relation

    @@ -4468,21 +4483,21 @@

    5.5 filter(contains(Name,'East') or contains(Name,'Central'))) &$expand=Superordinate/$ref

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations",
    -  "value": [
    -    { "ID": "EMEA",  "Name": "EMEA",
    -      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    -    { "ID": "US",    "Name": "US",
    -      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    -    { "ID": "Sales", "Name": "Sales",
    -      "Superordinate": null }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations",
    +  "value": [
    +    { "ID": "EMEA",  "Name": "EMEA",
    +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    +    { "ID": "US",    "Name": "US",
    +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    +    { "ID": "Sales", "Name": "Sales",
    +      "Superordinate": null }
    +  ]
    +}

    -

    Example 58: Request based on the SalesOrgHierarchy +

    Example 59: Request based on the SalesOrgHierarchy defined in Hierarchy Examples, with Superordinate/$ref expanded to illustrate the hierarchy relation

    @@ -4491,21 +4506,21 @@

    5.5 filter(Name eq 'US'),keep start) &$expand=Superordinate/$ref

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations",
    -  "value": [
    -    { "ID": "US West", "Name": "US West",
    -      "Superordinate": { "@id": "SalesOrganizations('US')" } },
    -    { "ID": "US",      "Name": "US",
    -      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    -    { "ID": "US East", "Name": "US East",
    -      "Superordinate": { "@id": "SalesOrganizations('US')" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations",
    +  "value": [
    +    { "ID": "US West", "Name": "US West",
    +      "Superordinate": { "@id": "SalesOrganizations('US')" } },
    +    { "ID": "US",      "Name": "US",
    +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    +    { "ID": "US East", "Name": "US East",
    +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
    +  ]
    +}

    -

    ⚠ Example 59: Input set and recursive hierarchy from two different +

    ⚠ Example 60: Input set and recursive hierarchy from two different entity sets

    GET /service/Sales?$apply=
         ancestors($root/SalesOrganizations,
    @@ -4515,22 +4530,22 @@ 

    5.5 or contains(SalesOrganization/Name,'Central')), keep start)

    results in

    -
    {
    -  "@context": "$metadata#Sales",
    -  "value": [
    -    { "ID": "4", "Amount": 8,
    -      "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
    -    { "ID": "5", "Amount": 4,
    -      "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
    -    { "ID": "6", "Amount": 2,
    -      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central" } },
    -    { "ID": "7", "Amount": 1,
    -      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central" } },
    -    { "ID": "8", "Amount": 2,
    -      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales",
    +  "value": [
    +    { "ID": "4", "Amount": 8,
    +      "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
    +    { "ID": "5", "Amount": 4,
    +      "SalesOrganization": { "ID": "US East",      "Name": "US East" } },
    +    { "ID": "6", "Amount": 2,
    +      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central" } },
    +    { "ID": "7", "Amount": 1,
    +      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central" } },
    +    { "ID": "8", "Amount": 2,
    +      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central" } }
    +  ]
    +}

    6.2.2 @@ -4620,7 +4635,7 @@

    5.5 In this case \(\Pi_G(σ(x))\) injects all properties of \(x\) into the instances of the output set. (See Example -61.) +62.)
  • Case where the recursive hierarchy is defined on the related entity type addressed by a navigation property path
    This case applies if \(p'\) is a @@ -4634,7 +4649,7 @@

    5.5 the whole related entity \(x\) into the instances of the output set. The navigation property path \(p'\) is expanded by default. (See Example 62.)

  • +href="#rollupnode">Example 63.)
  • Case where the recursive hierarchy is related to the input set only through equality of node identifiers, not through navigation
    @@ -4647,7 +4662,7 @@

    5.5

    Here paths are considered equal if their non-type-cast segments refer to the same model elements when evaluated relative to the input set (see -Example 63).

    +Example 64).

    The function \(a(u,v,x)\) takes an instance, a path and another instance as arguments and is defined recursively as follows:

    @@ -4656,7 +4671,7 @@

    5.5 \(ε\), set \(u\) to a new instance of the input type without properties and -without entity-id.

  • +without entity id.
  • If \(v\) contains only one segment other than a type cast, let \(v_1=v\), and let \(x'=x\), then go to step @@ -4671,7 +4686,7 @@

    5.5 where \({}/v_2\) may be absent.

  • Let \(u'\) be an instance of the type of \(v_1/v_2\) without -properties and without entity-id.
  • +properties and without entity id.
  • Let \(x'=a(u',v_3,x)\).
  • If \(v_1\) is single-valued, let @@ -4681,7 +4696,7 @@

    5.5 consisting of one item \(x'\).

  • Return \(u\).
  • -

    (See Example 107.)

    +

    (See Example 108.)

    Let \(r_1,…,r_n\) be a sequence of the root nodes of the recursive hierarchy \((H',Q)\) 5.5 lambdaVariableExprs and \({}/r\) may be absent.

    -

    Example 60: Based on the SalesOrgHierarchy defined in Example 61: Based on the SalesOrgHierarchy defined in Hierarchy Examples

    GET /service/SalesOrganizations?$apply=
         descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,
    @@ -4735,16 +4750,16 @@ 

    5.5 /traverse($root/SalesOrganizations,SalesOrgHierarchy,ID,preorder) &$expand=Superordinate/$ref

    results in

    -
    {
    -  "@context": "$metadata#SalesOrganizations",
    -  "value": [
    -    { "ID": "US",      "Name": "US",
    -      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    -    { "ID": "US East", "Name": "US East",
    -      "Superordinate": { "@id": "SalesOrganizations('US')" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#SalesOrganizations",
    +  "value": [
    +    { "ID": "US",      "Name": "US",
    +      "Superordinate": { "@id": "SalesOrganizations('Sales')" } },
    +    { "ID": "US East", "Name": "US East",
    +      "Superordinate": { "@id": "SalesOrganizations('US')" } }
    +  ]
    +}

    The algorithm given so far is valid for a single-valued RecursiveHierarchy/ParentNavigationProperty. The remainder @@ -4848,7 +4863,7 @@

    5.5

    The rolluprecursive algorithm:

    A property \(χ_N\) appears in the algorithm, but is not present in the output set. It is explained later -(see Example 62). Example 63). \(Z_N\) is a transformation whose output set is its input set with property \(χ_N\) removed.

    @@ -4909,7 +4924,7 @@

    5.5 class="math inline">\(y_1,…,y_k\) denote lambdaVariableExprs and \({}/r\) may be absent. (See example 108 for a case with example 109 for a case with \(k=1\).)

    Non-normatively speaking, the effect of the algorithm can be summarized as follows: If \(M≥1\) and @@ -4926,7 +4941,7 @@

    5.5 class="math inline">\(\Pi_G\) transformations inject information about the nodes \(χ_1,…,χ_M\).

    -

    Example 61: Total number of +

    Example 62: Total number of sub-organizations for all organizations in the hierarchy defined in Hierarchy Examples with \(p=q={\tt ID}\) (case 1 of the 5.5 &$select=ID,Name,SubOrgCnt &$expand=Superordinate($select=ID)

    results in

    -
    {
    -  "@context":
    -      "$metadata#SalesOrganizations(ID,Name,SubOrgCnt,Superordinate(ID))",
    -  "value": [
    -    { "ID": "US West",      "Name": "US West",
    -      "SubOrgCount": 0, "Superordinate": { "ID": "US" } },
    -    { "ID": "US East",      "Name": "US East",
    -      "SubOrgCount": 0, "Superordinate": { "ID": "US" } },
    -    { "ID": "US",           "Name": "US",
    -      "SubOrgCount": 2, "Superordinate": { "ID": "Sales" } },
    -    { "ID": "EMEA Central", "Name": "EMEA Central",
    -      "SubOrgCount": 0, "Superordinate": { "ID": "EMEA" } },
    -    { "ID": "EMEA",         "Name": "EMEA",
    -      "SubOrgCount": 1, "Superordinate": { "ID": "Sales" } },
    -    { "ID": "Sales",        "Name": "Sales",
    -      "SubOrgCount": 5, "Superordinate": null }
    -  ]
    -}
    +
    {
    +  "@context":
    +      "$metadata#SalesOrganizations(ID,Name,SubOrgCnt,Superordinate(ID))",
    +  "value": [
    +    { "ID": "US West",      "Name": "US West",
    +      "SubOrgCount": 0, "Superordinate": { "ID": "US" } },
    +    { "ID": "US East",      "Name": "US East",
    +      "SubOrgCount": 0, "Superordinate": { "ID": "US" } },
    +    { "ID": "US",           "Name": "US",
    +      "SubOrgCount": 2, "Superordinate": { "ID": "Sales" } },
    +    { "ID": "EMEA Central", "Name": "EMEA Central",
    +      "SubOrgCount": 0, "Superordinate": { "ID": "EMEA" } },
    +    { "ID": "EMEA",         "Name": "EMEA",
    +      "SubOrgCount": 1, "Superordinate": { "ID": "Sales" } },
    +    { "ID": "Sales",        "Name": "Sales",
    +      "SubOrgCount": 5, "Superordinate": null }
    +  ]
    +}

    The value of the property \(χ_N\) in the algorithm is the node \(x\) at @@ -4978,7 +4993,7 @@

    5.5 otherwise). If \(N=1\), the Position parameter can be omitted.

    -

    ⚠ Example 62: Total sales +

    ⚠ Example 63: Total sales amounts per organization, both including and excluding sub-organizations, in the US sub-hierarchy defined in Hierarchy Examples with 5.5 /aggregate(Amount with sum as TotalAmountIncl, AmountExcl with sum as TotalAmountExcl))

    results in

    -
    {
    -  "@context": "$metadata#Sales(SalesOrganization(),
    -                               TotalAmountIncl,TotalAmountExcl)",
    -  "value": [
    -    { "SalesOrganization": { "ID": "US West", "Name": "US West" },
    -      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
    -      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 },
    -    { "SalesOrganization": { "ID": "US",      "Name": "US" },
    -      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
    -      "TotalAmountExcl": null },
    -    { "SalesOrganization": { "ID": "US East", "Name": "US East" },
    -      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
    -      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(SalesOrganization(),
    +                               TotalAmountIncl,TotalAmountExcl)",
    +  "value": [
    +    { "SalesOrganization": { "ID": "US West", "Name": "US West" },
    +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
    +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 },
    +    { "SalesOrganization": { "ID": "US",      "Name": "US" },
    +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
    +      "TotalAmountExcl": null },
    +    { "SalesOrganization": { "ID": "US East", "Name": "US East" },
    +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
    +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 }
    +  ]
    +}
    -

    ⚠ Example 63: Although +

    ⚠ Example 64: Although \(p={\tt ID}\) and \(q={\tt ID}\), they are not equal in the sense of case 1, because they are evaluated relative to different entity @@ -5036,19 +5051,19 @@

    5.5 $root/SalesOrganizations,SalesOrgHierarchy,ID))), aggregate(Amount with sum as TotalAmount))

    results in

    -
    {
    -  "@context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
    -  "value": [
    -    { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" },
    -      "TotalAmount": null },
    -    { "SalesOrganization": { "ID": "EMEA",  "Name": "EMEA" },
    -      "TotalAmount": null },
    -    { "SalesOrganization": { "ID": "US",    "Name": "US" },
    -      "TotalAmount": null },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(SalesOrganization(),TotalAmount)",
    +  "value": [
    +    { "SalesOrganization": { "ID": "Sales", "Name": "Corporate Sales" },
    +      "TotalAmount": null },
    +    { "SalesOrganization": { "ID": "EMEA",  "Name": "EMEA" },
    +      "TotalAmount": null },
    +    { "SalesOrganization": { "ID": "US",    "Name": "US" },
    +      "TotalAmount": null },
    +    ...
    +  ]
    +}

    The algorithm given so far is valid for a single-valued RecursiveHierarchy/ParentNavigationProperty. The remainder @@ -5088,34 +5103,34 @@

    7

    Grouping without specifying a set transformation returns the distinct combination of the grouping properties.

    -

    Example 64:

    +

    Example 65:

    GET /service/Customers?$apply=groupby((Name))

    results in

    -
    {
    -  "@context": "$metadata#Customers(Name)",
    -  "value": [
    -    { "Name": "Luc" },
    -    { "Name": "Joe" },
    -    { "Name": "Sue" }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Customers(Name)",
    +  "value": [
    +    { "Name": "Luc" },
    +    { "Name": "Joe" },
    +    { "Name": "Sue" }
    +  ]
    +}

    Note that "Sue" appears only once although the customer base contains two different Sues.

    Aggregation is also possible across related entities.

    -

    Example 65: customers that bought something

    +

    Example 66: customers that bought something

    GET /service/Sales?$apply=groupby((Customer/Name))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Name))",
    -  "value": [
    -    { "Customer": { "Name": "Joe" } },
    -    { "Customer": { "Name": "Sue" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Name))",
    +  "value": [
    +    { "Customer": { "Name": "Joe" } },
    +    { "Customer": { "Name": "Sue" } }
    +  ]
    +}

    Since groupby expands navigation properties in grouping properties by default, this is the same result as if the request would include a $expand=Customer($select=Name). With the use of @@ -5129,99 +5144,99 @@

    7 right level of uniqueness in the grouping can repair that.

    -

    Example 66:

    +

    Example 67:

    GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Name,ID))",
    -  "value": [
    -    { "Customer": { "Name": "Joe", "ID": "C1" } },
    -    { "Customer": { "Name": "Sue", "ID": "C2" } },
    -    { "Customer": { "Name": "Sue", "ID": "C3" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Name,ID))",
    +  "value": [
    +    { "Customer": { "Name": "Joe", "ID": "C1" } },
    +    { "Customer": { "Name": "Sue", "ID": "C2" } },
    +    { "Customer": { "Name": "Sue", "ID": "C3" } }
    +  ]
    +}

    This could also have been formulated as

    GET /service/Sales?$apply=groupby((Customer))
                &$expand=Customer($select=Name,ID)
    -

    Example 67: Grouping by +

    Example 68: Grouping by navigation property Customer

    
     GET /service/Sales?$apply=groupby((Customer))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer())",
    -  "value": [
    -    { "Customer": { "ID": "C1", "Name": "Joe", "Country": "USA" } },
    -    { "Customer": { "ID": "C2", "Name": "Sue", "Country": "USA" } },
    -    { "Customer": { "ID": "C3", "Name": "Sue", "Country": "Netherlands" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer())",
    +  "value": [
    +    { "Customer": { "ID": "C1", "Name": "Joe", "Country": "USA" } },
    +    { "Customer": { "ID": "C2", "Name": "Sue", "Country": "USA" } },
    +    { "Customer": { "ID": "C3", "Name": "Sue", "Country": "Netherlands" } }
    +  ]
    +}
    -

    Example 68: the first question in the motivating example in Example 69: the first question in the motivating example in section 2.3, which customers bought which products, can now be expressed as

    GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID,Product/Name))

    and results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
    -  "value": [
    -    { "Customer": { "Name": "Joe", "ID": "C1" },
    -      "Product": { "Name": "Coffee"} },
    -    { "Customer": { "Name": "Joe", "ID": "C1" },
    -      "Product": { "Name": "Paper" } },
    -    { "Customer": { "Name": "Joe", "ID": "C1" },
    -      "Product": { "Name": "Sugar" } },
    -    { "Customer": { "Name": "Sue", "ID": "C2" },
    -      "Product": { "Name": "Coffee"} },
    -    { "Customer": { "Name": "Sue", "ID": "C2" },
    -      "Product": { "Name": "Paper" } },
    -    { "Customer": { "Name": "Sue", "ID": "C3" },
    -      "Product": { "Name": "Paper" } },
    -    { "Customer": { "Name": "Sue", "ID": "C3" },
    -      "Product": { "Name": "Sugar" } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Name,ID),Product(Name))",
    +  "value": [
    +    { "Customer": { "Name": "Joe", "ID": "C1" },
    +      "Product": { "Name": "Coffee"} },
    +    { "Customer": { "Name": "Joe", "ID": "C1" },
    +      "Product": { "Name": "Paper" } },
    +    { "Customer": { "Name": "Joe", "ID": "C1" },
    +      "Product": { "Name": "Sugar" } },
    +    { "Customer": { "Name": "Sue", "ID": "C2" },
    +      "Product": { "Name": "Coffee"} },
    +    { "Customer": { "Name": "Sue", "ID": "C2" },
    +      "Product": { "Name": "Paper" } },
    +    { "Customer": { "Name": "Sue", "ID": "C3" },
    +      "Product": { "Name": "Paper" } },
    +    { "Customer": { "Name": "Sue", "ID": "C3" },
    +      "Product": { "Name": "Sugar" } }
    +  ]
    +}
    -

    ⚠ Example 69: +

    ⚠ Example 70: grouping by properties of subtypes

    GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating,
                                           SalesModel.NonFoodProduct/RatingClass))

    results in

    -
    {
    -  "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
    -                                  SalesModel.NonFoodProduct/RatingClass)",
    -  "value": [
    -    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
    -    { "@type": "#SalesModel.FoodProduct", "Rating": null },
    -    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
    -    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": null }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(SalesModel.FoodProduct/Rating,
    +                                  SalesModel.NonFoodProduct/RatingClass)",
    +  "value": [
    +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
    +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
    +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": "average" },
    +    { "@type": "#SalesModel.NonFoodProduct", "RatingClass": null }
    +  ]
    +}
    -

    ⚠ Example 70: +

    ⚠ Example 71: grouping by a property of a subtype

    GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating))

    results in a third group representing entities with no SalesModel.FoodProduct/Rating, including the SalesModel.NonFoodProducts:

    -
    {
    -  "@context": "$metadata#Products(@Core.AnyStructure)",
    -  "value": [
    -    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
    -    { "@type": "#SalesModel.FoodProduct", "Rating": null },
    -    { }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(@Core.AnyStructure)",
    +  "value": [
    +    { "@type": "#SalesModel.FoodProduct", "Rating": 5 },
    +    { "@type": "#SalesModel.FoodProduct", "Rating": null },
    +    { }
    +  ]
    +}

    7.2 @@ -5239,20 +5254,20 @@

    7 href="#Keywordas">alias. The aggregated values are returned in a dynamic property whose name is determined by the alias.

    -

    Example 71:

    +

    Example 72:

    GET /service/Products?$apply=groupby((Name),
                                   aggregate(Sales/Amount with sum as Total))

    results in

    -
    {
    -  "@context": "$metadata#Products(Name,Total)",
    -  "value": [
    -    { "Name": "Coffee", "Total@type": "Decimal", "Total":   12 },
    -    { "Name": "Paper",  "Total@type": "Decimal", "Total":    8 },
    -    { "Name": "Pencil",                                "Total": null },
    -    { "Name": "Sugar",  "Total@type": "Decimal", "Total":    4 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(Name,Total)",
    +  "value": [
    +    { "Name": "Coffee", "Total@type": "Decimal", "Total":   12 },
    +    { "Name": "Paper",  "Total@type": "Decimal", "Total":    8 },
    +    { "Name": "Pencil",                                "Total": null },
    +    { "Name": "Sugar",  "Total@type": "Decimal", "Total":    4 }
    +  ]
    +}

    Note that the base set of the request is Products, so there is a result item for product Pencil even though there are no sales items. The input set for the aggregation in the third row @@ -5263,251 +5278,251 @@

    7 the empty collection is null.

    -

    Example 72: Alternatively, the +

    Example 73: Alternatively, the request could ask for the aggregated amount to be nested inside a clone of Sales

    GET /service/Products?$apply=addnested(Sales,
         aggregate(Amount with sum as Total) as AggregatedSales)

    results in

    -
    {
    -  "@context": "$metadata#Products(AggregatedSales())",
    -  "value": [
    -    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": [ { "Total@type": "Decimal", "Total": 12 } ] },
    -    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  8 } ] },
    -    { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": [ {                              "Total": null } ] },
    -    { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
    -      "AggregatedSales@context": "#Sales(Total)",
    -      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  4 } ] }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(AggregatedSales())",
    +  "value": [
    +    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": [ { "Total@type": "Decimal", "Total": 12 } ] },
    +    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  8 } ] },
    +    { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": [ {                              "Total": null } ] },
    +    { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
    +      "AggregatedSales@context": "#Sales(Total)",
    +      "AggregatedSales": [ { "Total@type": "Decimal", "Total":  4 } ] }
    +  ]
    +}
    -

    Example 73: To compute the aggregate as a property without nesting, +

    Example 74: To compute the aggregate as a property without nesting, use the aggregate function in $compute rather than the aggregate transformation in $apply:

    GET /service/Products?$compute=Sales/aggregate(Amount with sum) as Total

    results in

    -
    {
    -  "@context": "$metadata#Products(*,Total)",
    -  "value": [
    -    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
    -      "Total@type": "Decimal", "Total": 12 },
    -    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
    -      "Total@type": "Decimal", "Total":  8 },
    -    { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
    -                                     "Total": null },
    -    { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
    -      "Total@type": "Decimal", "Total":  4 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(*,Total)",
    +  "value": [
    +    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06,
    +      "Total@type": "Decimal", "Total": 12 },
    +    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14,
    +      "Total@type": "Decimal", "Total":  8 },
    +    { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14,
    +                                     "Total": null },
    +    { "ID": "P1", "Name": "Sugar",  "Color": "White", "TaxRate": 0.06,
    +      "Total@type": "Decimal", "Total":  4 }
    +  ]
    +}

    The expression $it/Sales refers to the sales of the current product. Without $it, all sales of all products would be aggregated, because the input collection for the aggregate function consists of all products.

    -

    Example 74: Alternatively, join could be applied to +

    Example 75: Alternatively, join could be applied to yield a flat structure:

    GET /service/Products?$apply=
         join(Sales as TotalSales,aggregate(Amount with sum as Total))
         /groupby((Name,TotalSales/Total))

    results in

    -
    {
    -  "@context": "$metadata#Products(Name,TotalSales())",
    -  "value": [
    -    { "Name": "Coffee",
    -      "TotalSales@context": "#Sales(Total)/$entity",
    -      "TotalSales": { "Total@type": "Decimal", "Total": 12 } },
    -    { "Name": "Paper",
    -      "TotalSales@context": "#Sales(Total)/$entity",
    -      "TotalSales": { "Total@type": "Decimal", "Total":  8 } },
    -    { "Name": "Sugar",
    -      "TotalSales@context": "#Sales(Total)/$entity",
    -      "TotalSales": { "Total@type": "Decimal", "Total":  4 } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(Name,TotalSales())",
    +  "value": [
    +    { "Name": "Coffee",
    +      "TotalSales@context": "#Sales(Total)/$entity",
    +      "TotalSales": { "Total@type": "Decimal", "Total": 12 } },
    +    { "Name": "Paper",
    +      "TotalSales@context": "#Sales(Total)/$entity",
    +      "TotalSales": { "Total@type": "Decimal", "Total":  8 } },
    +    { "Name": "Sugar",
    +      "TotalSales@context": "#Sales(Total)/$entity",
    +      "TotalSales": { "Total@type": "Decimal", "Total":  4 } }
    +  ]
    +}

    Applying outerjoin instead would return an additional entity for product with ID "Pencil" and TotalSales having a null value.

    -

    Example 75:

    +

    Example 76:

    GET /service/Sales?$apply=groupby((Customer/Country),
                                 aggregate(Amount with average as AverageAmount))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),AverageAmount)",
    -  "value": [
    -    { "Customer": { "Country": "Netherlands" },
    -      "AverageAmount": 1.6666666666666667 },
    -    { "Customer": { "Country": "USA" },
    -      "AverageAmount": 3.8 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),AverageAmount)",
    +  "value": [
    +    { "Customer": { "Country": "Netherlands" },
    +      "AverageAmount": 1.6666666666666667 },
    +    { "Customer": { "Country": "USA" },
    +      "AverageAmount": 3.8 }
    +  ]
    +}

    Here the AverageAmount is of type Edm.Double.

    -

    Example 76: $count after navigation property

    +

    Example 77: $count after navigation property

    GET /service/Products?$apply=groupby((Name),
                                   aggregate(Sales/$count as SalesCount))

    results in

    -
    {
    -  "@context": "$metadata#Products(Name,SalesCount)",
    -  "value": [
    -    { "Name": "Coffee", "SalesCount@type": "Decimal", "SalesCount": 2 },
    -    { "Name": "Paper",  "SalesCount@type": "Decimal", "SalesCount": 4 },
    -    { "Name": "Pencil", "SalesCount@type": "Decimal", "SalesCount": 0 },
    -    { "Name": "Sugar",  "SalesCount@type": "Decimal", "SalesCount": 2 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(Name,SalesCount)",
    +  "value": [
    +    { "Name": "Coffee", "SalesCount@type": "Decimal", "SalesCount": 2 },
    +    { "Name": "Paper",  "SalesCount@type": "Decimal", "SalesCount": 4 },
    +    { "Name": "Pencil", "SalesCount@type": "Decimal", "SalesCount": 0 },
    +    { "Name": "Sugar",  "SalesCount@type": "Decimal", "SalesCount": 2 }
    +  ]
    +}

    To place the number of instances in a group next to other aggregated values, the aggregate expression $count can be used:

    -

    ⚠ Example 77: The effect of the groupby is to create +

    ⚠ Example 78: The effect of the groupby is to create transient entities and avoid in the result structural properties other than Name.

    GET /service/Products?$apply=groupby((Name),addnested(Sales,
           aggregate($count as SalesCount,
                     Amount with sum as TotalAmount) as AggregatedSales))

    results in

    -
    {
    -  "@context": "$metadata#Products(Name,AggregatedSales())",
    -  "value": [
    -    { "Name": "Coffee",
    -      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    -      "AggregatedSales": [ { "SalesCount": 2,
    -          "TotalAmount@type": "Decimal", "TotalAmount": 12 } ] },
    -    { "Name": "Paper",
    -      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    -      "AggregatedSales": [ { "SalesCount": 4,
    -          "TotalAmount@type": "Decimal", "TotalAmount":  8 } ] },
    -    { "Name": "Pencil",
    -      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    -      "AggregatedSales": [ { "SalesCount": 0, "TotalAmount":  null } ] },
    -    { "Name": "Sugar",
    -      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    -      "AggregatedSales": [ { "SalesCount": 2,
    -          "TotalAmount@type": "Decimal",  "TotalAmount":  4 } ] }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(Name,AggregatedSales())",
    +  "value": [
    +    { "Name": "Coffee",
    +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    +      "AggregatedSales": [ { "SalesCount": 2,
    +          "TotalAmount@type": "Decimal", "TotalAmount": 12 } ] },
    +    { "Name": "Paper",
    +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    +      "AggregatedSales": [ { "SalesCount": 4,
    +          "TotalAmount@type": "Decimal", "TotalAmount":  8 } ] },
    +    { "Name": "Pencil",
    +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    +      "AggregatedSales": [ { "SalesCount": 0, "TotalAmount":  null } ] },
    +    { "Name": "Sugar",
    +      "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)",
    +      "AggregatedSales": [ { "SalesCount": 2,
    +          "TotalAmount@type": "Decimal",  "TotalAmount":  4 } ] }
    +  ]
    +}

    The aggregate function can not only be used in $compute but also in $filter and $orderby:

    -

    Example 78: Products with an aggregated sales volume of ten or +

    Example 79: Products with an aggregated sales volume of ten or more

    GET /service/Products?$filter=Sales/aggregate(Amount with sum) ge 10

    results in

    -
    {
    -  "@context": "$metadata#Products",
    -  "value": [
    -    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06 },
    -    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products",
    +  "value": [
    +    { "ID": "P2", "Name": "Coffee", "Color": "Brown", "TaxRate": 0.06 },
    +    { "ID": "P3", "Name": "Paper",  "Color": "White", "TaxRate": 0.14 }
    +  ]
    +}
    -

    Example 79: Customers in descending order of their aggregated sales +

    Example 80: Customers in descending order of their aggregated sales volume

    GET /service/Customers?$orderby=Sales/aggregate(Amount with sum) desc

    results in

    -
    {
    -  "@context": "$metadata#Customers",
    -  "value": [
    -    { "ID": "C2", "Name": "Sue", "Country": "USA" },
    -    { "ID": "C1", "Name": "Joe", "Country": "USA" },
    -    { "ID": "C3", "Name": "Sue", "Country": "Netherlands" },
    -    { "ID": "C4", "Name": "Luc", "Country": "France" }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Customers",
    +  "value": [
    +    { "ID": "C2", "Name": "Sue", "Country": "USA" },
    +    { "ID": "C1", "Name": "Joe", "Country": "USA" },
    +    { "ID": "C3", "Name": "Sue", "Country": "Netherlands" },
    +    { "ID": "C4", "Name": "Luc", "Country": "France" }
    +  ]
    +}
    -

    Example 80: Contribution of each sales to grand total sales +

    Example 81: Contribution of each sales to grand total sales amount

    GET /service/Sales?$compute=Amount divby $these/aggregate(Amount with sum)
                                 as Contribution

    results in

    -
    {
    -  "@context": "$metadata#Sales(*,Contribution)",
    -  "value": [
    -    { "ID": 1, "Amount": 1, "Contribution@type": "Decimal",
    -                            "Contribution": 0.0416666666666667 },
    -    { "ID": 2, "Amount": 2, "Contribution@type": "Decimal",
    -                            "Contribution": 0.0833333333333333 },
    -    { "ID": 3, "Amount": 4, "Contribution@type": "Decimal",
    -                            "Contribution": 0.1666666666666667 },
    -    { "ID": 4, "Amount": 8, "Contribution@type": "Decimal",
    -                            "Contribution": 0.3333333333333333 },
    -    { "ID": 5, "Amount": 4, "Contribution@type": "Decimal",
    -                            "Contribution": 0.1666666666666667 },
    -    { "ID": 6, "Amount": 2, "Contribution@type": "Decimal",
    -                            "Contribution": 0.0833333333333333 },
    -    { "ID": 7, "Amount": 1, "Contribution@type": "Decimal",
    -                            "Contribution": 0.0416666666666667 },
    -    { "ID": 8, "Amount": 2, "Contribution@type": "Decimal",
    -                            "Contribution": 0.0833333333333333 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(*,Contribution)",
    +  "value": [
    +    { "ID": 1, "Amount": 1, "Contribution@type": "Decimal",
    +                            "Contribution": 0.0416666666666667 },
    +    { "ID": 2, "Amount": 2, "Contribution@type": "Decimal",
    +                            "Contribution": 0.0833333333333333 },
    +    { "ID": 3, "Amount": 4, "Contribution@type": "Decimal",
    +                            "Contribution": 0.1666666666666667 },
    +    { "ID": 4, "Amount": 8, "Contribution@type": "Decimal",
    +                            "Contribution": 0.3333333333333333 },
    +    { "ID": 5, "Amount": 4, "Contribution@type": "Decimal",
    +                            "Contribution": 0.1666666666666667 },
    +    { "ID": 6, "Amount": 2, "Contribution@type": "Decimal",
    +                            "Contribution": 0.0833333333333333 },
    +    { "ID": 7, "Amount": 1, "Contribution@type": "Decimal",
    +                            "Contribution": 0.0416666666666667 },
    +    { "ID": 8, "Amount": 2, "Contribution@type": "Decimal",
    +                            "Contribution": 0.0833333333333333 }
    +  ]
    +}
    -

    Example 81: Product categories with at least one product having an +

    Example 82: Product categories with at least one product having an aggregated sales amount greater than 10

    GET /service/Categories?$filter=Products/any(
                                     p:p/Sales/aggregate(Amount with sum) gt 10)

    results in

    -
    {
    -  "@context": "$metadata#Categories",
    -  "value": [
    -    { "ID": "PG1", "Name": "Food" }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Categories",
    +  "value": [
    +    { "ID": "PG1", "Name": "Food" }
    +  ]
    +}

    The aggregate function can also be applied inside $apply:

    -

    Example 82: Sales volume per customer in relation to total volume

    +

    Example 83: Sales volume per customer in relation to total volume

    GET /service/Sales?$apply=
         groupby((Customer),aggregate(Amount with sum as CustomerAmount))
         /compute(CustomerAmount divby $these/aggregate(CustomerAmount with sum)
                  as Contribution)
       &$expand=Customer/$ref

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
    -  "value": [
    -    { "Customer":    { "@id": "Customers('C1')" },
    -      "Contribution@type": "Decimal", "Contribution": 0.2916667 },
    -    { "Customer":    { "@id": "Customers('C2')" },
    -      "Contribution@type": "Decimal", "Contribution": 0.5 },
    -    { "Customer":    { "@id": "Customers('C3')" },
    -      "Contribution@type": "Decimal", "Contribution": 0.2083333 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(),CustomerAmount,Contribution)",
    +  "value": [
    +    { "Customer":    { "@id": "Customers('C1')" },
    +      "Contribution@type": "Decimal", "Contribution": 0.2916667 },
    +    { "Customer":    { "@id": "Customers('C2')" },
    +      "Contribution@type": "Decimal", "Contribution": 0.5 },
    +    { "Customer":    { "@id": "Customers('C3')" },
    +      "Contribution@type": "Decimal", "Contribution": 0.2083333 }
    +  ]
    +}

    7.3 Requesting Expanded Results

    -

    Example 84: Assuming an extension of the data model where +

    Example 85: Assuming an extension of the data model where Customer contains an additional collection-valued complex property Addresses and these contain a single-valued navigation property ResponsibleSalesOrganization, @@ -5542,62 +5557,62 @@

    7 compute(Superordinate/Name as SalesRegion) as AugmentedSalesOrganization)

    results in

    -
    {
    -  "@context": "$metadata#Customers(Addresses(AugmentedSalesOrganization())",
    -  "value": [
    -    { "ID": "C1", "Name": "Joe", "Country": "US",
    -      "Addresses": [
    -        { "Locality": "Seattle",
    -          "AugmentedSalesOrganization":
    -          { "@context": "#SalesOrganizations/$entity",
    -            "ID": "US West", "SalesRegion": "US" } },
    -        { "Locality": "DC",
    -          "AugmentedSalesOrganization":
    -          { "@context": "#SalesOrganizations/$entity",
    -            "ID": "US",      "SalesRegion": "Corporate Sales" } },
    -      ]
    -    }, ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Customers(Addresses(AugmentedSalesOrganization())",
    +  "value": [
    +    { "ID": "C1", "Name": "Joe", "Country": "US",
    +      "Addresses": [
    +        { "Locality": "Seattle",
    +          "AugmentedSalesOrganization":
    +          { "@context": "#SalesOrganizations/$entity",
    +            "ID": "US West", "SalesRegion": "US" } },
    +        { "Locality": "DC",
    +          "AugmentedSalesOrganization":
    +          { "@context": "#SalesOrganizations/$entity",
    +            "ID": "US",      "SalesRegion": "Corporate Sales" } },
    +      ]
    +    }, ...
    +  ]
    +}

    addnested transformations can be nested.

    -

    Example 85: nested addnested transformations

    +

    Example 86: nested addnested transformations

    GET /service/Categories?$apply=
         addnested(Products,
           addnested(Sales,filter(Amount gt 3) as FilteredSales)
         as FilteredProducts)

    results in

    -
    {
    -  "@context": "$metadata#Categories(FilteredProducts()",
    -  "value": [
    -    { "ID": "PG1", "Name": "Food",
    -      "FilteredProducts@context": "#Products(FilteredSales())",
    -      "FilteredProducts": [
    -        { "ID": "P1", "Name": "Sugar",  "Color": "White",
    -          "FilteredSales@context": "#Sales",
    -          "FilteredSales": [] },
    -        { "ID": "P2", "Name": "Coffee", "Color": "Brown",
    -          "FilteredSales@context": "#Sales",
    -          "FilteredSales": [ { "ID": 3, "Amount": 4 },
    -                             { "ID": 4, "Amount": 8 } ] }
    -      ]
    -    },
    -    { "ID": "PG2", "Name": "Non-Food",
    -      "FilteredProducts@context": "#Products(FilteredSales())",
    -      "FilteredProducts": [
    -        { "ID": "P3", "Name": "Paper",  "Color": "White",
    -          "FilteredSales@context": "#Sales",
    -          "FilteredSales": [ { "ID": 5, "Amount": 4 } ] },
    -        { "ID": "P4", "Name": "Pencil", "Color": "Black",
    -          "FilteredSales@context": "#Sales",
    -          "FilteredSales": [] }
    -      ]
    -    }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Categories(FilteredProducts()",
    +  "value": [
    +    { "ID": "PG1", "Name": "Food",
    +      "FilteredProducts@context": "#Products(FilteredSales())",
    +      "FilteredProducts": [
    +        { "ID": "P1", "Name": "Sugar",  "Color": "White",
    +          "FilteredSales@context": "#Sales",
    +          "FilteredSales": [] },
    +        { "ID": "P2", "Name": "Coffee", "Color": "Brown",
    +          "FilteredSales@context": "#Sales",
    +          "FilteredSales": [ { "ID": 3, "Amount": 4 },
    +                             { "ID": 4, "Amount": 8 } ] }
    +      ]
    +    },
    +    { "ID": "PG2", "Name": "Non-Food",
    +      "FilteredProducts@context": "#Products(FilteredSales())",
    +      "FilteredProducts": [
    +        { "ID": "P3", "Name": "Paper",  "Color": "White",
    +          "FilteredSales@context": "#Sales",
    +          "FilteredSales": [ { "ID": 5, "Amount": 4 } ] },
    +        { "ID": "P4", "Name": "Pencil", "Color": "Black",
    +          "FilteredSales@context": "#Sales",
    +          "FilteredSales": [] }
    +      ]
    +    }
    +  ]
    +}

    Instead of keeping all related entities from navigation properties that addnested expanded by default, an explicit $expand controls which of them to include in the @@ -5611,70 +5626,70 @@

    7 navigation properties expanded in the result.

    -

    Example 86: Here only the GroupedSales are expanded, +

    Example 87: Here only the GroupedSales are expanded, because they are named in $expand, the related Product entity is not:

    GET /service/Customers?$apply=addnested(Sales,
         groupby((Product/Name)) as GroupedSales)
       &$expand=GroupedSales

    results in

    -
    {
    -  "@context": "$metadata#Customers(GroupedSales())",
    -  "value": [
    -    { "ID": "C1", "Name": "Joe", "Country": "USA",
    -      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    -      "GroupedSales": [
    -        { },
    -        { },
    -        { }
    -      ] },
    -    { "ID": "C2", "Name": "Sue", "Country": "USA",
    -      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    -      "GroupedSales": [
    -        { },
    -        { }
    -      ] },
    -    { "ID": "C3", "Name": "Joe", "Country": "Netherlands",
    -      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    -      "GroupedSales": [
    -        { },
    -        { }
    -      ] },
    -    { "ID": "C4", "Name": "Luc", "Country": "France",
    -      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    -      "GroupedSales": [ ] }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Customers(GroupedSales())",
    +  "value": [
    +    { "ID": "C1", "Name": "Joe", "Country": "USA",
    +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    +      "GroupedSales": [
    +        { },
    +        { },
    +        { }
    +      ] },
    +    { "ID": "C2", "Name": "Sue", "Country": "USA",
    +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    +      "GroupedSales": [
    +        { },
    +        { }
    +      ] },
    +    { "ID": "C3", "Name": "Joe", "Country": "Netherlands",
    +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    +      "GroupedSales": [
    +        { },
    +        { }
    +      ] },
    +    { "ID": "C4", "Name": "Luc", "Country": "France",
    +      "GroupedSales@context": "#Sales(@Core.AnyStructure)",
    +      "GroupedSales": [ ] }
    +  ]
    +}
    -

    Example 87: use outerjoin to split up collection-valued +

    Example 88: use outerjoin to split up collection-valued navigation properties for grouping

    GET /service/Customers?$apply=outerjoin(Sales as ProductSales)
                            /groupby((Country,ProductSales/Product/Name))

    returns the different combinations of products sold per country:

    -
    {
    -  "@context":"$metadata#Customers(Country,ProductSales())",
    -  "value": [
    -    { "Country": "Netherlands",
    -      "ProductSales@context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Paper"  } } },
    -    { "Country": "Netherlands",
    -      "ProductSales@context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    -    { "Country": "USA",
    -      "ProductSales@context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Coffee" } } },
    -    { "Country": "USA",
    -      "ProductSales@context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Paper"  } } },
    -    { "Country": "USA",
    -      "ProductSales@context": "#Sales(Product(Name))/$entity",
    -      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    -    { "Country": "France", "ProductSales": null }
    -  ]
    -}
    +
    {
    +  "@context":"$metadata#Customers(Country,ProductSales())",
    +  "value": [
    +    { "Country": "Netherlands",
    +      "ProductSales@context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Paper"  } } },
    +    { "Country": "Netherlands",
    +      "ProductSales@context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    +    { "Country": "USA",
    +      "ProductSales@context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Coffee" } } },
    +    { "Country": "USA",
    +      "ProductSales@context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Paper"  } } },
    +    { "Country": "USA",
    +      "ProductSales@context": "#Sales(Product(Name))/$entity",
    +      "ProductSales": { "Product": { "Name": "Sugar"  } } },
    +    { "Country": "France", "ProductSales": null }
    +  ]
    +}

    Requesting Custom Aggregates Custom aggregates are defined through the CustomAggregate @@ -5684,44 +5699,44 @@

    7 aggregate in the aggregate clause.

    -

    Example 88:

    +

    Example 89:

    GET /service/Sales?$apply=groupby((Customer/Country),
                                aggregate(Amount with sum as Actual,Forecast))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
    -  "value": [
    -    { "Customer": { "Country": "Netherlands" },
    -      "Actual@type": "Decimal", "Actual":  5,
    -      "Forecast@type": "Decimal", "Forecast": 4 },
    -    { "Customer": { "Country": "USA" },
    -      "Actual@type": "Decimal", "Actual": 19,
    -      "Forecast@type": "Decimal", "Forecast": 21 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),Actual,Forecast)",
    +  "value": [
    +    { "Customer": { "Country": "Netherlands" },
    +      "Actual@type": "Decimal", "Actual":  5,
    +      "Forecast@type": "Decimal", "Forecast": 4 },
    +    { "Customer": { "Country": "USA" },
    +      "Actual@type": "Decimal", "Actual": 19,
    +      "Forecast@type": "Decimal", "Forecast": 21 }
    +  ]
    +}

    When associated with an entity set a custom aggregate MAY have the same name as a property of the underlying entity type with the same type as the type returned by the custom aggregate. This is typically done when the aggregate is used as a default aggregate for that property.

    -

    Example 89: A custom aggregate can be defined with the same name as a +

    Example 90: A custom aggregate can be defined with the same name as a property of the same type in order to define a default aggregate for that property.

    GET /service/Sales?$apply=groupby((Customer/Country),aggregate(Amount))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),Amount)",
    -  "value": [
    -    { "Customer": { "Country": "Netherlands" }, "Amount":  5 },
    -    { "Customer": { "Country": "USA" },         "Amount": 19 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),Amount)",
    +  "value": [
    +    { "Customer": { "Country": "Netherlands" }, "Amount":  5 },
    +    { "Customer": { "Country": "USA" },         "Amount": 19 }
    +  ]
    +}
    -

    Example 90: illustrates rule 1 for keyword +

    Example 91: illustrates rule 1 for keyword from: maximal sales forecast for a product

    GET /service/Sales?$apply=aggregate(Forecast from Product with max
                                         as MaxProductForecast)
    @@ -5731,7 +5746,7 @@

    7 /aggregate(Forecast with max as MaxProductForecast)

    -

    Example 91: illustrates rule 2 for keyword +

    Example 92: illustrates rule 2 for keyword from: the forecast is computed in two steps

    GET /service/Sales?$apply=aggregate(Forecast from Product as ProductForecast)

    is equivalent to the following (except that the property name is @@ -5741,7 +5756,7 @@

    7 /aggregate(Forecast)

    7.4 @@ -5762,74 +5777,74 @@

    7.4

    A property can be aggregated in multiple ways, each with a different alias.

    -

    Example 93:

    +

    Example 94:

    GET /service/Sales?$apply=groupby((Customer/Country),
                                aggregate(Amount with sum as Total,
                                          Amount with average as AvgAmt))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
    -  "value": [
    -    { "Customer": { "Country": "Netherlands" },
    -      "Total@type": "Decimal", "Total":  5,
    -      "AvgAmt@type": "Decimal", "AvgAmt": 1.6666667 },
    -    { "Customer": { "Country": "USA" },
    -      "Total@type": "Decimal", "Total": 19,
    -      "AvgAmt@type": "Decimal", "AvgAmt": 3.8 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),Total,AvgAmt)",
    +  "value": [
    +    { "Customer": { "Country": "Netherlands" },
    +      "Total@type": "Decimal", "Total":  5,
    +      "AvgAmt@type": "Decimal", "AvgAmt": 1.6666667 },
    +    { "Customer": { "Country": "USA" },
    +      "Total@type": "Decimal", "Total": 19,
    +      "AvgAmt@type": "Decimal", "AvgAmt": 3.8 }
    +  ]
    +}

    The introduced dynamic property is added to the context where the aggregate expression is applied to:

    -

    Example 94:

    +

    Example 95:

    GET /service/Products?$apply=groupby((Name),
                                   aggregate(Sales/Amount with sum as Total))
         /groupby((Name),
          addnested(Sales,aggregate(Amount with average as AvgAmt)
                    as AggregatedSales))

    results in

    -
    {
    -  "@context": "$metadata#Products(Name,Total,AggregatedSales())",
    -  "value": [
    -    { "Name": "Coffee", "Total":   12,
    -      "AggregatedSales@context": "#Sales(AvgAmt)",
    -      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
    -                             "AvgAmt": 6 } ] },
    -    { "Name": "Paper",  "Total":    8,
    -      "AggregatedSales@context": "#Sales(AvgAmt)",
    -      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
    -                             "AvgAmt": 2 } ] },
    -    { "Name": "Pencil", "Total": null,
    -      "AggregatedSales@context": "#Sales(AvgAmt)",
    -      "AggregatedSales": [ { "AvgAmt": null } ] },
    -    { "Name": "Sugar",  "Total":    4,
    -      "AggregatedSales@context": "#Sales(AvgAmt)",
    -      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
    -                             "AvgAmt": 2 } ] }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(Name,Total,AggregatedSales())",
    +  "value": [
    +    { "Name": "Coffee", "Total":   12,
    +      "AggregatedSales@context": "#Sales(AvgAmt)",
    +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
    +                             "AvgAmt": 6 } ] },
    +    { "Name": "Paper",  "Total":    8,
    +      "AggregatedSales@context": "#Sales(AvgAmt)",
    +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
    +                             "AvgAmt": 2 } ] },
    +    { "Name": "Pencil", "Total": null,
    +      "AggregatedSales@context": "#Sales(AvgAmt)",
    +      "AggregatedSales": [ { "AvgAmt": null } ] },
    +    { "Name": "Sugar",  "Total":    4,
    +      "AggregatedSales@context": "#Sales(AvgAmt)",
    +      "AggregatedSales": [ { "AvgAmt@type": "Decimal",
    +                             "AvgAmt": 2 } ] }
    +  ]
    +}

    There is no hard distinction between groupable and aggregatable properties: the same property can be aggregated and used to group the aggregated results.

    -

    Example 95:

    +

    Example 96:

    GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total))

    will return all distinct amounts appearing in sales orders and how much money was made with deals of this amount

    -
    {
    -  "@context": "$metadata#Sales(Amount,Total)",
    -  "value": [
    -    { "Amount": 1, "Total@type": "Decimal", "Total": 2 },
    -    { "Amount": 2, "Total@type": "Decimal", "Total": 6 },
    -    { "Amount": 4, "Total@type": "Decimal", "Total": 8 },
    -    { "Amount": 8, "Total@type": "Decimal", "Total": 8 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Amount,Total)",
    +  "value": [
    +    { "Amount": 1, "Total@type": "Decimal", "Total": 2 },
    +    { "Amount": 2, "Total@type": "Decimal", "Total": 6 },
    +    { "Amount": 4, "Total@type": "Decimal", "Total": 8 },
    +    { "Amount": 8, "Total@type": "Decimal", "Total": 8 }
    +  ]
    +}

    7.5 @@ -5837,7 +5852,7 @@

    7.4

    Dynamic property names may be reused in different transformation sequences passed to concat.

    -

    Example 96: to get the +

    Example 97: to get the best-selling product per country with sub-totals for every country, the partial results of a transformation sequence and a groupby transformation are concatenated:

    @@ -5848,56 +5863,56 @@

    7.4 groupby((Customer/Country), aggregate(Amount with sum as Total)))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),Total)",
    -  "value": [
    -    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
    -      "Total@type": "Decimal", "Total": 12
    -    },
    -    { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
    -      "Total@type": "Decimal", "Total":  3
    -    },
    -    { "Customer":{ "Country": "USA" },
    -      "Total@type": "Decimal", "Total": 19
    -    },
    -    { "Customer":{ "Country": "Netherlands" },
    -      "Total@type": "Decimal", "Total":  5
    -    }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),Total)",
    +  "value": [
    +    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
    +      "Total@type": "Decimal", "Total": 12
    +    },
    +    { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
    +      "Total@type": "Decimal", "Total":  3
    +    },
    +    { "Customer":{ "Country": "USA" },
    +      "Total@type": "Decimal", "Total": 19
    +    },
    +    { "Customer":{ "Country": "Netherlands" },
    +      "Total@type": "Decimal", "Total":  5
    +    }
    +  ]
    +}

    -

    Example 97: transformation sequences are also useful inside +

    Example 98: transformation sequences are also useful inside groupby: Aggregate the amount by only considering the top two sales amounts per product and country:

    GET /service/Sales?$apply=groupby((Customer/Country,Product/Name),
                           topcount(2,Amount)/aggregate(Amount with sum as Total))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
    -  "value": [
    -    { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
    -      "Total@type": "Decimal", "Total":  3
    -    },
    -    { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Sugar" },
    -      "Total@type": "Decimal", "Total":  2
    -    },
    -    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Sugar" },
    -      "Total@type": "Decimal", "Total":  2
    -    },
    -    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
    -      "Total@type": "Decimal", "Total": 12
    -    },
    -    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Paper" },
    -      "Total@type": "Decimal", "Total":  5
    -    }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),Product(Name),Total)",
    +  "value": [
    +    { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Paper" },
    +      "Total@type": "Decimal", "Total":  3
    +    },
    +    { "Customer":{ "Country": "Netherlands" }, "Product":{ "Name": "Sugar" },
    +      "Total@type": "Decimal", "Total":  2
    +    },
    +    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Sugar" },
    +      "Total@type": "Decimal", "Total":  2
    +    },
    +    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Coffee" },
    +      "Total@type": "Decimal", "Total": 12
    +    },
    +    { "Customer":{ "Country": "USA" },         "Product":{ "Name": "Paper" },
    +      "Total@type": "Decimal", "Total":  5
    +    }
    +  ]
    +}
    -

    Example 98: concatenation +

    Example 99: concatenation of two different groupings "biggest sale per customer" and "biggest sale per product", made distinguishable by a dynamic property:

    GET /service/Sales?$apply=concat(
    @@ -5909,45 +5924,45 @@ 

    7.4 UI consuming the response presents the two groupings in separate columns based on the per property, no contradiction effectively arises.

    -
    {
    -  "@context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
    -  "value": [
    -    { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
    -      "ID": "3", "Amount": 4, "per": "Customer" },
    -    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    -      "ID": "4", "Amount": 8, "per": "Customer" },
    -    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    -      "ID": "6", "Amount": 2, "per": "Customer" },
    -    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    -      "ID": "6", "Amount": 2, "per": "Product" },
    -    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    -      "ID": "4", "Amount": 8, "per": "Product" },
    -    { "Customer": { "ID": "C2" }, "Product": { "ID": "P3" },
    -      "ID": "5", "Amount": 4, "per": "Product" }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(*,per,Customer(ID),Product(ID))",
    +  "value": [
    +    { "Customer": { "ID": "C1" }, "Product": { "ID": "P2" },
    +      "ID": "3", "Amount": 4, "per": "Customer" },
    +    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    +      "ID": "4", "Amount": 8, "per": "Customer" },
    +    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    +      "ID": "6", "Amount": 2, "per": "Customer" },
    +    { "Customer": { "ID": "C3" }, "Product": { "ID": "P1" },
    +      "ID": "6", "Amount": 2, "per": "Product" },
    +    { "Customer": { "ID": "C2" }, "Product": { "ID": "P2" },
    +      "ID": "4", "Amount": 8, "per": "Product" },
    +    { "Customer": { "ID": "C2" }, "Product": { "ID": "P3" },
    +      "ID": "5", "Amount": 4, "per": "Product" }
    +  ]
    +}

    7.6 Model Functions as Set Transformations

    -

    Example 99: As a variation of example 96, +

    Example 100: As a variation of example 97, a query for returning the best-selling product per country and the total amount of the remaining products can be formulated with the help of a model function.

    For this purpose, the model includes a definition of a TopCountAndRemainder function that accepts a count and a numeric property for the top entities:

    -
    <edm:Function Name="TopCountAndRemainder"
    -              IsBound="true">
    -    <edm:Parameter  Name="EntityCollection"
    -                    Type="Collection(Edm.EntityType)" />
    -    <edm:Parameter  Name="Count" Type="Edm.Int16" />
    -    <edm:Parameter  Name="Property" Type="Edm.String" />
    -    <edm:ReturnType Type="Collection(Edm.EntityType)" />
    -</edm:Function>
    +
    <edm:Function Name="TopCountAndRemainder"
    +              IsBound="true">
    +    <edm:Parameter  Name="EntityCollection"
    +                    Type="Collection(Edm.EntityType)" />
    +    <edm:Parameter  Name="Count" Type="Edm.Int16" />
    +    <edm:Parameter  Name="Property" Type="Edm.String" />
    +    <edm:ReturnType Type="Collection(Edm.EntityType)" />
    +</edm:Function>

    The function retains those entities that topcount also would retain, and replaces the remaining entities by a single aggregated entity, where only the numeric property has a value, which is the sum @@ -5958,22 +5973,22 @@

    7.4 /groupby((Customer/Country), Self.TopCountAndRemainder(Count=1,Property='Total'))

    results in

    -
    {
    -  "@context": "$metadata#Sales(Customer(Country),Total)",
    -  "value": [
    -    { "Customer": { "Country": "Netherlands" },
    -      "Product": { "Name": "Paper" },
    -      "Total@type": "Decimal", "Total":  3 },
    -    { "Customer": { "Country": "Netherlands" },
    -      "Total@type": "Decimal", "Total":  2 },
    -    { "Customer": { "Country": "USA" },
    -      "Product": { "Name": "Coffee" },
    -      "Total@type": "Decimal", "Total": 12 },
    -    { "Customer": { "Country": "USA" },
    -      "Total@type": "Decimal", "Total":  7 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Customer(Country),Total)",
    +  "value": [
    +    { "Customer": { "Country": "Netherlands" },
    +      "Product": { "Name": "Paper" },
    +      "Total@type": "Decimal", "Total":  3 },
    +    { "Customer": { "Country": "Netherlands" },
    +      "Total@type": "Decimal", "Total":  2 },
    +    { "Customer": { "Country": "USA" },
    +      "Product": { "Name": "Coffee" },
    +      "Total@type": "Decimal", "Total": 12 },
    +    { "Customer": { "Country": "USA" },
    +      "Total@type": "Decimal", "Total":  7 }
    +  ]
    +}

    Note that these two entities get their values for the Country property from the groupby transformation, which ensures that they contain all grouping properties with the correct values.

    @@ -5986,7 +6001,7 @@

    7.4 href="#Groupingwithrollup">rollup as a hierarchy level below the root level.

    -

    Example 100: get the average of the overall amount by month per +

    Example 101: get the average of the overall amount by month per product.

    Using a transformation sequence:

    GET /service/Sales?$apply=groupby((Product/ID,Product/Name,Time/Month),
    @@ -6000,7 +6015,7 @@ 

    7.4 as MonthlyAverage))

    -

    Example 101: get the total amount per customer, the average of the +

    Example 102: get the total amount per customer, the average of the total customer amounts per country, and the overall average of these averages

    GET /service/Sales?$apply=concat(
    @@ -6013,29 +6028,29 @@ 

    7.4 from Customer/Country with average as CustomerCountryAverage)))

    results in

    -
    {
    -  "@context": "$metadata#Sales(CustomerCountryAverage)",
    -  "value": [
    -    { "Customer": { "Country": "USA", "ID": "C1" },
    -      "CustomerCountryAverage@type":"Decimal",
    -      "CustomerCountryAverage":   7 },
    -    { "Customer": { "Country": "USA", "ID": "C2" },
    -      "CustomerCountryAverage@type":"Decimal",
    -      "CustomerCountryAverage":  12 },
    -    { "Customer": { "Country": "USA" },
    -      "CustomerCountryAverage@type":"Decimal",
    -      "CustomerCountryAverage": 9.5 },
    -    { "Customer": { "Country": "Netherlands", "ID": "C3" },
    -      "CustomerCountryAverage@type":"Decimal",
    -      "CustomerCountryAverage": 5 },
    -    { "Customer": { "Country": "Netherlands" },
    -      "CustomerCountryAverage@type":"Decimal",
    -      "CustomerCountryAverage": 5 },
    -    { "CustomerCountryAverage@type":"Decimal",
    -      "CustomerCountryAverage": 7.25 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(CustomerCountryAverage)",
    +  "value": [
    +    { "Customer": { "Country": "USA", "ID": "C1" },
    +      "CustomerCountryAverage@type":"Decimal",
    +      "CustomerCountryAverage":   7 },
    +    { "Customer": { "Country": "USA", "ID": "C2" },
    +      "CustomerCountryAverage@type":"Decimal",
    +      "CustomerCountryAverage":  12 },
    +    { "Customer": { "Country": "USA" },
    +      "CustomerCountryAverage@type":"Decimal",
    +      "CustomerCountryAverage": 9.5 },
    +    { "Customer": { "Country": "Netherlands", "ID": "C3" },
    +      "CustomerCountryAverage@type":"Decimal",
    +      "CustomerCountryAverage": 5 },
    +    { "Customer": { "Country": "Netherlands" },
    +      "CustomerCountryAverage@type":"Decimal",
    +      "CustomerCountryAverage": 5 },
    +    { "CustomerCountryAverage@type":"Decimal",
    +      "CustomerCountryAverage": 7.25 }
    +  ]
    +}

    Note that this example extends the result of rollup with concat and aggregate to append the overall average.

    @@ -6047,7 +6062,7 @@

    7.4 entire hierarchy, transformations ancestors and descendants may be used to restrict it as needed.

    -

    Example 102: Total sales amounts for sales orgs in 'US' in the +

    Example 103: Total sales amounts for sales orgs in 'US' in the SalesOrgHierarchy defined in Hierarchy Examples

    GET /service/Sales?$apply=
    @@ -6059,21 +6074,21 @@ 

    7.4 aggregate(Amount with sum as TotalAmount)) &$expand=SalesOrganization($expand=Superordinate/$ref)

    results in

    -
    {
    -  "@context": "$metadata#Sales(TotalAmount,SalesOrganization())",
    -  "value": [
    -    { "TotalAmount@type": "Decimal", "TotalAmount": 19,
    -      "SalesOrganization": { "ID": "US",      "Name": "US",
    -        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
    -    { "TotalAmount@type": "Decimal", "TotalAmount": 12,
    -      "SalesOrganization": { "ID": "US East", "Name": "US East",
    -        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
    -    { "TotalAmount@type": "Decimal", "TotalAmount":  7,
    -      "SalesOrganization": { "ID": "US West", "Name": "US West",
    -        "Superordinate": { "@id": "SalesOrganizations('US')" } } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(TotalAmount,SalesOrganization())",
    +  "value": [
    +    { "TotalAmount@type": "Decimal", "TotalAmount": 19,
    +      "SalesOrganization": { "ID": "US",      "Name": "US",
    +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
    +    { "TotalAmount@type": "Decimal", "TotalAmount": 12,
    +      "SalesOrganization": { "ID": "US East", "Name": "US East",
    +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
    +    { "TotalAmount@type": "Decimal", "TotalAmount":  7,
    +      "SalesOrganization": { "ID": "US West", "Name": "US West",
    +        "Superordinate": { "@id": "SalesOrganizations('US')" } } }
    +  ]
    +}

    Note that this example returns the actual total sums regardless of whether the descendants transformation comes before or after the groupby with rolluprecursive.

    @@ -6082,7 +6097,7 @@

    7.4 with rolluprecursive shall aggregate over a thinned-out hierarchy, like here:

    -

    Example 103: Number of Paper sales per sales org aggregated along the +

    Example 104: Number of Paper sales per sales org aggregated along the the SalesOrgHierarchy defined in Hierarchy Examples

    GET /service/Sales?$apply=
    @@ -6092,33 +6107,33 @@ 

    7.4 aggregate($count as PaperSalesCount)) &$expand=SalesOrganization($expand=Superordinate/$ref)

    results in

    -
    {
    -  "@context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
    -  "value": [
    -    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
    -      "SalesOrganization": { "ID": "US",           "Name": "US",
    -        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
    -    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
    -      "SalesOrganization": { "ID": "US East",      "Name": "US East",
    -        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
    -    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
    -      "SalesOrganization": { "ID": "US West",      "Name": "US West",
    -        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
    -    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
    -      "SalesOrganization": { "ID": "EMEA",         "Name": "EMEA",
    -        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
    -    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
    -      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central",
    -        "Superordinate": { "@id": "SalesOrganizations('EMEA')" } } },
    -    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 4,
    -      "SalesOrganization": { "ID": "Sales",        "Name": "Sales",
    -        "Superordinate": null } }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(PaperSalesCount,SalesOrganization())",
    +  "value": [
    +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
    +      "SalesOrganization": { "ID": "US",           "Name": "US",
    +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
    +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
    +      "SalesOrganization": { "ID": "US East",      "Name": "US East",
    +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
    +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 1,
    +      "SalesOrganization": { "ID": "US West",      "Name": "US West",
    +        "Superordinate": { "@id": "SalesOrganizations('US')" } } },
    +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
    +      "SalesOrganization": { "ID": "EMEA",         "Name": "EMEA",
    +        "Superordinate": { "@id": "SalesOrganizations('Sales')" } } },
    +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 2,
    +      "SalesOrganization": { "ID": "EMEA Central", "Name": "EMEA Central",
    +        "Superordinate": { "@id": "SalesOrganizations('EMEA')" } } },
    +    { "PaperSalesCount@type": "Decimal", "PaperSalesCount": 4,
    +      "SalesOrganization": { "ID": "Sales",        "Name": "Sales",
    +        "Superordinate": null } }
    +  ]
    +}
    -

    ⚠ Example 104: The input set Sales is filtered along a +

    ⚠ Example 105: The input set Sales is filtered along a hierarchy on a related entity (navigation property SalesOrganization) before an aggregation

    GET /service/Sales?$apply=
    @@ -6142,7 +6157,7 @@ 

    7.4 /aggregate(Sales/Amount with sum as TotalAmount)

    -

    Example 106: Return the result of example -62 in preorder

    +

    Example 107: Return the result of example +63 in preorder

    GET /service/Sales?$apply=groupby(
         (rolluprecursive(
           $root/SalesOrganizations,
    @@ -6198,25 +6213,25 @@ 

    7.4 preorder, Name asc)

    results in

    -
    {
    -  "@context": "$metadata#Sales(SalesOrganization(ID),
    -                               TotalAmountIncl,TotalAmountExcl)",
    -  "value": [
    -    { "SalesOrganization": { "ID": "US",      "Name": "US" },
    -      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
    -      "TotalAmountExcl": null },
    -    { "SalesOrganization": { "ID": "US East", "Name": "US East" },
    -      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
    -      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 },
    -    { "SalesOrganization": { "ID": "US West", "Name": "US West" },
    -      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
    -      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(SalesOrganization(ID),
    +                               TotalAmountIncl,TotalAmountExcl)",
    +  "value": [
    +    { "SalesOrganization": { "ID": "US",      "Name": "US" },
    +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 19,
    +      "TotalAmountExcl": null },
    +    { "SalesOrganization": { "ID": "US East", "Name": "US East" },
    +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl": 12,
    +      "TotalAmountExcl@type": "Decimal", "TotalAmountExcl": 12 },
    +    { "SalesOrganization": { "ID": "US West", "Name": "US West" },
    +      "TotalAmountIncl@type": "Decimal", "TotalAmountIncl":  7,
    +      "TotalAmountExcl@type": "Decimal" ,"TotalAmountExcl":  7 }
    +  ]
    +}
    -

    Example 107: Preorder +

    Example 108: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment \(p_1={\tt Sales}\) and \(r={\tt SalesOrganization}/{\tt ID}\).

    @@ -6233,33 +6248,33 @@

    7.4 The node \(x\) with \(x/{\tt ID}={}\)"US" has \(σ(x)={}\){"Sales": [{"SalesOrganization": {"ID": "US"}}]}.

    -
    {
    -  "@context":
    -      "$metadata#Products(ID,Sales(SalesOrganization(ID)))",
    -  "value": [
    -    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
    -    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
    -    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
    -    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ] },
    -    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ] },
    -    { "ID": "P1",
    -      "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ] },
    -    { "ID": "P3",
    -      "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ] },
    -    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "US" } } ] },
    -    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "US" } } ] },
    -    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "US" } } ] },
    -    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "US East" } } ] },
    -    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "US East" } } ] },
    -    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "US West" } } ] },
    -    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "US West" } } ] },
    -    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "US West" } } ] }
    -  ]
    -}
    +
    {
    +  "@context":
    +      "$metadata#Products(ID,Sales(SalesOrganization(ID)))",
    +  "value": [
    +    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
    +    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
    +    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ] },
    +    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ] },
    +    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ] },
    +    { "ID": "P1",
    +      "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ] },
    +    { "ID": "P3",
    +      "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ] },
    +    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "US" } } ] },
    +    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "US" } } ] },
    +    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "US" } } ] },
    +    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "US East" } } ] },
    +    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "US East" } } ] },
    +    { "ID": "P1", "Sales": [ { "SalesOrganization": { "ID": "US West" } } ] },
    +    { "ID": "P2", "Sales": [ { "SalesOrganization": { "ID": "US West" } } ] },
    +    { "ID": "P3", "Sales": [ { "SalesOrganization": { "ID": "US West" } } ] }
    +  ]
    +}

    -

    Example 108: Aggregation +

    Example 109: Aggregation along a hierarchy with 1:N relationship: Sold products per sales organization

    GET /service/Products?$apply=
    @@ -6269,27 +6284,27 @@ 

    7.4 Sales/SalesOrganization/ID)), aggregate(ID with Custom.concat as SoldProducts)

    results in

    -
    {
    -  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),SoldProducts)",
    -  "value": [
    -    { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
    -      "SoldProducts": "P1,P2,P3" },
    -    { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ],
    -      "SoldProducts": "P1,P3" },
    -    { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ],
    -      "SoldProducts": "P1,P3" },
    -    { "Sales": [ { "SalesOrganization": { "ID": "US" } } ],
    -      "SoldProducts": "P1,P2,P3" },
    -    { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ],
    -      "SoldProducts": "P2,P3" },
    -    { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ],
    -      "SoldProducts": "P1,P2,P3" }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Products(Sales(SalesOrganization(ID)),SoldProducts)",
    +  "value": [
    +    { "Sales": [ { "SalesOrganization": { "ID": "Sales" } } ],
    +      "SoldProducts": "P1,P2,P3" },
    +    { "Sales": [ { "SalesOrganization": { "ID": "EMEA" } } ],
    +      "SoldProducts": "P1,P3" },
    +    { "Sales": [ { "SalesOrganization": { "ID": "EMEA Central" } } ],
    +      "SoldProducts": "P1,P3" },
    +    { "Sales": [ { "SalesOrganization": { "ID": "US" } } ],
    +      "SoldProducts": "P1,P2,P3" },
    +    { "Sales": [ { "SalesOrganization": { "ID": "US East" } } ],
    +      "SoldProducts": "P2,P3" },
    +    { "Sales": [ { "SalesOrganization": { "ID": "US West" } } ],
    +      "SoldProducts": "P1,P2,P3" }
    +  ]
    +}
    -

    ⚠ Example 109: Assume an extension of the data model where a +

    ⚠ Example 110: Assume an extension of the data model where a SalesOrganization is associated with one or more instances of ProductCategory, and ProductCategory also organizes categories in a recursive hierarchy:

    @@ -6342,21 +6357,21 @@

    7.4 aggregate(Amount with sum as TotalAmount)) &$expand=SalesOrganization($select=ID,$expand=ProductCategories/$ref)

    results in

    -
    {
    -  "@context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
    -  "value": [
    -    { "SalesOrganization": { "ID": "Sales",   "ProductCategories": [ ] },
    -      "TotalAmount@type": "Decimal", "TotalAmount": 24 },
    -    { "SalesOrganization": { "ID": "US",      "ProductCategories": [
    -      { "@id": "ProductCategories('Food')" },
    -      { "@id": "ProductCategories('Cereals')" } ] },
    -      "TotalAmount@type": "Decimal", "TotalAmount": 19 },
    -    { "SalesOrganization": { "ID": "US West", "ProductCategories": [
    -      { "@id": "ProductCategories('Organic cereals')" } ] },
    -      "TotalAmount@type": "Decimal", "TotalAmount":  7 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(SalesOrganization(ID),TotalAmount)",
    +  "value": [
    +    { "SalesOrganization": { "ID": "Sales",   "ProductCategories": [ ] },
    +      "TotalAmount@type": "Decimal", "TotalAmount": 24 },
    +    { "SalesOrganization": { "ID": "US",      "ProductCategories": [
    +      { "@id": "ProductCategories('Food')" },
    +      { "@id": "ProductCategories('Cereals')" } ] },
    +      "TotalAmount@type": "Decimal", "TotalAmount": 19 },
    +    { "SalesOrganization": { "ID": "US West", "ProductCategories": [
    +      { "@id": "ProductCategories('Organic cereals')" } ] },
    +      "TotalAmount@type": "Decimal", "TotalAmount":  7 }
    +  ]
    +}

    traverse acts here as a filter, hence preorder could be changed to postorder without changing the result. If traverse was omitted, the @@ -6384,39 +6399,39 @@

    7.4 be specified in $apply in the order they are to be applied, separated by a forward slash.

    -

    Example 110:

    +

    Example 111:

    GET /service/Sales?$apply=filter(Amount le 1)
         /aggregate(Amount with sum as Total)

    means "filter first, then aggregate", and results in

    -
    {
    -  "@context": "$metadata#Sales(Total)",
    -  "value": [
    -    { "Total@type": "Decimal", "Total": 2 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Total)",
    +  "value": [
    +    { "Total@type": "Decimal", "Total": 2 }
    +  ]
    +}

    Using filter within $apply does not preclude using it as a normal system query option.

    -

    Example 111:

    +

    Example 112:

    GET /service/Sales?$apply=filter(Amount le 2)/groupby((Product/Name),
                                              aggregate(Amount with sum as Total))
                &$filter=Total ge 4

    results in

    -
    {
    -  "@context": "$metadata#Sales(Product(Name),Total)",
    -  "value": [
    -    { "Product": { "Name": "Paper" },
    -      "Total@type": "Decimal", "Total": 4 },
    -    { "Product": { "Name": "Sugar" },
    -      "Total@type": "Decimal", "Total": 4 }
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Sales(Product(Name),Total)",
    +  "value": [
    +    { "Product": { "Name": "Paper" },
    +      "Total@type": "Decimal", "Total": 4 },
    +    { "Product": { "Name": "Sugar" },
    +      "Total@type": "Decimal", "Total": 4 }
    +  ]
    +}
    -

    Example 112: Revisiting example 16 for using the +

    Example 113: Revisiting example 16 for using the from keyword with the aggregate function, the request

    GET /service/Sales?$apply=aggregate(Amount from Time with average
    @@ -6430,31 +6445,31 @@ 

    7.4 sets for cities, countries and continents and the obvious associations between them.

    -

    Example 113: getting the population per country with

    +

    Example 114: getting the population per country with

    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))

    results in

    -
    {
    -  "@context": "$metadata#Cities(Continent(Name),Country(Name),
    -                                TotalPopulation)",
    -  "value": [
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    -      "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    -      "TotalPopulation@type": "Int32", "TotalPopulation": 1408000000 },
    -    ...
    -  ]
    -}
    +
    {
    +  "@context": "$metadata#Cities(Continent(Name),Country(Name),
    +                                TotalPopulation)",
    +  "value": [
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    +      "TotalPopulation@type": "Int32", "TotalPopulation": 1412000000 },
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    +      "TotalPopulation@type": "Int32", "TotalPopulation": 1408000000 },
    +    ...
    +  ]
    +}
    -

    Example 114: all countries with megacities and their continents

    +

    Example 115: all countries with megacities and their continents

    GET /service/Cities?$apply=filter(Population ge 10000000)
                        /groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))
    -

    Example 115: all countries with tens of millions of city dwellers and +

    Example 116: all countries with tens of millions of city dwellers and the continents only for these countries

    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                               aggregate(Population with sum as CountryPopulation))
    @@ -6472,7 +6487,7 @@ 

    7.4 as TotalPopulation))

    -

    Example 119: The +

    Example 120: The output set of the concat transformation contains Sales entities multiple times with conflicting related AugmentedProduct entities that cannot be aggregated by the @@ -6512,28 +6527,28 @@

    7.4

    results in an error.

    -

    Example 120: The nest transformation can be used inside +

    Example 121: The nest transformation can be used inside groupby to produce one or more collection-valued properties per group.

    GET /service/Sales?$apply=groupby((Product/Category/ID),
                           nest(groupby((Customer/ID)) as Customers))

    results in

    -
    {
    -  "@context":"$metadata#Sales(Product(Category(ID)),Customers())",
    -  "value": [
    -    { "Product": { "Category": { "ID": "PG1" } },
    -      "Customers@context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] },
    -    { "Product": { "Category": { "ID": "PG2" } },
    -      "Customers@context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] }
    -  ]
    -}
    +
    {
    +  "@context":"$metadata#Sales(Product(Category(ID)),Customers())",
    +  "value": [
    +    { "Product": { "Category": { "ID": "PG1" } },
    +      "Customers@context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] },
    +    { "Product": { "Category": { "ID": "PG2" } },
    +      "Customers@context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] }
    +  ]
    +}

    8 @@ -6563,51 +6578,34 @@

    8
    [OData-ABNF]

    ABNF components: OData ABNF Construction Rules Version 4.01 and OData ABNF Test Cases.
    -See the link in "Related work" section on -cover page.

    +See link in "Related work" section on cover +page.

    [OData-Agg-ABNF]

    OData Aggregation ABNF Construction Rules Version 4.0.
    See link in "Additional artifacts" section on cover page.

    [OData-CSDL]

    OData Common Schema Definition Language (CSDL) JSON -Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and -Martin Zurmuehl. 11 May 2020. OASIS Standard.
    -https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/os/odata-csdl-json-v4.01-os.html.
    -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html.
    -OData Common Schema Definition Language (CSDL) XML Representation -Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. -11 May 2020. OASIS Standard.
    -https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/os/odata-csdl-xml-v4.01-os.html.
    -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html.

    +Representation Version 4.01.
    +See link in "Related work" section on cover +page.

    +

    OData Common Schema Definition Language (CSDL) XML Representation +Version 4.01.
    +See link in "Related work" section on cover +page.

    [OData-JSON]
    -

    OData JSON Format Version 4.01. Edited by Michael Pizzo, Ralf -Handl, and Mark Biamonte. 11 May 2020. OASIS Standard.
    -https://docs.oasis-open.org/odata/odata-json-format/v4.01/os/odata-json-format-v4.01-os.html.
    -Latest stage: https://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html.

    +

    OData JSON Format Version 4.01.
    +See link in "Related work" section on cover +page.

    [OData-Protocol]
    -

    OData Version 4.01. Part 1: Protocol. Edited by Michael Pizzo, -Ralf Handl, and Martin Zurmuehl. 23 April 2020. OASIS -Standard.
    -https://docs.oasis-open.org/odata/odata/v4.01/os/part1-protocol/odata-v4.01-os-part1-protocol.html.
    -Latest stage: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html.

    +

    OData Version 4.01. Part 1: Protocol.
    +See link in "Related work" section on cover +page.

    [OData-URL]
    -

    OData Version 4.01. Part 2: URL Conventions. Edited by Michael -Pizzo, Ralf Handl, and Martin Zurmuehl. 23 April 2020. OASIS -Standard.
    -https://docs.oasis-open.org/odata/odata/v4.01/os/part2-url-conventions/odata-v4.01-os-part2-url-conventions.html.
    -Latest stage: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html.

    +

    OData Version 4.01. Part 2: URL Conventions.
    +See link in "Related work" section on cover +page.

    [OData-VocAggr]

    OData Aggregation Vocabulary.
    See link in "Additional artifacts" diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index e8f828e75..0f690c243 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -206,6 +206,7 @@ This specification adds aggregation functionality to the Open Data Protocol (ODa This specification defines the following terms: - _Aggregatable Expression_ – an [expression](#Expression) resulting in a value of an [aggregatable primitive type](#AggregatablePrimitiveType) +- _Aggregate Expression_ – argument of the `aggregate` [transformation](#Transformationaggregate) or [function](#Functionaggregate) defined in [section 3.2.1.1](#AggregationAlgorithm) - _Aggregatable Primitive Type_ – a primitive type other than `Edm.Stream` or subtypes of `Edm.Geography` or `Edm.Geometry` - _Data Aggregation Path_ – a path that consists of one or more segments joined together by forward slashes (`/`). Segments are names of declared or dynamic structural or navigation properties, or type-cast segments consisting of the (optionally qualified) name of a structured type that is derived from the type identified by the preceding path segment to reach properties declared by the derived type. - _Expression_ – derived from the `commonExpr` rule (see [OData-ABNF](#ODataABNF)) @@ -220,7 +221,7 @@ The following non-exhaustive list contains variable names that are used througho - $x$ – an instance in a hierarchical collection, called a node - $p,q,r$ – paths - $S,T$ – transformation sequences -- $α$ – aggregate expression, defined [below](#AggregationAlgorithm) +- $α$ – aggregate expression, defined in [section 3.2.1.1](#AggregationAlgorithm) - $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths) - $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths) - $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined [below](#SimpleGrouping) @@ -1022,7 +1023,7 @@ The definitions of italicized terms made in this section are used throughout thi ### 3.1.1 Type, Structure and Context URL -All input sets and output sets in one transformation sequence are collections of the _input type_, that is the entity type or complex type of the first input set, or in other words, of the resource to which the transformation sequence is applied. The input type is determined by the entity model element identified within the metadata document by the context URL of that resource [OData-Protocol, section 10](#ODataProtocol). Individual instances in an input or output set can have a subtype of the input type. (See [example 69](#subinputtype).) The transformation sequence given as the `$apply` system query option is applied to the resource addressed by the resource path. The transformations defined below can have nested transformation sequences as parameters, these are then applied to resources that can differ from the current input set. +All input sets and output sets in one transformation sequence are collections of the _input type_, that is the entity type or complex type of the first input set, or in other words, of the resource to which the transformation sequence is applied. The input type is determined by the entity model element identified within the metadata document by the context URL of that resource [OData-Protocol, section 10](#ODataProtocol). Individual structured instances in an input or output set can have a subtype of the input type. (See [example 70](#subinputtype).) The transformation sequence given as the `$apply` system query option is applied to the resource addressed by the resource path. The transformations defined below can have nested transformation sequences as parameters, these are then applied to resources that can differ from the current input set. The _structure_ of an instance that occurs in an input or output set is defined by the names of the structural and navigation properties that the instance contains. Instances of an input type can have different structures, subject to the following rules: - Declared properties of the input type or a nested or related type thereof or of a subtype of one of these MUST have their declared type and meaning when they occur in an input or output set. @@ -1039,11 +1040,11 @@ Here is an overview of the structural changes made by different transformations: An output set thus consists of instances with different structures, this is the same situation as with a collection of an open type [OData-CSDL, sections 6.3 and 9.3](#ODataCSDL) and it is handled in the same way. -If the first input set is a collection of entities from a given entity set, then so are all input sets and output sets in the transformation sequence. The `{select-list}` in the context URL [OData-Protocol, section 10](#ODataProtocol) MUST describe only properties that are present or annotated as absent (for example, if `Core.Permissions` is `None` [OData-Protocol, section 11.2.2](#ODataProtocol)) in all instances of the collection, after applying any `$select` and `$expand` system query options. The `{select-list}` SHOULD describe as many such properties as possible, even if the request involves a concatenation that leads to a non-homogeneous structure. If the server cannot determine any such properties, the `{select-list}` MUST consist of just the instance annotation `AnyStructure` defined in the `Core` vocabulary [OData-VocCore](#ODataVocCore). (See [example 70](#anystructure).) +If the first input set is a collection of entities from a given entity set, then so are all input sets and output sets in the transformation sequence. The `{select-list}` in the context URL [OData-Protocol, section 10](#ODataProtocol) MUST describe only properties that are present or annotated as absent (for example, if `Core.Permissions` is `None` [OData-Protocol, section 11.2.2](#ODataProtocol)) in all instances of the collection, after applying any `$select` and `$expand` system query options. The `{select-list}` SHOULD describe as many such properties as possible, even if the request involves a concatenation that leads to a non-homogeneous structure. If the server cannot determine any such properties, the `{select-list}` MUST consist of just the instance annotation `AnyStructure` defined in the `Core` vocabulary [OData-VocCore](#ODataVocCore). (See [example 71](#anystructure).) ### 3.1.2 Sameness and Precedence -Input sets and output sets are not sets of instances in the mathematical sense but collections, because the same instance can occur multiple times in them. In other words: A collection contains values (which can be instances or primitive values), possibly with repetitions. The occurrences in the collection form a set in the mathematical sense. The _cardinality_ of a collection is the total number of occurrences in it. When this text describes a transformation algorithmically and stipulates that certain steps are carried out _for each_ instance in a collection, this means that the steps are carried out multiple times for the same instance if it occurs multiple times in the collection. +Input sets and output sets are not sets of instances in the mathematical sense but collections, because the same instance can occur multiple times in them. In other words: A collection contains values (which can be structured instances or primitive values), possibly with repetitions. The occurrences in the collection form a set in the mathematical sense. The _cardinality_ of a collection is the total number of occurrences in it. When this text describes a transformation algorithmically and stipulates that certain steps are carried out _for each_ instance in a collection, this means that the steps are carried out multiple times for the same instance if it occurs multiple times in the collection. A collection addressed by the resource path is returned by the service either as an ordered collection [OData-Protocol, section 11.4.10](#ODataProtocol) or as an unordered collection. The same applies to collections that are nested in or related to the addressed resource as well as to collections that are the result of evaluating an expression starting with `$root`, which occur, for example, as the first parameter of a [hierarchical transformation](#HierarchicalTransformations). @@ -1068,12 +1069,12 @@ The transformation [`orderby`](#Transformationorderby) defined below carries out A stable-sort does not necessarily produce a total order, the sorted collection may still contain two occurrences whose relative order does not matter. -The output set of a [basic aggregation](#BasicAggregation) transformation can contain instances of an entity type without entity-id. After a [`concat`](#Transformationconcat) transformation, different occurrences of the same entity can differ in individual non-declared properties. To account for such cases, the definition of sameness given in [OData-URL, section 5.1.1.1.1](#ODataURL) is refined here. Instances are _the same_ if +The output set of a [basic aggregation](#BasicAggregation) transformation can contain instances of an entity type without entity id. After a [`concat`](#Transformationconcat) transformation, different occurrences of the same entity can differ in individual non-declared properties. To account for such cases, the definition of sameness given in [OData-URL, section 5.1.1.1.1](#ODataURL) is refined here. Instances are _the same_ if - both are instances of complex types and both are null or both have the same structure and same values with null considered different from absent or -- both are instances of entity types without entity-id (transient entities, see [OData-Protocol, section 4.3](#ODataProtocol)) and both are null or both have the same structure and same values with null considered different from absent (informally speaking, they are compared like complex instances) or -- (1) both are instances of the same entity type with the same entity-id (non-transient entities, see [OData-Protocol, section 4.1](#ODataProtocol)) and (2) the structural and navigation properties contained in both have the same values (for non-primitive properties the sameness of values is decided by a recursive invocation of this definition). +- both are instances of entity types without entity id (transient entities, see [OData-Protocol, section 4.3](#ODataProtocol)) and both are null or both have the same structure and same values with null considered different from absent (informally speaking, they are compared like complex instances) or +- (1) both are instances of the same entity type with the same entity id (non-transient entities, see [OData-Protocol, section 4.1](#ODataProtocol)) and (2) the structural and navigation properties contained in both have the same values (for non-primitive properties the sameness of values is decided by a recursive invocation of this definition). - If this is fulfilled, the instances are called _complementary representations of the same non-transient entity_. If this case is encountered at some recursion level while the sameness of non-transient entities $u_1$ and $u_2$ is established, a merged representation of the entity $u_1=u_2$ exists that contains all properties of $u_1$ and $u_2$. But if the instances both occur in the last output set, services MUST represent each with its own structure in the response payload. - - If the first condition is fulfilled but not the second, the instances are not the same and are called _contradictory representations of the same non-transient entity_. ([Example 98](#contradict) describes a use case for this.) + - If the first condition is fulfilled but not the second, the instances are not the same and are called _contradictory representations of the same non-transient entity_. ([Example 99](#contradict) describes a use case for this.) Collections are _the same_ if there is a one-to-one correspondence $f$ between them such that - corresponding occurrences are of the same value and @@ -1083,7 +1084,7 @@ Collections are _the same_ if there is a one-to-one correspondence $f$ between t This document specifies how a [data aggregation path](#DataAggregationPath) that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request. -For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. +For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to a structured instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and returns a collection of structured instances or primitive values, depending on the type of the final segment of $p$. It is recursively defined as follows: 1. If $p$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9. @@ -1104,11 +1105,11 @@ This notation is extended to the case of an empty path $e$ by setting $\Gamma(A, #### 3.2.1.1 Aggregation Algorithm -The `aggregate` transformation takes a comma-separated list of one or more _aggregate expressions_ as parameters and returns an output set with a single instance of the [input type](#TypeStructureandContextURL) without entity-id containing one property per aggregate expression, representing the aggregated value of the input set. +The `aggregate` transformation takes a comma-separated list of one or more [_aggregate expressions_](#AggregateExpression) as parameters and returns an output set with a single instance of the [input type](#TypeStructureandContextURL) without entity id containing one property per aggregate expression, representing the aggregated value of the input set. -An aggregate expression MUST have one of the types listed below. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $A$ of instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on the type of aggregate expression, $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(A)$ which the aggregate transformation evaluates to obtain the property value. +An aggregate expression MUST have one of the types listed below or be constructed with the [`from`](#Keywordfrom) keyword. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $A$ of structured instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on its type, the aggregate expression contains a path $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(A)$ which the aggregate transformation evaluates to obtain the property value. -The property is a dynamic property, except for a special case in type 4. In types 1 and 2, the aggregate expression MUST end with the keyword `with` and an aggregation method $g$. The aggregation method also determines the type of the dynamic property. In types 1, 2 and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword [`as`](#Keywordas) and an [alias](#TypeStructureandContextURL), which is then the name of the dynamic property. More aggregate expressions can be constructed with the [`from`](#Keywordfrom) keyword, see later section. +The property is a dynamic property, except for a special case in type 4. In types 1 and 2, the aggregate expression MUST end with the keyword `with` and an aggregation method $g$. The aggregation method also determines the type of the dynamic property. In types 1, 2, and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword [`as`](#Keywordas) and an [alias](#TypeStructureandContextURL), which is then the name of the dynamic property. _Types of aggregate expressions:_ 1. A path $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a complex or entity or [aggregatable primitive type](#AggregatablePrimitiveType) whose values can be aggregated using the specified [aggregation method](#AggregationMethods) $g$, or $p=p_2$ if the input set can be aggregated using the [custom aggregation method](#CustomAggregationMethods) $g$. @@ -1118,17 +1119,17 @@ Let $f(A)=g(B)$ where $B$ is the collection consisting of the aggregatable expre 3. A path $p/{\tt\$count}$ (see [section 3.2.1.4](#AggregateExpressioncount)) with optional prefix $p/{}$ where $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Let $f(A)$ be the [cardinality](#SamenessandPrecedence) of $A$. 4. A path $p/c$ consisting of an optional prefix $p/{}$ with $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a structured type or $p=p_2$, and a [custom aggregate](#CustomAggregates) $c$ defined on the collection addressed by $p$. -Let $f(A)=c(A)$, if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property. +Let $f(A)=c(A)$, and if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property. _Determination of $A$:_ Let $I$ be the input set. If $p$ is absent, let $A=I$ with null values removed. Otherwise, let $q$ be the portion of $p$ up to and including the last navigation property, if any, and any type-cast segment that immediately follows, and let $r$ be the remainder, if any, of $p$ that contains no navigation properties, such that $p$ equals the concatenated path $q⁄r$. The aggregate transformation considers each entity reached via the path $q$ exactly once. To this end, using the [$\Gamma$ notation](#EvaluationofDataAggregationPaths): -- If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example 119](#aggrconflict).) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$. +- If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example 120](#aggrconflict).) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$. - If $q$ is empty, let $E=I$. -Then, if $r$ is empty, let $A=E$, otherwise let $A=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions. +Then, if $r$ is empty, let $A=E$, otherwise let $A=\Gamma(E,r)$, this consists of structured instances or primitive values, possibly with repetitions. #### 3.2.1.2 Keyword `as` @@ -1221,7 +1222,7 @@ results in The standard aggregation method `max` can be applied to values with a totally ordered domain to return the largest of the values, or null if there are no values to be aggregated. -The result property will have the same type as the input property +The result property will have the same type as the input property. ::: example Example 11: @@ -1241,7 +1242,7 @@ results in ##### 3.2.1.3.4 Standard Aggregation Method `average` -The standard aggregation method `average` can be applied to numeric values to return the sum of the non-null values divided by the count of the values, or null if there are no values to be aggregated. +The standard aggregation method `average` can be applied to numeric values to return the sum of the values divided by the count of the values, or null if there are no values to be aggregated. The provider MUST choose a single type for the property across all instances of that type in the result that is capable of representing the aggregated values; either `Edm.Double` or `Edm.Decimal` with sufficient `Precision` and `Scale`. @@ -1339,18 +1340,18 @@ results in #### 3.2.1.5 Keyword `from` -The `from` keyword offers a shortcut for a sequence of [`groupby`](Transformationgroupby) and [`aggregate`](#Transformationaggregate) transformations with the pattern ${\tt groupby}(…,{\tt aggregate}(…{\tt\ as\ }A_1))/{\tt aggregate}(A_1{\tt\ with\ }…)$. +The `from` keyword offers a shortcut for a sequence of [`groupby`](Transformationgroupby) and [`aggregate`](#Transformationaggregate) transformations with the pattern ${\tt groupby}(…,{\tt aggregate}(…{\tt\ as\ }D_1))/{\tt aggregate}(D_1{\tt\ with\ }…)$. In the following $p_1,…,p_n$ are [data aggregation paths](#DataAggregationPath) that are allowed in `groupby` for [simple grouping](#SimpleGrouping). 1. If $α$ is an aggregate expression and $g$ is an aggregation method, then $$α{\tt\ from\ }p_1,…,p_n{\tt\ with\ }g$$ is an aggregate expression which evaluates to the value of property $D$ in the single instance in the output set of the following transformation sequence: - $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }A_1))/{\tt aggregate}(A_1{\tt\ with\ }g{\tt\ as\ }D).$$ + $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }D_1))/{\tt aggregate}(D_1{\tt\ with\ }g{\tt\ as\ }D).$$ 2. If $α=p/c{\tt\ from\ }…$ is an aggregate expression that starts with a custom aggregate $c$, optionally prefixed with a path $p$ as in type 4 in the [aggregation algorithm](#AggregationAlgorithm), and that optionally continues with `from` and `with` clauses, then $$α{\tt\ from\ }p_1,…,p_n$$ is an aggregate expression which evaluates to the value of property $c$ in the single instance in the output set of the following transformation sequence: - $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }A_1))/{\tt aggregate}(p/c).$$ + $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }D_1))/{\tt aggregate}(p/c).$$ Aggregate expressions constructed by these rules MUST be followed in the `aggregate` transformation by the keyword `as` and an [alias](#TypeStructureandContextURL). These rules can be applied repeatedly and lead to multiple `from` clauses in one aggregate expression. @@ -1389,6 +1390,23 @@ GET /service/Sales?$apply=groupby((Time),aggregate(Forecast)) ``` ::: +::: example +⚠ Example 18: the maximal daily average for sales of a product +``` +GET /service/Sales?$apply=aggregate(Amount with average from Time,Product/Name + with max as MaxDailyAverage) +``` +results in +```json +{ + "@context": "$metadata#Sales(MaxDailyAverage)", + "value": [ + { "MaxDailyAverage@type": "Decimal", "MaxDailyAverage": 8 } + ] +} +``` +::: + ### 3.2.2 Transformation `concat` The `concat` transformation takes two or more parameters, each of which is a sequence of set transformations. @@ -1396,7 +1414,7 @@ The `concat` transformation takes two or more parameters, each of which is a seq It applies each transformation sequence to the input set and concatenates the intermediate output sets in the order of the parameters into the output set, preserving the ordering of the individual output sets as well as the structure of each instance in these sets, potentially leading to a non-homogeneously structured output set. If different intermediate output sets contain dynamic properties with the same alias, clients SHOULD ensure they have the same type and meaning in each intermediate output set. ::: example -⚠ Example 18: +⚠ Example 19: ``` GET /service/Sales?$apply=concat(topcount(2,Amount), aggregate(Amount)) @@ -1419,17 +1437,17 @@ The output set of `concat` has a mixed form consisting of the structures imposed ### 3.2.3 Transformation `groupby` -The `groupby` transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the [input type](#TypeStructureandContextURL) without properties and without entity-id. +The `groupby` transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the [input type](#TypeStructureandContextURL) without properties and without entity id. #### 3.2.3.1 Simple Grouping -In its simplest form the first parameter of `groupby` specifies the _grouping properties_, a comma-separated parenthesized list $G$ of one or more [data aggregation paths](#DataAggregationPath) with single-valued segments. The same path SHOULD NOT appear more than once; redundant property paths MAY be considered valid, but MUST NOT alter the meaning of the request. Navigation properties and stream properties specified in grouping properties are expanded by default (see [example 67](#groupbynav)). +In its simplest form the first parameter of `groupby` specifies the _grouping properties_, a comma-separated parenthesized list $G$ of one or more [data aggregation paths](#DataAggregationPath) with single-valued segments. The same path SHOULD NOT appear more than once; redundant property paths MAY be considered valid, but MUST NOT alter the meaning of the request. Navigation properties and stream properties specified in grouping properties are expanded by default (see [example 68](#groupbynav)). The algorithmic description of this transformation makes use of the following definitions: Let $u[q]$ denote the value of a structural or navigation property $q$ in an instance $u$. A path $p_1$ is called a _prefix_ of a path $p$ if there is a non-empty path $p_2$ such that $p$ equals the concatenated path $p_1/p_2$. Let $e$ denote the empty path. The output set of the groupby transformation is constructed in five steps. 1. [For each](#SamenessandPrecedence) instance $u$ in the input set, a projection is computed that contains only the grouping properties. This projection is $s_G(u,e)$ and the function $s_G(u,p)$ takes an instance and a path relative to the input set as arguments and is computed recursively as follows: - - Let $v$ be an instance of the type of $u$ without properties and without entity-id. + - Let $v$ be an instance of the type of $u$ without properties and without entity id. - For each structural or navigation property $q$ of $u$: - If $u$ has a subtype of the type addressed by $p$ and $q$ is only declared on that subtype, let $p'=p/p''/q$ where $p''$ is a type-cast to the subtype, otherwise let $p'=p/q$. - If $p'$ occurs in $G$, let $v[q]=u[q]$. @@ -1454,7 +1472,7 @@ The output set of the transformation $\Pi_G(s)$ is in one-to-one correspondence 3. Return $u$. ::: example -Example 19: +Example 20: ``` GET /service/Sales?$apply=groupby((Customer/Country,Product/Name), aggregate(Amount with sum as Total)) @@ -1487,7 +1505,7 @@ results in If the second parameter is omitted, steps 2 and 3 above produce one instance containing only the grouping properties per distinct value combination. ::: example -⚠ Example 20: +⚠ Example 21: ``` GET /service/Sales?$apply=groupby((Product/Name,Amount)) ``` @@ -1529,7 +1547,7 @@ Such a grouping with `rollup` for a leveled hierarchy is processed using the fol $$\matrix{ {\tt concat}(\hfill&\tt (3)\\ \quad {\tt groupby}((P_1,p_1,p_2,P_2),T),\hfill\\ \quad {\tt groupby}((P_1,p_1,P_2),T)\hfill\\ ).\hskip25pc\\ }$$ ::: example -Example 21: rolling up two hierarchies, the first with two levels, the second with three levels: +Example 22: rolling up two hierarchies, the first with two levels, the second with three levels: $$({\tt rollup}(p_{1,1},p_{1,2}),{\tt rollup}(p_{2,1},p_{2,2},p_{2,3}))$$ will result in the six groupings $$\matrix{ (p_{1,1},p_{1,2},\hfill&p_{2,1},p_{2,2},p_{2,3})\hfill\\ (p_{1,1},p_{1,2},\hfill&p_{2,1},p_{2,2})\hfill\\ (p_{1,1},p_{1,2},\hfill&p_{2,1})\hfill\\ (p_{1,1},\hfill&p_{2,1},p_{2,2},p_{2,3})\hfill\\ (p_{1,1},\hfill&p_{2,1},p_{2,2})\hfill\\ (p_{1,1},\hfill&p_{2,1})\hfill }$$ @@ -1537,11 +1555,11 @@ The leveled hierarchy of the first rollup has 2 levels, the one of the second ha ::: ::: example -Example 22: answering the second question in [section 2.3](#ExampleUseCases) +Example 23: answering the second question in [section 2.3](#ExampleUseCases) ``` GET /service/Sales?$apply=groupby((rollup(Customer/Country,Customer/Name), - rollup(Product/Category/Name,Product/Name)), - aggregate(Amount with sum as Total)) + rollup(Product/Category/Name,Product/Name)), + aggregate(Amount with sum as Total)) ``` results in seven entities for the finest grouping level ```json @@ -1590,7 +1608,7 @@ Another grouping operator [`rolluprecursive`](#Groupingwithrolluprecursive) whic These transformations produce an output set that is a subset of their input set. Some of the algorithmic descriptions that follow make use of the following definition: A total order of a collection is called _stable across requests_ if it is the same for all requests that construct the collection by executing the same resource path and transformations, possibly nested, on the same underlying data. ::: example -⚠ Example 23: A stable total order is required for the input set of a [`skip`](#Transformationskip) transformation. The following request constructs that input set by executing the `Sales` resource path and the `groupby` transformation, computing the total sales per customer. Because of the subsequent `skip` transformation, the service must endow this with a stable total order. In other words, the request divides the total sales per customer into pages of $N$ customers and returns page number $i$ in a reproducible manner (as long as the underlying data do not change). +⚠ Example 24: A stable total order is required for the input set of a [`skip`](#Transformationskip) transformation. The following request constructs that input set by executing the `Sales` resource path and the `groupby` transformation, computing the total sales per customer. Because of the subsequent `skip` transformation, the service must endow this with a stable total order. In other words, the request divides the total sales per customer into pages of $N$ customers and returns page number $i$ in a reproducible manner (as long as the underlying data do not change). ``` GET /service/Sales?$apply= groupby((Customer),aggregate(Amount with sum as Total)) @@ -1612,7 +1630,7 @@ The output set is constructed as follows: 6. Insert the current item of the loop into the output set in the order of $A$. 7. Continue the loop. -For example, if the input set consists of non-transient entities and the datastore contains an index ordered by the second parameter and then the entity-id, a service may implement this algorithm with $A=B$ ordered like this index. +For example, if the input set consists of non-transient entities and the datastore contains an index ordered by the second parameter and then the entity id, a service may implement this algorithm with $A=B$ ordered like this index. The order of the output set can be influenced with a subsequent [`orderby`](#Transformationorderby) transformation. @@ -1621,7 +1639,7 @@ The order of the output set can be influenced with a subsequent [`orderby`](#Tra The first parameter MUST evaluate to a positive integer $c$. The second parameter MUST evaluate to a primitive type whose values are totally ordered. In step 5, exit the loop if the cardinality of the output set equals $c$. ::: example -Example 24: +Example 25: ``` GET /service/Sales?$apply=bottomcount(2,Amount) ``` @@ -1638,7 +1656,7 @@ results in ::: ::: example -Example 25: +Example 26: ``` GET /service/Sales?$apply=topcount(2,Amount) ``` @@ -1660,7 +1678,7 @@ Note that two `Sales` entities with the second highest amount 4 exist in the inp The first parameter MUST evaluate to a positive number $p$ less than or equal to 100. The second parameter MUST evaluate to a number. In step 5, exit the loop if the ratio of the sum of the numbers addressed by the second parameter in the output set to their sum in the input set equals or exceeds $p$%. ::: example -Example 26: +Example 27: ``` GET /service/Sales?$apply=bottompercent(50,Amount) ``` @@ -1681,7 +1699,7 @@ results in ::: ::: example -Example 27: +Example 28: ``` GET /service/Sales?$apply=toppercent(50,Amount) ``` @@ -1702,7 +1720,7 @@ results in The first parameter MUST evaluate to a number $s$. The second parameter MUST be an [aggregatable expression](#AggregatableExpression) that evaluates to a number. In step 5, exit the loop if the sum of the numbers addressed by the second parameter in the output set is greater than or equal to a non-negative $s$ or is less than or equal to a negative $s$. ::: example -Example 28: +Example 29: ``` GET /service/Sales?$apply=bottomsum(7,Amount) ``` @@ -1722,7 +1740,7 @@ results in ::: ::: example -Example 29: +Example 30: ``` GET /service/Sales?$apply=topsum(15,Amount) ``` @@ -1744,7 +1762,7 @@ results in The `filter` transformation takes a Boolean expression that could also be passed as a `$filter` system query option. Its output set is the subset of the input set containing all instances (possibly with repetitions) for which this expression, evaluated relative to the instance, yields true. No order is defined on the output set. ::: example -Example 30: +Example 31: ``` GET /service/Sales?$apply=filter(Amount gt 3) ``` @@ -1766,7 +1784,7 @@ results in The output set of the `identity` transformation is its input set in unchanged order. ::: example -Example 31: Add a grand total row to the `Sales` result set +Example 32: Add a grand total row to the `Sales` result set ``` GET /service/Sales?$apply=concat(identity,aggregate(Amount with sum as Total)) ``` @@ -1777,7 +1795,7 @@ GET /service/Sales?$apply=concat(identity,aggregate(Amount with sum as Total)) The `orderby` transformation takes a list of expressions that could also be passed as a `$orderby` system query option. Its output set consists of the instances of the input set in the same order `$orderby` would produce for the given expressions, but keeping the relative order from the input set if the given expressions do not distinguish between two instances. The orderby transformation thereby performs a [stable-sort](#SamenessandPrecedence). A service supporting this transformation MUST at least offer sorting by values addressed by property paths, including dynamic properties, with both suffixes `asc` and `desc`. ::: example -Example 32: +Example 33: ``` GET /service/Sales?$apply=groupby((Product/Name), aggregate(Amount with sum as Total)) @@ -1804,7 +1822,7 @@ results in The `search` transformation takes a search expression that could also be passed as a `$search` system query option. Its output set is the subset of the input set containing all instances (possibly with repetitions) that match this search expression. Closing parentheses in search expressions must be within single or double quotes in order to avoid syntax errors like `search())`. No order is defined on the output set. ::: example -Example 33: assuming that free-text search on `Sales` takes the related product name into account, +Example 34: assuming that free-text search on `Sales` takes the related product name into account, ``` GET /service/Sales?$apply=search(coffee) ``` @@ -1827,7 +1845,7 @@ The `skip` transformation takes a non-negative integer $c$ as argument. Let $A$ The transformation excludes from the output set the first $c$ instances of $A$. It keeps all remaining instances in the same order as they occur in $A$. ::: example -Example 34: +Example 35: ``` GET /service/Sales?$apply=orderby(Customer/Name desc)/skip(2)/top(2) ``` @@ -1852,7 +1870,7 @@ If $A$ contains more than $c$ instances, the output set consists of the first $c Note the transformation `top(0)` produces an empty output set. ::: example -Example 35: +Example 36: ``` GET /service/Sales?$apply=orderby(Customer/Name desc)/top(2) ``` @@ -1887,7 +1905,7 @@ The output set is constructed by copying the instances of the input set and addi The values of properties copied from the input set are not changed, nor is the order of instances changed. ::: example -Example 36: +Example 37: ``` GET /service/Sales?$apply=compute(Amount mul Product/TaxRate as Tax) ``` @@ -1924,7 +1942,7 @@ The `join` and `outerjoin` transformations take as their first parameter $p$ a c - The property $w$ carries as control information the context URL of $u$. ::: example -Example 37: all links between products and sales instances +Example 38: all links between products and sales instances ``` GET /service/Products?$apply=join(Sales as Sale)&$select=ID&$expand=Sale ``` @@ -1978,10 +1996,10 @@ Applying `outerjoin` instead would return an additional instance for product wit The `nest` transformation takes as parameters one or more transformation sequences followed by the `as` keyword followed by an [alias](#TypeStructureandContextURL). These aliases MUST NOT collide with names of properties in the input set or with other aliases introduced in the same nest transformation. -The output set consists of a single instance of the [input type](#TypeStructureandContextURL) without entity-id having one dynamic property per transformation sequence. The name of the dynamic property is the alias for this transformation sequence. The value of the dynamic property is the collection resulting from the transformation sequence applied to the input set. The dynamic property carries as control information the context URL of the transformed input set. +The output set consists of a single instance of the [input type](#TypeStructureandContextURL) without entity id having one dynamic property per transformation sequence. The name of the dynamic property is the alias for this transformation sequence. The value of the dynamic property is the collection resulting from the transformation sequence applied to the input set. The dynamic property carries as control information the context URL of the transformed input set. ::: example -Example 38: +Example 39: ``` GET /service/Sales?$apply=nest(groupby((Customer/ID)) as Customers)) ``` @@ -2017,7 +2035,7 @@ If $p_k$ is single-valued, the transformation sequences MUST consist of only `id If $v$ is defined, then for each transformation sequence, a dynamic property is added to $u$ as follows: If $p_k$ is a navigation property, the added property is a dynamic navigation property, which is expanded by default, otherwise it is a dynamic structural property. Its name is the alias of the transformation sequence. The value of the added property is the result of the transformation sequence applied to $v$. The dynamic property carries as control information the context URL of $v$. ::: example -Example 39: +Example 40: ``` GET /service/Customers?$apply=addnested(Sales, filter(Amount gt 3) as FilteredSales) @@ -2060,12 +2078,12 @@ These expressions are ### 3.5.1 Function `aggregate` -The `aggregate` function allows to use aggregated values in [expressions](#Expression). It takes a single parameter accepting an aggregate-function expression and returns the aggregated value of type `Edm.PrimitiveType` as the result from applying the aggregate-function expression on its input collection. +The `aggregate` function allows to use aggregated values in [expressions](#Expression). It takes a single parameter accepting an [aggregate expression](#AggregateExpression) and returns the aggregated value of type `Edm.PrimitiveType` as the result from applying the aggregate expression on its input collection. -An aggregate-function expression offers the same capabilities as an aggregate expression defined for the `aggregate` transformation except that it never includes an alias for introducing a dynamic property containing the aggregated value. More precisely, if $α$ is an aggregate expression, the function $p/{\tt aggregate}(α)$ or ${\tt\$these}/{\tt aggregate}(α)$ evaluates to the value of the property $D$ in the single instance of the output set that is produced when the transformation ${\tt aggregate}(α{\tt\ as\ }D)$ is applied with the input collection as input set. +More precisely, if $α$ is an aggregate expression, the function $p/{\tt aggregate}(α)$ or ${\tt\$these}/{\tt aggregate}(α)$ evaluates to the value of the property $D$ in the single instance of the output set that is produced when the transformation ${\tt aggregate}(α{\tt\ as\ }D)$ is applied with the input collection as input set. ::: example -Example 40: Sales making up at least a third of the total sales amount. +Example 41: Sales making up at least a third of the total sales amount. ``` GET /service/Sales?$filter=Amount mul 3 ge $these/aggregate(Amount with sum) ``` @@ -2081,7 +2099,7 @@ results in ::: ::: example -Example 41: Products with more than 1.00 sales tax. The aggregate expression of type 2 combines paths with and without `$it` prefix (compare this with [example 8](#aggrmul)). +Example 42: Products with more than 1.00 sales tax. The aggregate expression of type 2 combines paths with and without `$it` prefix (compare this with [example 8](#aggrmul)). ``` GET /service/Products?$filter=Sales/aggregate(Amount mul $it/TaxRate with sum) gt 1 @@ -2089,7 +2107,7 @@ GET /service/Products?$filter=Sales/aggregate(Amount mul $it/TaxRate with sum) ::: ::: example -⚠ Example 42: products with a single sale of at least twice the average sales amount +⚠ Example 43: products with a single sale of at least twice the average sales amount ``` GET /service/Products?$filter=Sales/any(s:s/Amount ge Sales/aggregate(Amount with average) mul 2) @@ -2110,7 +2128,7 @@ Both examples result in The expression `$count` evaluates to the cardinality of the input collection. ::: example -Example 43: The input collection for `$count` consists of all sales entities, the top third of sales entities by amount form the result. +Example 44: The input collection for `$count` consists of all sales entities, the top third of sales entities by amount form the result. ``` GET /service/Sales?$apply=topcount($these/$count div 3,Amount) ``` @@ -2135,7 +2153,7 @@ Properties that are not explicitly mentioned in [`aggregate`](#Transformationagg The function `isdefined` can be used to determine whether a property is present or absent in an instance. It takes a [single-valued property path](#SingleValuedPropertyPath) as its only parameter and returns true if the property is present in the instance for which the expression containing the `isdefined` function call is evaluated. A present property can still have the null value; it can represent a grouping of null values, or an aggregation that results in a null value. ::: example -Example 44: `Product` has been aggregated away, causing an empty result +Example 45: `Product` has been aggregated away, causing an empty result ``` GET /service/Sales?$apply=aggregate(Amount with sum as Total) &$filter=isdefined(Product) @@ -2154,7 +2172,7 @@ results in The new system query option `$apply` can be used as an expand or select option to inline the result of aggregating related entities or nested instances. The rules for [evaluating `$apply`](#SystemQueryOptionapply) are applied in the context of the expanded navigation or the selected collection of instances, i.e. `$apply` is evaluated first, and other expand or select options on the same (navigation) property are evaluated on the result of `$apply`. ::: example -Example 45: products with aggregated sales +Example 46: products with aggregated sales ``` GET /service/Products ?$expand=Sales($apply=aggregate(Amount with sum as Total)) @@ -2192,7 +2210,7 @@ In some cases, however, requests need to span entity sets with no predefined ass Where useful navigations exist it is beneficial to expose those as explicit navigation properties in the model, but the ability to pose queries that span entity sets not related by an association provides a mechanism for advanced consumers to use more flexible join conditions. ::: example -Example 46: if `Sale` had a string property `ProductID` instead of the navigation property `Product`, a "join" between `Sales` and `Products` could be accessed via the `$crossjoin` resource +Example 47: if `Sale` had a string property `ProductID` instead of the navigation property `Product`, a "join" between `Sales` and `Products` could be accessed via the `$crossjoin` resource ``` GET /service/$crossjoin(Products,Sales) ?$expand=Products($select=Name),Sales($select=Amount) @@ -2212,7 +2230,7 @@ results in ::: ::: example -Example 47: using the `$crossjoin` resource for aggregate queries +Example 48: using the `$crossjoin` resource for aggregate queries ``` GET /service/$crossjoin(Products,Sales) ?$apply=filter(Products/ID eq Sales/ProductID) @@ -2264,7 +2282,7 @@ The term `ApplySupportedDefaults` can be applied to an entity container. It allo - Properties specified neither in `ApplySupported` nor in `ApplySupportedDefault` have their default value. ::: example -Example 48: an entity container with default support for everything defined in this specification +Example 49: an entity container with default support for everything defined in this specification ```xml @@ -2288,7 +2306,7 @@ If the custom aggregate is associated with an entity set, entity type or collect If the custom aggregate is associated with an entity container, the value of the `Qualifier` attribute MUST NOT collide with the names of any entity container children. ::: example -Example 49: Sales forecasts are modeled as a custom aggregate of the Sale entity type because it belongs there. For the budget, there is no appropriate structured type, so it is modeled as a custom aggregate of the `SalesData` entity container. +Example 50: Sales forecasts are modeled as a custom aggregate of the Sale entity type because it belongs there. For the budget, there is no appropriate structured type, so it is modeled as a custom aggregate of the `SalesData` entity container. ```xml 5.4 Annotation Example ::: example -Example 50: This simplified `Sales` entity set has a single aggregatable property `Amount` whose context is defined by the `Code` property of the related `Currency`, and a custom aggregate `Forecast` with the same context. The `Code` property of `Currencies` is groupable. All other properties are neither groupable nor aggregatable. +Example 51: This simplified `Sales` entity set has a single aggregatable property `Amount` whose context is defined by the `Code` property of the related `Currency`, and a custom aggregate `Forecast` with the same context. The `Code` property of `Currencies` is groupable. All other properties are neither groupable nor aggregatable. ```xml @@ -2442,7 +2460,7 @@ The following functions are defined: The hierarchy terms can be applied to the Example Data Model. ::: example -Example 51: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations +Example 52: leveled hierarchies for products and time, and a recursive hierarchy for the sales organizations ```xml @@ -2494,7 +2512,7 @@ Example 51: leveled hierarchies for products and time, and a recursive hierarchy The recursive hierarchy `SalesOrgHierarchy` can be used in functions with the `$filter` system query option. ::: example -Example 52: requesting all organizations below EMEA +Example 53: requesting all organizations below EMEA ``` GET /service/SalesOrganizations?$filter=Aggregation.isdescendant( HierarchyNodes=$root/SalesOrganizations, @@ -2520,7 +2538,7 @@ results in ::: ::: example -Example 53: requesting just those organizations directly below EMEA +Example 54: requesting just those organizations directly below EMEA ``` GET /service/SalesOrganizations?$filter=Aggregation.isdescendant( HierarchyNodes=$root/SalesOrganizations, @@ -2544,7 +2562,7 @@ results in ::: ::: example -Example 54: just the lowest-level organizations +Example 55: just the lowest-level organizations ``` GET /service/SalesOrganizations?$filter=Aggregation.isleaf( HierarchyNodes=$root/SalesOrganizations, @@ -2565,7 +2583,7 @@ results in ::: ::: example -Example 55: the lowest-level organizations including their superordinate's `ID` +Example 56: the lowest-level organizations including their superordinate's `ID` ``` GET /service/SalesOrganizations?$filter=Aggregation.isleaf( HierarchyNodes=$root/SalesOrganizations, @@ -2589,7 +2607,7 @@ results in ::: ::: example -Example 56: retrieving the sales `ID`s involving sales organizations from EMEA can be requested by +Example 57: retrieving the sales `ID`s involving sales organizations from EMEA can be requested by ``` GET /service/Sales?$select=ID&$filter=Aggregation.isdescendant( @@ -2677,7 +2695,7 @@ If parameter $d$ is absent, the parameter ${\tt MaxDistance}=d$ is omitted. If ` Since the output set of `ancestors` is constructed as a union, no instance from the input set will occur more than once in it, even if, for example, a sale is related to both a sales organization and one of its ancestor organizations. For `descendants`, analogously. ::: example -Example 57: Request based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples), with `Superordinate/$ref` expanded to illustrate the hierarchy relation +Example 58: Request based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples), with `Superordinate/$ref` expanded to illustrate the hierarchy relation ``` GET /service/SalesOrganizations?$apply= ancestors($root/SalesOrganizations,SalesOrgHierarchy,ID, @@ -2701,7 +2719,7 @@ results in ::: ::: example -Example 58: Request based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples), with `Superordinate/$ref` expanded to illustrate the hierarchy relation +Example 59: Request based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples), with `Superordinate/$ref` expanded to illustrate the hierarchy relation ``` GET /service/SalesOrganizations?$apply= descendants($root/SalesOrganizations,SalesOrgHierarchy,ID, @@ -2725,7 +2743,7 @@ results in ::: ::: example -⚠ Example 59: Input set and recursive hierarchy from two different entity sets +⚠ Example 60: Input set and recursive hierarchy from two different entity sets ``` GET /service/Sales?$apply= ancestors($root/SalesOrganizations, @@ -2774,27 +2792,27 @@ The definition of $σ(x)$ makes use of a function $a(ε,v,x)$, which returns a s Three cases are distinguished: 1. _Case where the recursive hierarchy is defined on the input set_ This case applies if the paths $p$ and $q$ are equal. Let $σ(x)=x$ and let $G$ be a list containing all structural and navigation properties of the entity type of $H$. - In this case $\Pi_G(σ(x))$ injects all properties of $x$ into the instances of the output set. (See [Example 61](#caseone).) + In this case $\Pi_G(σ(x))$ injects all properties of $x$ into the instances of the output set. (See [Example 62](#caseone).) 2. _Case where the recursive hierarchy is defined on the related entity type addressed by a navigation property path_ This case applies if $p'$ is a non-empty navigation property path and $p''$ an optional type-cast segment such that $p$ equals the concatenated path $p'/p''/q$. Let $σ(x)=a(ε,p'/p'',x)$ and let $G=(p')$. - In this case $\Pi_G(σ(x))$ injects the whole related entity $x$ into the instances of the output set. The navigation property path $p'$ is expanded by default. (See [Example 62](#rollupnode).) + In this case $\Pi_G(σ(x))$ injects the whole related entity $x$ into the instances of the output set. The navigation property path $p'$ is expanded by default. (See [Example 63](#rollupnode).) 3. _Case where the recursive hierarchy is related to the input set only through equality of node identifiers, not through navigation_ If neither case 1 nor case 2 applies, let $σ(x)=a(ε,p,x[q])$ and let $G=(p)$. In this case $\Pi_G(σ(x))$ injects only the node identifier of $x$ into the instances of the output set. -Here paths are considered equal if their non-type-cast segments refer to the same model elements when evaluated relative to the input set (see [Example 63](#pathequals)). +Here paths are considered equal if their non-type-cast segments refer to the same model elements when evaluated relative to the input set (see [Example 64](#pathequals)). The function $a(u,v,x)$ takes an instance, a path and another instance as arguments and is defined recursively as follows: -1. If $u$ equals the special symbol $ε$, set $u$ to a new instance of the [input type](#TypeStructureandContextURL) without properties and without entity-id. +1. If $u$ equals the special symbol $ε$, set $u$ to a new instance of the [input type](#TypeStructureandContextURL) without properties and without entity id. 2. If $v$ contains only one segment other than a type cast, let $v_1=v$, and let $x'=x$, then go to step 6. 3. Otherwise, let $v_1$ be the first property segment in $v$, possibly together with a preceding type-cast segment, let $v_2$ be any type-cast segment that immediately follows, and let $v_3$ be the remainder such that $v$ equals the concatenated path $v_1/v_2/v_3$ where ${}/v_2$ may be absent. -4. Let $u'$ be an instance of the type of $v_1/v_2$ without properties and without entity-id. +4. Let $u'$ be an instance of the type of $v_1/v_2$ without properties and without entity id. 5. Let $x'=a(u',v_3,x)$. 6. If $v_1$ is single-valued, let $u[v_1]=x'$. 7. If $v_1$ is collection-valued, let $u[v_1]$ be a collection consisting of one item $x'$. 8. Return $u$. -(See [Example 107](#traversecoll).) +(See [Example 108](#traversecoll).) Let $r_1,…,r_n$ be a sequence of the root nodes of the recursive hierarchy $(H',Q)$ [preserving the order](#SamenessandPrecedence) of $H'$ stable-sorted by $o$. Then the transformation ${\tt traverse}(H,Q,p,h,S,o)$ is defined as equivalent to $${\tt concat}(R(r_1),…,R(r_n)).$$ @@ -2815,7 +2833,7 @@ $$\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hski where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent. ::: example -Example 60: Based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples) +Example 61: Based on the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples) ``` GET /service/SalesOrganizations?$apply= descendants($root/SalesOrganizations,SalesOrgHierarchy,ID, @@ -2872,7 +2890,7 @@ Let $T$ be a transformation sequence, $P_1$ stand in for zero or more property p _The `rolluprecursive` algorithm:_ -A property $χ_N$ appears in the algorithm, but is not present in the output set. It is explained later (see [Example 62](#rollupnode)). $Z_N$ is a transformation whose output set is its input set with property $χ_N$ removed. +A property $χ_N$ appears in the algorithm, but is not present in the output set. It is explained later (see [Example 63](#rollupnode)). $Z_N$ is a transformation whose output set is its input set with property $χ_N$ removed. If $r_1,…,r_n$ are the root nodes of the recursive hierarchy $(H',Q)$, the transformation ${\tt groupby}((P_1,{\tt rolluprecursive}(H,Q,p,S),P_2),T)$ is defined as equivalent to $${\tt concat}(R(r_1),…,R(r_n))$$ @@ -2893,12 +2911,12 @@ $$\matrix{ F(x)={\tt filter}(\hbox{\tt Aggregation.isdescendant}(\hfill\\ \quad Otherwise $p=p_1/…/p_k/r$ with $k≥1$ and $$\matrix{ F(x)={\tt filter}(\hfill\\ \hskip1pc p_1/{\tt any}(y_1:\hfill\\ \hskip2pc y_1/p_2/{\tt any}(y_2:\hfill\\ \hskip3pc ⋱\hfill\\ \hskip4pc y_{k-1}/p_k/{\tt any}(y_k:\hfill\\ \hskip5pc \hbox{\tt Aggregation.isdescendant}(\hfill\\ \hskip6pc {\tt HierarchyNodes}=H',\;{\tt HierarchyQualifier}=\hbox{\tt{'$Q$'}},\hfill\\ \hskip6pc {\tt Node}=y_k/r,\;{\tt Ancestor}=x[q],\;{\tt IncludeSelf}={\tt true}\hfill\\ \hskip5pc )\hfill\\ \hskip4pc )\hfill\\ \hskip3pc ⋰\hfill\\ \hskip2pc )\hfill\\ \hskip1pc )\hfill\\ )\hfill }$$ -where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent. (See [example 108](#rollupcoll) for a case with $k=1$.) +where $y_1,…,y_k$ denote `lambdaVariableExpr`s and ${}/r$ may be absent. (See [example 109](#rollupcoll) for a case with $k=1$.) Non-normatively speaking, the effect of the algorithm can be summarized as follows: If $M≥1$ and $\hat F_N(x)$ denotes the collection of all instances that are related to a node $x$ from the recursive hierarchy of the $N$-th `rolluprecursive` operator, then $T$ is applied to each of the intersections of $\hat F_1(χ_1),…,\hat F_M(χ_M)$, as $χ_N$ runs over all nodes of the $N$-th recursive hierarchy for $1≤N≤M$. Into the instances of the resulting output sets the $\Pi_G$ transformations inject information about the nodes $χ_1,…,χ_M$. ::: example -Example 61: Total number of sub-organizations for all organizations in the hierarchy defined in [Hierarchy Examples](#HierarchyExamples) with $p=q={\tt ID}$ (case 1 of the [definition](#Transformationtraverse) of $σ(x)$). In this case, the entire node is written back into the output set of $T$, aggregates must have an alias to avoid overwriting. +Example 62: Total number of sub-organizations for all organizations in the hierarchy defined in [Hierarchy Examples](#HierarchyExamples) with $p=q={\tt ID}$ (case 1 of the [definition](#Transformationtraverse) of $σ(x)$). In this case, the entire node is written back into the output set of $T$, aggregates must have an alias to avoid overwriting. ``` GET /service/SalesOrganizations?$apply= groupby((rolluprecursive( @@ -2933,7 +2951,7 @@ results in The value of the property $χ_N$ in the algorithm is the node $x$ at recursion level $N$. In a common expression, $χ_N$ cannot be accessed by its name, but can only be read as the return value of the instance-bound function ${\tt rollupnode}({\tt Position}=N)$ defined in the `Aggregation` vocabulary [OData-VocAggr](#ODataVocAggr), with $1≤N≤M$, and only during the application of the transformation sequence $T$ in the row labeled (1) in the formula $R(x)$ above (the function is undefined otherwise). If $N=1$, the Position parameter can be omitted. ::: example -⚠ Example 62: Total sales amounts per organization, both including and excluding sub-organizations, in the US sub-hierarchy defined in [Hierarchy Examples](#HierarchyExamples) with $p=p'/q={\tt SalesOrganization}/{\tt ID}$ and $p'={\tt SalesOrganization}$ (case 2 of the [definition](#Transformationtraverse) of $σ(x)$). The Boolean expression $p'\hbox{\tt\ eq Aggregation.rollupnode}()$ is true for sales in the organization for which the aggregate is computed, but not for sales in sub-organizations. +⚠ Example 63: Total sales amounts per organization, both including and excluding sub-organizations, in the US sub-hierarchy defined in [Hierarchy Examples](#HierarchyExamples) with $p=p'/q={\tt SalesOrganization}/{\tt ID}$ and $p'={\tt SalesOrganization}$ (case 2 of the [definition](#Transformationtraverse) of $σ(x)$). The Boolean expression $p'\hbox{\tt\ eq Aggregation.rollupnode}()$ is true for sales in the organization for which the aggregate is computed, but not for sales in sub-organizations. ``` GET /service/Sales?$apply=groupby( (rolluprecursive( @@ -2969,7 +2987,7 @@ results in ::: ::: example -⚠ Example 63: Although $p={\tt ID}$ and $q={\tt ID}$, they are not equal in the sense of case 1, because they are evaluated relative to different entity sets. Hence, this is an example of case 3 of the [definition](#Transformationtraverse) of $σ(x)$, where no `Sales/ID` matches a `SalesOrganizations/ID`, that is, all $F(x)$ have empty output sets. +⚠ Example 64: Although $p={\tt ID}$ and $q={\tt ID}$, they are not equal in the sense of case 1, because they are evaluated relative to different entity sets. Hence, this is an example of case 3 of the [definition](#Transformationtraverse) of $σ(x)$, where no `Sales/ID` matches a `SalesOrganizations/ID`, that is, all $F(x)$ have empty output sets. ``` GET /service/Sales?$apply= groupby((rolluprecursive( @@ -3014,7 +3032,7 @@ The following examples show some common aggregation-related questions that can b Grouping without specifying a set transformation returns the distinct combination of the grouping properties. ::: example -Example 64: +Example 65: ``` GET /service/Customers?$apply=groupby((Name)) ``` @@ -3036,7 +3054,7 @@ Note that "Sue" appears only once although the customer base contains two differ Aggregation is also possible across related entities. ::: example -Example 65: customers that bought something +Example 66: customers that bought something ``` GET /service/Sales?$apply=groupby((Customer/Name)) ``` @@ -3059,7 +3077,7 @@ However, even though both Sues bought products, only one "Sue" appears in the ag ::: ::: example -Example 66: +Example 67: ``` GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID)) ``` @@ -3082,7 +3100,7 @@ GET /service/Sales?$apply=groupby((Customer)) ::: ::: example -Example 67: Grouping by navigation property `Customer` +Example 68: Grouping by navigation property `Customer` ``` GET /service/Sales?$apply=groupby((Customer)) @@ -3101,7 +3119,7 @@ results in ::: ::: example -Example 68: the first question in the motivating example in [section 2.3](#ExampleUseCases), which customers bought which products, can now be expressed as +Example 69: the first question in the motivating example in [section 2.3](#ExampleUseCases), which customers bought which products, can now be expressed as ``` GET /service/Sales?$apply=groupby((Customer/Name,Customer/ID,Product/Name)) ``` @@ -3130,7 +3148,7 @@ and results in ::: ::: example -⚠ Example 69: grouping by properties of subtypes +⚠ Example 70: grouping by properties of subtypes ``` GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating, SalesModel.NonFoodProduct/RatingClass)) @@ -3151,7 +3169,7 @@ results in ::: ::: example -⚠ Example 70: grouping by a property of a subtype +⚠ Example 71: grouping by a property of a subtype ``` GET /service/Products?$apply=groupby((SalesModel.FoodProduct/Rating)) ``` @@ -3173,7 +3191,7 @@ results in a third group representing entities with no `SalesModel.FoodProduct/R The client may specify one of the predefined aggregation methods [`min`](#StandardAggregationMethodmin), [`max`](#StandardAggregationMethodmax), [`sum`](#StandardAggregationMethodsum), [`average`](#StandardAggregationMethodaverage), and [`countdistinct`](#StandardAggregationMethodcountdistinct), or a [custom aggregation method](#CustomAggregationMethods), to aggregate an [aggregatable expression](#AggregatableExpression). Expressions defining an aggregate method specify an [alias](#Keywordas). The aggregated values are returned in a dynamic property whose name is determined by the alias. ::: example -Example 71: +Example 72: ``` GET /service/Products?$apply=groupby((Name), aggregate(Sales/Amount with sum as Total)) @@ -3195,7 +3213,7 @@ Note that the base set of the request is `Products`, so there is a result item f ::: ::: example -Example 72: Alternatively, the request could ask for the aggregated amount to be nested inside a clone of Sales +Example 73: Alternatively, the request could ask for the aggregated amount to be nested inside a clone of Sales ``` GET /service/Products?$apply=addnested(Sales, aggregate(Amount with sum as Total) as AggregatedSales) @@ -3223,7 +3241,7 @@ results in ::: ::: example -Example 73: To compute the aggregate as a property without nesting, use the aggregate function in `$compute` rather than the aggregate transformation in `$apply`: +Example 74: To compute the aggregate as a property without nesting, use the aggregate function in `$compute` rather than the aggregate transformation in `$apply`: ``` GET /service/Products?$compute=Sales/aggregate(Amount with sum) as Total ``` @@ -3248,7 +3266,7 @@ The expression `$it/Sales` refers to the sales of the current product. Without ` ::: ::: example -Example 74: Alternatively, `join` could be applied to yield a flat structure: +Example 75: Alternatively, `join` could be applied to yield a flat structure: ``` GET /service/Products?$apply= join(Sales as TotalSales,aggregate(Amount with sum as Total)) @@ -3276,7 +3294,7 @@ Applying `outerjoin` instead would return an additional entity for product with ::: ::: example -Example 75: +Example 76: ``` GET /service/Sales?$apply=groupby((Customer/Country), aggregate(Amount with average as AverageAmount)) @@ -3297,7 +3315,7 @@ Here the `AverageAmount` is of type `Edm.Double`. ::: ::: example -Example 76: `$count` after navigation property +Example 77: `$count` after navigation property ``` GET /service/Products?$apply=groupby((Name), aggregate(Sales/$count as SalesCount)) @@ -3319,7 +3337,7 @@ results in To place the number of instances in a group next to other aggregated values, the aggregate expression [`$count`](#AggregateExpressioncount) can be used: ::: example -⚠ Example 77: The effect of the `groupby` is to create transient entities and avoid in the result structural properties other than `Name`. +⚠ Example 78: The effect of the `groupby` is to create transient entities and avoid in the result structural properties other than `Name`. ``` GET /service/Products?$apply=groupby((Name),addnested(Sales, aggregate($count as SalesCount, @@ -3353,7 +3371,7 @@ results in The `aggregate` function can not only be used in `$compute` but also in `$filter` and `$orderby`: ::: example -Example 78: Products with an aggregated sales volume of ten or more +Example 79: Products with an aggregated sales volume of ten or more ``` GET /service/Products?$filter=Sales/aggregate(Amount with sum) ge 10 ``` @@ -3370,7 +3388,7 @@ results in ::: ::: example -Example 79: Customers in descending order of their aggregated sales volume +Example 80: Customers in descending order of their aggregated sales volume ``` GET /service/Customers?$orderby=Sales/aggregate(Amount with sum) desc ``` @@ -3389,7 +3407,7 @@ results in ::: ::: example -Example 80: Contribution of each sales to grand total sales amount +Example 81: Contribution of each sales to grand total sales amount ``` GET /service/Sales?$compute=Amount divby $these/aggregate(Amount with sum) as Contribution @@ -3421,7 +3439,7 @@ results in ::: ::: example -Example 81: Product categories with at least one product having an aggregated sales amount greater than 10 +Example 82: Product categories with at least one product having an aggregated sales amount greater than 10 ``` GET /service/Categories?$filter=Products/any( p:p/Sales/aggregate(Amount with sum) gt 10) @@ -3440,7 +3458,7 @@ results in The `aggregate` function can also be applied inside `$apply`: ::: example -Example 82: Sales volume per customer in relation to total volume +Example 83: Sales volume per customer in relation to total volume ``` GET /service/Sales?$apply= groupby((Customer),aggregate(Amount with sum as CustomerAmount)) @@ -3465,7 +3483,7 @@ results in ::: ::: example -Example 83: rule 1 for [keyword `from`](#Keywordfrom) applied repeatedly +Example 84: rule 1 for [keyword `from`](#Keywordfrom) applied repeatedly ``` GET /service/Sales?$apply=aggregate(Amount with sum from Time with average @@ -3476,23 +3494,23 @@ is equivalent to (with nested `groupby` transformations) ``` GET /service/Sales?$apply= groupby((Customer/Country), - groupby((Time),aggregate(Amount with sum as A1)) - /aggregate(A1 with average as A2)) - /aggregate(A2 with max as MaxDailyAveragePerCountry) + groupby((Time),aggregate(Amount with sum as D1)) + /aggregate(D1 with average as D2)) + /aggregate(D2 with max as MaxDailyAveragePerCountry) ``` and is equivalent to (with consecutive `groupby` transformations) ``` GET /service/Sales?$apply= - groupby((Customer/Country,Time),aggregate(Amount with sum as A1)) - /groupby((Customer/Country),aggregate(A1 with average as A2)) - /aggregate(A2 with max as MaxDailyAveragePerCountry) + groupby((Customer/Country,Time),aggregate(Amount with sum as D1)) + /groupby((Customer/Country),aggregate(D1 with average as D2)) + /aggregate(D2 with max as MaxDailyAveragePerCountry) ``` ::: ## 7.3 Requesting Expanded Results ::: example -Example 84: Assuming an extension of the data model where `Customer` contains an additional collection-valued complex property `Addresses` and these contain a single-valued navigation property `ResponsibleSalesOrganization`, `addnested` can be used to compute a nested dynamic property: +Example 85: Assuming an extension of the data model where `Customer` contains an additional collection-valued complex property `Addresses` and these contain a single-valued navigation property `ResponsibleSalesOrganization`, `addnested` can be used to compute a nested dynamic property: ``` GET /service/Customers?$apply= addnested(Addresses/ResponsibleSalesOrganization, @@ -3524,7 +3542,7 @@ results in `addnested` transformations can be nested. ::: example -Example 85: nested `addnested` transformations +Example 86: nested `addnested` transformations ``` GET /service/Categories?$apply= addnested(Products, @@ -3575,7 +3593,7 @@ results in the response before without the FilteredSales dynamic navigation prop ::: ::: example -Example 86: Here only the `GroupedSales` are expanded, because they are named in `$expand`, the related `Product` entity is not: +Example 87: Here only the `GroupedSales` are expanded, because they are named in `$expand`, the related `Product` entity is not: ``` GET /service/Customers?$apply=addnested(Sales, groupby((Product/Name)) as GroupedSales) @@ -3614,7 +3632,7 @@ results in ::: ::: example -Example 87: use `outerjoin` to split up collection-valued navigation properties for grouping +Example 88: use `outerjoin` to split up collection-valued navigation properties for grouping ``` GET /service/Customers?$apply=outerjoin(Sales as ProductSales) /groupby((Country,ProductSales/Product/Name)) @@ -3651,7 +3669,7 @@ Custom aggregates are defined through the [`CustomAggregate`](#CustomAggregates) A custom aggregate can be used by specifying the name of the custom aggregate in the [`aggregate`](#Transformationaggregate) clause. ::: example -Example 88: +Example 89: ``` GET /service/Sales?$apply=groupby((Customer/Country), aggregate(Amount with sum as Actual,Forecast)) @@ -3675,7 +3693,7 @@ results in When associated with an entity set a custom aggregate MAY have the same name as a property of the underlying entity type with the same type as the type returned by the custom aggregate. This is typically done when the aggregate is used as a default aggregate for that property. ::: example -Example 89: A custom aggregate can be defined with the same name as a property of the same type in order to define a default aggregate for that property. +Example 90: A custom aggregate can be defined with the same name as a property of the same type in order to define a default aggregate for that property. ``` GET /service/Sales?$apply=groupby((Customer/Country),aggregate(Amount)) ``` @@ -3692,7 +3710,7 @@ results in ::: ::: example -Example 90: illustrates rule 1 for [keyword `from`](#Keywordfrom): maximal sales forecast for a product +Example 91: illustrates rule 1 for [keyword `from`](#Keywordfrom): maximal sales forecast for a product ``` GET /service/Sales?$apply=aggregate(Forecast from Product with max as MaxProductForecast) @@ -3706,7 +3724,7 @@ GET /service/Sales?$apply= ::: ::: example -Example 91: illustrates rule 2 for [keyword `from`](#Keywordfrom): the forecast is computed in two steps +Example 92: illustrates rule 2 for [keyword `from`](#Keywordfrom): the forecast is computed in two steps ``` GET /service/Sales?$apply=aggregate(Forecast from Product as ProductForecast) ``` @@ -3719,7 +3737,7 @@ GET /service/Sales?$apply= ::: ::: example -Example 92: illustrates rule 1 followed by rule 2 for [keyword `from`](#Keywordfrom): a forecast based on the average daily forecasts per country +Example 93: illustrates rule 1 followed by rule 2 for [keyword `from`](#Keywordfrom): a forecast based on the average daily forecasts per country ``` GET /service/Sales?$apply=aggregate(Forecast from Time with average from Customer/Country @@ -3730,7 +3748,7 @@ is equivalent to the following (except that the property name is `Forecast` inst GET /service/Sales?$apply= groupby((Customer/Country), groupby((Time),aggregate(Forecast)) - /aggregate(Forecast with average as A1)) + /aggregate(Forecast with average as D1)) /aggregate(Forecast) ``` ::: @@ -3740,7 +3758,7 @@ GET /service/Sales?$apply= A property can be aggregated in multiple ways, each with a different alias. ::: example -Example 93: +Example 94: ``` GET /service/Sales?$apply=groupby((Customer/Country), aggregate(Amount with sum as Total, @@ -3765,7 +3783,7 @@ results in The introduced dynamic property is added to the context where the aggregate expression is applied to: ::: example -Example 94: +Example 95: ``` GET /service/Products?$apply=groupby((Name), aggregate(Sales/Amount with sum as Total)) @@ -3801,7 +3819,7 @@ results in There is no hard distinction between groupable and aggregatable properties: the same property can be aggregated and used to group the aggregated results. ::: example -Example 95: +Example 96: ``` GET /service/Sales?$apply=groupby((Amount),aggregate(Amount with sum as Total)) ``` @@ -3824,7 +3842,7 @@ will return all distinct amounts appearing in sales orders and how much money wa Dynamic property names may be reused in different transformation sequences passed to `concat`. ::: example -Example 96: to get the best-selling product per country with sub-totals for every country, the partial results of a transformation sequence and a `groupby` transformation are concatenated: +Example 97: to get the best-selling product per country with sub-totals for every country, the partial results of a transformation sequence and a `groupby` transformation are concatenated: ``` GET /service/Sales?$apply=concat( groupby((Customer/Country,Product/Name), @@ -3856,7 +3874,7 @@ results in ::: ::: example -Example 97: transformation sequences are also useful inside `groupby`: Aggregate the amount by only considering the top two sales amounts per product and country: +Example 98: transformation sequences are also useful inside `groupby`: Aggregate the amount by only considering the top two sales amounts per product and country: ``` GET /service/Sales?$apply=groupby((Customer/Country,Product/Name), topcount(2,Amount)/aggregate(Amount with sum as Total)) @@ -3887,7 +3905,7 @@ results in ::: ::: example -Example 98: concatenation of two different groupings "biggest sale per customer" and "biggest sale per product", made distinguishable by a dynamic property: +Example 99: concatenation of two different groupings "biggest sale per customer" and "biggest sale per product", made distinguishable by a dynamic property: ``` GET /service/Sales?$apply=concat( groupby((Customer),topcount(1,Amount))/compute('Customer' as per), @@ -3919,7 +3937,7 @@ In the result, `Sales` entities 4 and 6 occur twice each with contradictory valu ## 7.6 Model Functions as Set Transformations ::: example -Example 99: As a variation of [example 96](#bestselling), a query for returning the best-selling product per country and the total amount of the remaining products can be formulated with the help of a model function. +Example 100: As a variation of [example 97](#bestselling), a query for returning the best-selling product per country and the total amount of the remaining products can be formulated with the help of a model function. For this purpose, the model includes a definition of a `TopCountAndRemainder` function that accepts a count and a numeric property for the top entities: ```xml @@ -3967,7 +3985,7 @@ Note that these two entities get their values for the Country property from the For a leveled hierarchy, consumers may specify a different aggregation method per level for every property passed to [`rollup`](#Groupingwithrollup) as a hierarchy level below the root level. ::: example -Example 100: get the average of the overall amount by month per product. +Example 101: get the average of the overall amount by month per product. Using a transformation sequence: ``` @@ -3987,7 +4005,7 @@ GET /service/Sales?$apply=groupby((Product/ID,Product/Name), ::: ::: example -Example 101: get the total amount per customer, the average of the total customer amounts per country, and the overall average of these averages +Example 102: get the total amount per customer, the average of the total customer amounts per country, and the overall average of these averages ``` GET /service/Sales?$apply=concat( groupby((rollup(Customer/Country,Customer/ID)), @@ -4034,7 +4052,7 @@ average. If aggregation along a recursive hierarchy does not apply to the entire hierarchy, transformations `ancestors` and `descendants` may be used to restrict it as needed. ::: example -Example 102: Total sales amounts for sales orgs in 'US' in the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples) +Example 103: Total sales amounts for sales orgs in 'US' in the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples) ``` GET /service/Sales?$apply= descendants( @@ -4069,7 +4087,7 @@ Note that this example returns the actual total sums regardless of whether the ` The order of transformations becomes relevant if `groupby` with `rolluprecursive` shall aggregate over a thinned-out hierarchy, like here: ::: example -Example 103: Number of Paper sales per sales org aggregated along the the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples) +Example 104: Number of Paper sales per sales org aggregated along the the `SalesOrgHierarchy` defined in [Hierarchy Examples](#HierarchyExamples) ``` GET /service/Sales?$apply= filter(Product/Name eq 'Paper') @@ -4107,7 +4125,7 @@ results in ::: ::: example -⚠ Example 104: The input set `Sales` is filtered along a hierarchy on a related entity (navigation property `SalesOrganization`) before an aggregation +⚠ Example 105: The input set `Sales` is filtered along a hierarchy on a related entity (navigation property `SalesOrganization`) before an aggregation ``` GET /service/Sales?$apply= descendants($root/SalesOrganizations, @@ -4131,7 +4149,7 @@ GET /service/SalesOrganizations?$apply= ::: ::: example -⚠ Example 105: total sales amount aggregated along the sales organization subhierarchy with root EMEA restricted to 3 levels +⚠ Example 106: total sales amount aggregated along the sales organization subhierarchy with root EMEA restricted to 3 levels ``` GET /service/Sales?$apply= groupby((rolluprecursive($root/SalesOrganizations, @@ -4170,7 +4188,7 @@ GET /service/Sales?$apply= ::: ::: example -Example 106: Return the result of [example 62](#rollupnode) in preorder +Example 107: Return the result of [example 63](#rollupnode) in preorder ``` GET /service/Sales?$apply=groupby( (rolluprecursive( @@ -4212,7 +4230,7 @@ results in ::: ::: example -Example 107: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment $p_1={\tt Sales}$ and $r={\tt SalesOrganization}/{\tt ID}$. +Example 108: Preorder traversal of a hierarchy with 1:N relationship with collection-valued segment $p_1={\tt Sales}$ and $r={\tt SalesOrganization}/{\tt ID}$. ``` GET /service/Products?$apply=traverse( $root/SalesOrganizations, @@ -4252,7 +4270,7 @@ The result contains multiple instances of the same `Product` that differ in thei ::: ::: example -Example 108: Aggregation along a hierarchy with 1:N relationship: Sold products per sales organization +Example 109: Aggregation along a hierarchy with 1:N relationship: Sold products per sales organization ``` GET /service/Products?$apply= groupby((rolluprecursive( @@ -4284,7 +4302,7 @@ results in ::: ::: example -⚠ Example 109: Assume an extension of the data model where a `SalesOrganization` is associated with one or more instances of `ProductCategory`, and `ProductCategory` also organizes categories in a recursive hierarchy: +⚠ Example 110: Assume an extension of the data model where a `SalesOrganization` is associated with one or more instances of `ProductCategory`, and `ProductCategory` also organizes categories in a recursive hierarchy: ProductCategory|parent ProductCategory|associated SalesOrganizations ---------------|----------------------|----------------------------- @@ -4352,7 +4370,7 @@ would determine descendants of sales organizations for "Cereals" and their ances Applying aggregation first covers the most prominent use cases. The slightly more sophisticated question "how much money is earned with small sales" requires filtering the base set before applying the aggregation. To enable this type of question several transformations can be specified in `$apply` in the order they are to be applied, separated by a forward slash. ::: example -Example 110: +Example 111: ``` GET /service/Sales?$apply=filter(Amount le 1) /aggregate(Amount with sum as Total) @@ -4371,7 +4389,7 @@ means "filter first, then aggregate", and results in Using `filter` within `$apply` does not preclude using it as a normal system query option. ::: example -Example 111: +Example 112: ``` GET /service/Sales?$apply=filter(Amount le 2)/groupby((Product/Name), aggregate(Amount with sum as Total)) @@ -4392,7 +4410,7 @@ results in ::: ::: example -Example 112: Revisiting [example 16](#from) for using the `from` keyword with the `aggregate` function, the request +Example 113: Revisiting [example 16](#from) for using the `from` keyword with the `aggregate` function, the request ``` GET /service/Sales?$apply=aggregate(Amount from Time with average as DailyAverage) @@ -4406,7 +4424,7 @@ GET /service/Sales?$apply=groupby((Time),aggregate(Amount with sum as Total)) For further examples, consider another data model containing entity sets for cities, countries and continents and the obvious associations between them. ::: example -Example 113: getting the population per country with +Example 114: getting the population per country with ``` GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), aggregate(Population with sum as TotalPopulation)) @@ -4428,7 +4446,7 @@ results in ::: ::: example -Example 114: all countries with megacities and their continents +Example 115: all countries with megacities and their continents ``` GET /service/Cities?$apply=filter(Population ge 10000000) /groupby((Continent/Name,Country/Name), @@ -4437,7 +4455,7 @@ GET /service/Cities?$apply=filter(Population ge 10000000) ::: ::: example -Example 115: all countries with tens of millions of city dwellers and the continents only for these countries +Example 116: all countries with tens of millions of city dwellers and the continents only for these countries ``` GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), aggregate(Population with sum as CountryPopulation)) @@ -4459,7 +4477,7 @@ GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), ::: ::: example -Example 116: all countries with tens of millions of city dwellers and all continents with cities independent of their size +Example 117: all countries with tens of millions of city dwellers and all continents with cities independent of their size ``` GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), aggregate(Population with sum as CountryPopulation)) @@ -4471,7 +4489,7 @@ GET /service/Cities?$apply=groupby((Continent/Name,Country/Name), ::: ::: example -Example 117: assuming the data model includes a sales order entity set with related sets for order items and customers, the base set as well as the related items can be filtered before aggregation +Example 118: assuming the data model includes a sales order entity set with related sets for order items and customers, the base set as well as the related items can be filtered before aggregation ``` GET /service/SalesOrders?$apply=filter(Status eq 'incomplete') /addnested(Items,filter(not Shipped) as FilteredItems) @@ -4481,7 +4499,7 @@ GET /service/SalesOrders?$apply=filter(Status eq 'incomplete') ::: ::: example -Example 118: assuming that `Amount` is a custom aggregate in addition to the property, determine the total for countries with an `Amount` greater than 1000 +Example 119: assuming that `Amount` is a custom aggregate in addition to the property, determine the total for countries with an `Amount` greater than 1000 ``` GET /service/SalesOrders?$apply= groupby((Customer/Country),aggregate(Amount)) @@ -4491,7 +4509,7 @@ GET /service/SalesOrders?$apply= ::: ::: example -Example 119: The output set of the `concat` transformation contains `Sales` entities multiple times with conflicting related `AugmentedProduct` entities that cannot be aggregated by the second transformation. +Example 120: The output set of the `concat` transformation contains `Sales` entities multiple times with conflicting related `AugmentedProduct` entities that cannot be aggregated by the second transformation. ``` GET /service/Sales?$apply= concat(addnested(Product,compute(0.1 as Discount) as AugmentedProduct), @@ -4502,7 +4520,7 @@ results in an error. ::: ::: example -Example 120: The `nest` transformation can be used inside `groupby` to produce one or more collection-valued properties per group. +Example 121: The `nest` transformation can be used inside `groupby` to produce one or more collection-valued properties per group. ``` GET /service/Sales?$apply=groupby((Product/Category/ID), nest(groupby((Customer/ID)) as Customers)) @@ -4549,34 +4567,30 @@ The following documents are referenced in such a way that some or all of their c ###### [OData-ABNF] _ABNF components: OData ABNF Construction Rules Version 4.01 and OData ABNF Test Cases._ -See the link in "[Related work](#RelatedWork)" section on cover page. +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-Agg-ABNF] _OData Aggregation ABNF Construction Rules Version 4.0._ See link in "[Additional artifacts](#AdditionalArtifacts)" section on cover page. ###### [OData-CSDL] -_OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/os/odata-csdl-json-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html. -_OData Common Schema Definition Language (CSDL) XML Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/os/odata-csdl-xml-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html. +_OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01._ +See link in "[Related work](#RelatedWork)" section on cover page. + +_OData Common Schema Definition Language (CSDL) XML Representation Version 4.01._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-JSON] -_OData JSON Format Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Mark Biamonte. 11 May 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata-json-format/v4.01/os/odata-json-format-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html. +_OData JSON Format Version 4.01._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-Protocol] -_OData Version 4.01. Part 1: Protocol. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 23 April 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata/v4.01/os/part1-protocol/odata-v4.01-os-part1-protocol.html. -Latest stage: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html. +_OData Version 4.01. Part 1: Protocol._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-URL] -_OData Version 4.01. Part 2: URL Conventions. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 23 April 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata/v4.01/os/part2-url-conventions/odata-v4.01-os-part2-url-conventions.html. -Latest stage: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html. +_OData Version 4.01. Part 2: URL Conventions._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-VocAggr] _OData Aggregation Vocabulary._ diff --git a/odata-data-aggregation-ext/1 Introduction.md b/odata-data-aggregation-ext/1 Introduction.md index 87b31a18f..855df345b 100644 --- a/odata-data-aggregation-ext/1 Introduction.md +++ b/odata-data-aggregation-ext/1 Introduction.md @@ -13,6 +13,7 @@ This specification adds aggregation functionality to the Open Data Protocol (ODa This specification defines the following terms: - _Aggregatable Expression_ – an [expression](#Expression) resulting in a value of an [aggregatable primitive type](#AggregatablePrimitiveType) +- _Aggregate Expression_ – argument of the `aggregate` [transformation](#Transformationaggregate) or [function](#Functionaggregate) defined in [section ##AggregationAlgorithm] - _Aggregatable Primitive Type_ – a primitive type other than `Edm.Stream` or subtypes of `Edm.Geography` or `Edm.Geometry` - _Data Aggregation Path_ – a path that consists of one or more segments joined together by forward slashes (`/`). Segments are names of declared or dynamic structural or navigation properties, or type-cast segments consisting of the (optionally qualified) name of a structured type that is derived from the type identified by the preceding path segment to reach properties declared by the derived type. - _Expression_ – derived from the `commonExpr` rule (see [OData-ABNF](#ODataABNF)) @@ -27,7 +28,7 @@ The following non-exhaustive list contains variable names that are used througho - $x$ – an instance in a hierarchical collection, called a node - $p,q,r$ – paths - $S,T$ – transformation sequences -- $α$ – aggregate expression, defined [below](#AggregationAlgorithm) +- $α$ – aggregate expression, defined in [section ##AggregationAlgorithm] - $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths) - $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths) - $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined [below](#SimpleGrouping) diff --git a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md index 5e7f7157f..b26323932 100644 --- a/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md +++ b/odata-data-aggregation-ext/3.1 Fundamentals of Input and Output Sets.md @@ -32,7 +32,7 @@ The definitions of italicized terms made in this section are used throughout thi ### ##subsubsec Type, Structure and Context URL -All input sets and output sets in one transformation sequence are collections of the _input type_, that is the entity type or complex type of the first input set, or in other words, of the resource to which the transformation sequence is applied. The input type is determined by the entity model element identified within the metadata document by the context URL of that resource [OData-Protocol, section 10](#ODataProtocol). Individual instances in an input or output set can have a subtype of the input type. (See [example ##subinputtype].) The transformation sequence given as the `$apply` system query option is applied to the resource addressed by the resource path. The transformations defined below can have nested transformation sequences as parameters, these are then applied to resources that can differ from the current input set. +All input sets and output sets in one transformation sequence are collections of the _input type_, that is the entity type or complex type of the first input set, or in other words, of the resource to which the transformation sequence is applied. The input type is determined by the entity model element identified within the metadata document by the context URL of that resource [OData-Protocol, section 10](#ODataProtocol). Individual structured instances in an input or output set can have a subtype of the input type. (See [example ##subinputtype].) The transformation sequence given as the `$apply` system query option is applied to the resource addressed by the resource path. The transformations defined below can have nested transformation sequences as parameters, these are then applied to resources that can differ from the current input set. The _structure_ of an instance that occurs in an input or output set is defined by the names of the structural and navigation properties that the instance contains. Instances of an input type can have different structures, subject to the following rules: - Declared properties of the input type or a nested or related type thereof or of a subtype of one of these MUST have their declared type and meaning when they occur in an input or output set. @@ -53,7 +53,7 @@ If the first input set is a collection of entities from a given entity set, then ### ##subsubsec Sameness and Precedence -Input sets and output sets are not sets of instances in the mathematical sense but collections, because the same instance can occur multiple times in them. In other words: A collection contains values (which can be instances or primitive values), possibly with repetitions. The occurrences in the collection form a set in the mathematical sense. The _cardinality_ of a collection is the total number of occurrences in it. When this text describes a transformation algorithmically and stipulates that certain steps are carried out _for each_ instance in a collection, this means that the steps are carried out multiple times for the same instance if it occurs multiple times in the collection. +Input sets and output sets are not sets of instances in the mathematical sense but collections, because the same instance can occur multiple times in them. In other words: A collection contains values (which can be structured instances or primitive values), possibly with repetitions. The occurrences in the collection form a set in the mathematical sense. The _cardinality_ of a collection is the total number of occurrences in it. When this text describes a transformation algorithmically and stipulates that certain steps are carried out _for each_ instance in a collection, this means that the steps are carried out multiple times for the same instance if it occurs multiple times in the collection. A collection addressed by the resource path is returned by the service either as an ordered collection [OData-Protocol, section 11.4.10](#ODataProtocol) or as an unordered collection. The same applies to collections that are nested in or related to the addressed resource as well as to collections that are the result of evaluating an expression starting with `$root`, which occur, for example, as the first parameter of a [hierarchical transformation](#HierarchicalTransformations). @@ -78,10 +78,10 @@ The transformation [`orderby`](#Transformationorderby) defined below carries out A stable-sort does not necessarily produce a total order, the sorted collection may still contain two occurrences whose relative order does not matter. -The output set of a [basic aggregation](#BasicAggregation) transformation can contain instances of an entity type without entity-id. After a [`concat`](#Transformationconcat) transformation, different occurrences of the same entity can differ in individual non-declared properties. To account for such cases, the definition of sameness given in [OData-URL, section 5.1.1.1.1](#ODataURL) is refined here. Instances are _the same_ if +The output set of a [basic aggregation](#BasicAggregation) transformation can contain instances of an entity type without entity id. After a [`concat`](#Transformationconcat) transformation, different occurrences of the same entity can differ in individual non-declared properties. To account for such cases, the definition of sameness given in [OData-URL, section 5.1.1.1.1](#ODataURL) is refined here. Instances are _the same_ if - both are instances of complex types and both are null or both have the same structure and same values with null considered different from absent or -- both are instances of entity types without entity-id (transient entities, see [OData-Protocol, section 4.3](#ODataProtocol)) and both are null or both have the same structure and same values with null considered different from absent (informally speaking, they are compared like complex instances) or -- (1) both are instances of the same entity type with the same entity-id (non-transient entities, see [OData-Protocol, section 4.1](#ODataProtocol)) and (2) the structural and navigation properties contained in both have the same values (for non-primitive properties the sameness of values is decided by a recursive invocation of this definition). +- both are instances of entity types without entity id (transient entities, see [OData-Protocol, section 4.3](#ODataProtocol)) and both are null or both have the same structure and same values with null considered different from absent (informally speaking, they are compared like complex instances) or +- (1) both are instances of the same entity type with the same entity id (non-transient entities, see [OData-Protocol, section 4.1](#ODataProtocol)) and (2) the structural and navigation properties contained in both have the same values (for non-primitive properties the sameness of values is decided by a recursive invocation of this definition). - If this is fulfilled, the instances are called _complementary representations of the same non-transient entity_. If this case is encountered at some recursion level while the sameness of non-transient entities $u_1$ and $u_2$ is established, a merged representation of the entity $u_1=u_2$ exists that contains all properties of $u_1$ and $u_2$. But if the instances both occur in the last output set, services MUST represent each with its own structure in the response payload. - If the first condition is fulfilled but not the second, the instances are not the same and are called _contradictory representations of the same non-transient entity_. ([Example ##contradict] describes a use case for this.) @@ -93,7 +93,7 @@ Collections are _the same_ if there is a one-to-one correspondence $f$ between t This document specifies how a [data aggregation path](#DataAggregationPath) that occurs in a request is evaluated by the service. If such an evaluation fails, the service MUST reject the request. -For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to an instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. +For a data aggregation path to be a common expression according to [OData-URL, section 5.1.1](#ODataURL), its segments must be single-valued with the possible exception of the last segment, and it can then be evaluated relative to a structured instance. For the transformations defined in this document, a data aggregation path can also be evaluated relative to a collection $A$, even if it has arbitrary collection-valued segments itself. To this end, the following notation is used in the subsequent sections: If $A$ is a collection and $p$ a data aggregation path, optionally followed by a type-cast segment, the result of such a path evaluation is denoted by $\Gamma(A,p)$ and defined as the unordered concatenation, possibly containing repetitions, of the collections $γ(u,p)$ for each $u$ in $A$ that is not null. The function $γ(u,p)$ takes a non-null value and a path as arguments and returns a collection of structured instances or primitive values, depending on the type of the final segment of $p$. It is recursively defined as follows: 1. If $p$ is an empty path, let $B$ be a collection with $u$ as its single member and continue with step 9. diff --git a/odata-data-aggregation-ext/3.2 Basic Aggregation.md b/odata-data-aggregation-ext/3.2 Basic Aggregation.md index 71f5bddd5..8e71718f6 100644 --- a/odata-data-aggregation-ext/3.2 Basic Aggregation.md +++ b/odata-data-aggregation-ext/3.2 Basic Aggregation.md @@ -4,11 +4,11 @@ #### ##subsubsubsec Aggregation Algorithm -The `aggregate` transformation takes a comma-separated list of one or more _aggregate expressions_ as parameters and returns an output set with a single instance of the [input type](#TypeStructureandContextURL) without entity-id containing one property per aggregate expression, representing the aggregated value of the input set. +The `aggregate` transformation takes a comma-separated list of one or more [_aggregate expressions_](#AggregateExpression) as parameters and returns an output set with a single instance of the [input type](#TypeStructureandContextURL) without entity id containing one property per aggregate expression, representing the aggregated value of the input set. -An aggregate expression MUST have one of the types listed below. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $A$ of instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on the type of aggregate expression, $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(A)$ which the aggregate transformation evaluates to obtain the property value. +An aggregate expression MUST have one of the types listed below or be constructed with the [`from`](#Keywordfrom) keyword. To compute the value of the property for a given aggregate expression, the `aggregate` transformation first determines a collection $A$ of structured instances or primitive values, based on the input set of the `aggregate` transformation, and a path $p$ that occurs in the aggregate expression. Let $p_1$ denote a [data aggregation path](#DataAggregationPath) with single- or collection-valued segments and $p_2$ a type-cast segment. Depending on its type, the aggregate expression contains a path $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Each type of aggregate expression defines a function $f(A)$ which the aggregate transformation evaluates to obtain the property value. -The property is a dynamic property, except for a special case in type 4. In types 1 and 2, the aggregate expression MUST end with the keyword `with` and an aggregation method $g$. The aggregation method also determines the type of the dynamic property. In types 1, 2 and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword [`as`](#Keywordas) and an [alias](#TypeStructureandContextURL), which is then the name of the dynamic property. More aggregate expressions can be constructed with the [`from`](#Keywordfrom) keyword, see later section. +The property is a dynamic property, except for a special case in type 4. In types 1 and 2, the aggregate expression MUST end with the keyword `with` and an aggregation method $g$. The aggregation method also determines the type of the dynamic property. In types 1, 2, and 3 the aggregate expression MUST, and in type 4 it MAY, be followed by the keyword [`as`](#Keywordas) and an [alias](#TypeStructureandContextURL), which is then the name of the dynamic property. _Types of aggregate expressions:_ 1. A path $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a complex or entity or [aggregatable primitive type](#AggregatablePrimitiveType) whose values can be aggregated using the specified [aggregation method](#AggregationMethods) $g$, or $p=p_2$ if the input set can be aggregated using the [custom aggregation method](#CustomAggregationMethods) $g$. @@ -18,7 +18,7 @@ Let $f(A)=g(B)$ where $B$ is the collection consisting of the aggregatable expre 3. A path $p/{\tt\$count}$ (see [section ##AggregateExpressioncount]) with optional prefix $p/{}$ where $p=p_1$ or $p=p_2$ or $p=p_1/p_2$. Let $f(A)$ be the [cardinality](#SamenessandPrecedence) of $A$. 4. A path $p/c$ consisting of an optional prefix $p/{}$ with $p=p_1$ or $p=p_1/p_2$ where the last segment of $p_1$ has a structured type or $p=p_2$, and a [custom aggregate](#CustomAggregates) $c$ defined on the collection addressed by $p$. -Let $f(A)=c(A)$, if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property. +Let $f(A)=c(A)$, and if computation of the custom aggregate fails, the service MUST reject the request. In the absence of an alias, the name of the property MUST be the name of the custom aggregate, this is a dynamic property unless there is a declared property with that name, which is allowed by the `CustomAggregate` annotation. The custom aggregate also determines the type of the dynamic property. _Determination of $A$:_ @@ -28,7 +28,7 @@ Otherwise, let $q$ be the portion of $p$ up to and including the last navigation - If $q$ is non-empty, let $E=\Gamma(I,q)$ and remove duplicates from that entity collection: If [multiple representations of the same non-transient entity](#SamenessandPrecedence) are reached, the service MUST merge them into one occurrence in $E$ if they are complementary and MUST reject the request if they are contradictory. (See [example ##aggrconflict].) If [multiple occurrences of the same transient entity](#SamenessandPrecedence) are reached, the service MUST keep only one occurrence in $E$. - If $q$ is empty, let $E=I$. -Then, if $r$ is empty, let $A=E$, otherwise let $A=\Gamma(E,r)$, this consists of instances or primitive values, possibly with repetitions. +Then, if $r$ is empty, let $A=E$, otherwise let $A=\Gamma(E,r)$, this consists of structured instances or primitive values, possibly with repetitions. #### ##subsubsubsec Keyword `as` @@ -121,7 +121,7 @@ results in The standard aggregation method `max` can be applied to values with a totally ordered domain to return the largest of the values, or null if there are no values to be aggregated. -The result property will have the same type as the input property +The result property will have the same type as the input property. ::: example Example ##ex: @@ -141,7 +141,7 @@ results in ##### ##subsubsubsubsec Standard Aggregation Method `average` -The standard aggregation method `average` can be applied to numeric values to return the sum of the non-null values divided by the count of the values, or null if there are no values to be aggregated. +The standard aggregation method `average` can be applied to numeric values to return the sum of the values divided by the count of the values, or null if there are no values to be aggregated. The provider MUST choose a single type for the property across all instances of that type in the result that is capable of representing the aggregated values; either `Edm.Double` or `Edm.Decimal` with sufficient `Precision` and `Scale`. @@ -239,18 +239,18 @@ results in #### ##subsubsubsec Keyword `from` -The `from` keyword offers a shortcut for a sequence of [`groupby`](Transformationgroupby) and [`aggregate`](#Transformationaggregate) transformations with the pattern ${\tt groupby}(…,{\tt aggregate}(…{\tt\ as\ }A_1))/{\tt aggregate}(A_1{\tt\ with\ }…)$. +The `from` keyword offers a shortcut for a sequence of [`groupby`](Transformationgroupby) and [`aggregate`](#Transformationaggregate) transformations with the pattern ${\tt groupby}(…,{\tt aggregate}(…{\tt\ as\ }D_1))/{\tt aggregate}(D_1{\tt\ with\ }…)$. In the following $p_1,…,p_n$ are [data aggregation paths](#DataAggregationPath) that are allowed in `groupby` for [simple grouping](#SimpleGrouping). 1. If $α$ is an aggregate expression and $g$ is an aggregation method, then $$α{\tt\ from\ }p_1,…,p_n{\tt\ with\ }g$$ is an aggregate expression which evaluates to the value of property $D$ in the single instance in the output set of the following transformation sequence: - $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }A_1))/{\tt aggregate}(A_1{\tt\ with\ }g{\tt\ as\ }D).$$ + $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }D_1))/{\tt aggregate}(D_1{\tt\ with\ }g{\tt\ as\ }D).$$ 2. If $α=p/c{\tt\ from\ }…$ is an aggregate expression that starts with a custom aggregate $c$, optionally prefixed with a path $p$ as in type 4 in the [aggregation algorithm](#AggregationAlgorithm), and that optionally continues with `from` and `with` clauses, then $$α{\tt\ from\ }p_1,…,p_n$$ is an aggregate expression which evaluates to the value of property $c$ in the single instance in the output set of the following transformation sequence: - $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }A_1))/{\tt aggregate}(p/c).$$ + $${\tt groupby}((p_1,…,p_n),{\tt aggregate}(α{\tt\ as\ }D_1))/{\tt aggregate}(p/c).$$ Aggregate expressions constructed by these rules MUST be followed in the `aggregate` transformation by the keyword `as` and an [alias](#TypeStructureandContextURL). These rules can be applied repeatedly and lead to multiple `from` clauses in one aggregate expression. @@ -289,6 +289,23 @@ GET /service/Sales?$apply=groupby((Time),aggregate(Forecast)) ``` ::: +::: example +⚠ Example ##ex: the maximal daily average for sales of a product +``` +GET /service/Sales?$apply=aggregate(Amount with average from Time,Product/Name + with max as MaxDailyAverage) +``` +results in +```json +{ + "@context": "$metadata#Sales(MaxDailyAverage)", + "value": [ + { "MaxDailyAverage@type": "Decimal", "MaxDailyAverage": 8 } + ] +} +``` +::: + ### ##subsubsec Transformation `concat` The `concat` transformation takes two or more parameters, each of which is a sequence of set transformations. @@ -319,7 +336,7 @@ The output set of `concat` has a mixed form consisting of the structures imposed ### ##subsubsec Transformation `groupby` -The `groupby` transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the [input type](#TypeStructureandContextURL) without properties and without entity-id. +The `groupby` transformation takes one or two parameters where the second is a list of set transformations, separated by forward slashes to express that they are consecutively applied. If the second parameter is not specified, it defaults to a single transformation whose output set consists of a single instance of the [input type](#TypeStructureandContextURL) without properties and without entity id. #### ##subsubsubsec Simple Grouping @@ -329,7 +346,7 @@ The algorithmic description of this transformation makes use of the following de The output set of the groupby transformation is constructed in five steps. 1. [For each](#SamenessandPrecedence) instance $u$ in the input set, a projection is computed that contains only the grouping properties. This projection is $s_G(u,e)$ and the function $s_G(u,p)$ takes an instance and a path relative to the input set as arguments and is computed recursively as follows: - - Let $v$ be an instance of the type of $u$ without properties and without entity-id. + - Let $v$ be an instance of the type of $u$ without properties and without entity id. - For each structural or navigation property $q$ of $u$: - If $u$ has a subtype of the type addressed by $p$ and $q$ is only declared on that subtype, let $p'=p/p''/q$ where $p''$ is a type-cast to the subtype, otherwise let $p'=p/q$. - If $p'$ occurs in $G$, let $v[q]=u[q]$. @@ -457,8 +474,8 @@ The leveled hierarchy of the first rollup has 2 levels, the one of the second ha Example ##ex: answering the second question in [section ##ExampleUseCases] ``` GET /service/Sales?$apply=groupby((rollup(Customer/Country,Customer/Name), - rollup(Product/Category/Name,Product/Name)), - aggregate(Amount with sum as Total)) + rollup(Product/Category/Name,Product/Name)), + aggregate(Amount with sum as Total)) ``` results in seven entities for the finest grouping level ```json diff --git a/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md b/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md index 82fbdd4f8..6ec415464 100644 --- a/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md +++ b/odata-data-aggregation-ext/3.3 Transformations Preserving the Input Set Structure.md @@ -25,7 +25,7 @@ The output set is constructed as follows: 6. Insert the current item of the loop into the output set in the order of $A$. 7. Continue the loop. -For example, if the input set consists of non-transient entities and the datastore contains an index ordered by the second parameter and then the entity-id, a service may implement this algorithm with $A=B$ ordered like this index. +For example, if the input set consists of non-transient entities and the datastore contains an index ordered by the second parameter and then the entity id, a service may implement this algorithm with $A=B$ ordered like this index. The order of the output set can be influenced with a subsequent [`orderby`](#Transformationorderby) transformation. diff --git a/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md b/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md index 5fd31523f..b890d57ab 100644 --- a/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md +++ b/odata-data-aggregation-ext/3.4 Transformations Changing the Input Set Structure.md @@ -104,7 +104,7 @@ Applying `outerjoin` instead would return an additional instance for product wit The `nest` transformation takes as parameters one or more transformation sequences followed by the `as` keyword followed by an [alias](#TypeStructureandContextURL). These aliases MUST NOT collide with names of properties in the input set or with other aliases introduced in the same nest transformation. -The output set consists of a single instance of the [input type](#TypeStructureandContextURL) without entity-id having one dynamic property per transformation sequence. The name of the dynamic property is the alias for this transformation sequence. The value of the dynamic property is the collection resulting from the transformation sequence applied to the input set. The dynamic property carries as control information the context URL of the transformed input set. +The output set consists of a single instance of the [input type](#TypeStructureandContextURL) without entity id having one dynamic property per transformation sequence. The name of the dynamic property is the alias for this transformation sequence. The value of the dynamic property is the collection resulting from the transformation sequence applied to the input set. The dynamic property carries as control information the context URL of the transformed input set. ::: example Example ##ex: diff --git a/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md b/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md index 93ac51e25..87419d64e 100644 --- a/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md +++ b/odata-data-aggregation-ext/3.5 Expressions Evaluable on a Collection.md @@ -11,9 +11,9 @@ These expressions are ### ##subsubsec Function `aggregate` -The `aggregate` function allows to use aggregated values in [expressions](#Expression). It takes a single parameter accepting an aggregate-function expression and returns the aggregated value of type `Edm.PrimitiveType` as the result from applying the aggregate-function expression on its input collection. +The `aggregate` function allows to use aggregated values in [expressions](#Expression). It takes a single parameter accepting an [aggregate expression](#AggregateExpression) and returns the aggregated value of type `Edm.PrimitiveType` as the result from applying the aggregate expression on its input collection. -An aggregate-function expression offers the same capabilities as an aggregate expression defined for the `aggregate` transformation except that it never includes an alias for introducing a dynamic property containing the aggregated value. More precisely, if $α$ is an aggregate expression, the function $p/{\tt aggregate}(α)$ or ${\tt\$these}/{\tt aggregate}(α)$ evaluates to the value of the property $D$ in the single instance of the output set that is produced when the transformation ${\tt aggregate}(α{\tt\ as\ }D)$ is applied with the input collection as input set. +More precisely, if $α$ is an aggregate expression, the function $p/{\tt aggregate}(α)$ or ${\tt\$these}/{\tt aggregate}(α)$ evaluates to the value of the property $D$ in the single instance of the output set that is produced when the transformation ${\tt aggregate}(α{\tt\ as\ }D)$ is applied with the input collection as input set. ::: example Example ##ex: Sales making up at least a third of the total sales amount. diff --git a/odata-data-aggregation-ext/6 Hierarchical Transformations.md b/odata-data-aggregation-ext/6 Hierarchical Transformations.md index 08d6c894a..d06b22181 100644 --- a/odata-data-aggregation-ext/6 Hierarchical Transformations.md +++ b/odata-data-aggregation-ext/6 Hierarchical Transformations.md @@ -208,10 +208,10 @@ Three cases are distinguished: Here paths are considered equal if their non-type-cast segments refer to the same model elements when evaluated relative to the input set (see [Example ##pathequals]). The function $a(u,v,x)$ takes an instance, a path and another instance as arguments and is defined recursively as follows: -1. If $u$ equals the special symbol $ε$, set $u$ to a new instance of the [input type](#TypeStructureandContextURL) without properties and without entity-id. +1. If $u$ equals the special symbol $ε$, set $u$ to a new instance of the [input type](#TypeStructureandContextURL) without properties and without entity id. 2. If $v$ contains only one segment other than a type cast, let $v_1=v$, and let $x'=x$, then go to step 6. 3. Otherwise, let $v_1$ be the first property segment in $v$, possibly together with a preceding type-cast segment, let $v_2$ be any type-cast segment that immediately follows, and let $v_3$ be the remainder such that $v$ equals the concatenated path $v_1/v_2/v_3$ where ${}/v_2$ may be absent. -4. Let $u'$ be an instance of the type of $v_1/v_2$ without properties and without entity-id. +4. Let $u'$ be an instance of the type of $v_1/v_2$ without properties and without entity id. 5. Let $x'=a(u',v_3,x)$. 6. If $v_1$ is single-valued, let $u[v_1]=x'$. 7. If $v_1$ is collection-valued, let $u[v_1]$ be a collection consisting of one item $x'$. diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 14cfc7dac..f58384f01 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -471,16 +471,16 @@ is equivalent to (with nested `groupby` transformations) ``` GET /service/Sales?$apply= groupby((Customer/Country), - groupby((Time),aggregate(Amount with sum as A1)) - /aggregate(A1 with average as A2)) - /aggregate(A2 with max as MaxDailyAveragePerCountry) + groupby((Time),aggregate(Amount with sum as D1)) + /aggregate(D1 with average as D2)) + /aggregate(D2 with max as MaxDailyAveragePerCountry) ``` and is equivalent to (with consecutive `groupby` transformations) ``` GET /service/Sales?$apply= - groupby((Customer/Country,Time),aggregate(Amount with sum as A1)) - /groupby((Customer/Country),aggregate(A1 with average as A2)) - /aggregate(A2 with max as MaxDailyAveragePerCountry) + groupby((Customer/Country,Time),aggregate(Amount with sum as D1)) + /groupby((Customer/Country),aggregate(D1 with average as D2)) + /aggregate(D2 with max as MaxDailyAveragePerCountry) ``` ::: @@ -725,7 +725,7 @@ is equivalent to the following (except that the property name is `Forecast` inst GET /service/Sales?$apply= groupby((Customer/Country), groupby((Time),aggregate(Forecast)) - /aggregate(Forecast with average as A1)) + /aggregate(Forecast with average as D1)) /aggregate(Forecast) ``` ::: diff --git a/odata-data-aggregation-ext/8 Conformance.md b/odata-data-aggregation-ext/8 Conformance.md index 8fae32566..e4885ef2e 100644 --- a/odata-data-aggregation-ext/8 Conformance.md +++ b/odata-data-aggregation-ext/8 Conformance.md @@ -20,34 +20,30 @@ The following documents are referenced in such a way that some or all of their c ###### [OData-ABNF] _ABNF components: OData ABNF Construction Rules Version 4.01 and OData ABNF Test Cases._ -See the link in "[Related work](#RelatedWork)" section on cover page. +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-Agg-ABNF] _OData Aggregation ABNF Construction Rules Version 4.0._ See link in "[Additional artifacts](#AdditionalArtifacts)" section on cover page. ###### [OData-CSDL] -_OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/os/odata-csdl-json-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html. -_OData Common Schema Definition Language (CSDL) XML Representation Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 11 May 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/os/odata-csdl-xml-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html. +_OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01._ +See link in "[Related work](#RelatedWork)" section on cover page. + +_OData Common Schema Definition Language (CSDL) XML Representation Version 4.01._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-JSON] -_OData JSON Format Version 4.01. Edited by Michael Pizzo, Ralf Handl, and Mark Biamonte. 11 May 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata-json-format/v4.01/os/odata-json-format-v4.01-os.html. -Latest stage: https://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html. +_OData JSON Format Version 4.01._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-Protocol] -_OData Version 4.01. Part 1: Protocol. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 23 April 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata/v4.01/os/part1-protocol/odata-v4.01-os-part1-protocol.html. -Latest stage: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html. +_OData Version 4.01. Part 1: Protocol._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-URL] -_OData Version 4.01. Part 2: URL Conventions. Edited by Michael Pizzo, Ralf Handl, and Martin Zurmuehl. 23 April 2020. OASIS Standard._ -https://docs.oasis-open.org/odata/odata/v4.01/os/part2-url-conventions/odata-v4.01-os-part2-url-conventions.html. -Latest stage: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html. +_OData Version 4.01. Part 2: URL Conventions._ +See link in "[Related work](#RelatedWork)" section on cover page. ###### [OData-VocAggr] _OData Aggregation Vocabulary._ From 1b5da528152847678a5c7bf6cc94523d5c97a4ad Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 17:18:53 +0200 Subject: [PATCH 024/116] References in variable list --- .../odata-data-aggregation-ext.html | 15 ++++++++------- .../odata-data-aggregation-ext.md | 8 ++++---- odata-data-aggregation-ext/1 Introduction.md | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 743aa2653..8857bdd11 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -527,20 +527,21 @@

    1.1
  • \(\Gamma(A,p)\) – the collection that results from evaluating a data aggregation path \(p\) relative to -a collection \(A\), defined below
  • +a collection \(A\), defined in section 3.1.3
  • \(γ(u,p)\) – the collection that results from evaluating a data aggregation path \(p\) relative to -an instance \(u\), defined below
  • +an instance \(u\), defined in section 3.1.3
  • \(\Pi_G(s)\) – a transformation of a collection that injects grouping properties into every instance of the -collection, defined below
  • +collection, defined in section +3.2.3.1
  • \(σ(x)\) – instance containing a grouping property that represents a node \(x\), defined below
  • +class="math inline">\(x\), defined in section 6.2.2

    1.1.3 diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 108e5ef7e..f0cf9f841 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -222,10 +222,10 @@ The following non-exhaustive list contains variable names that are used througho - $p,q,r$ – paths - $S,T$ – transformation sequences - $α$ – aggregate expression, defined in [section 3.2.1.1](#AggregationAlgorithm) -- $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths) -- $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths) -- $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined [below](#SimpleGrouping) -- $σ(x)$ – instance containing a grouping property that represents a node $x$, defined [below](#Transformationtraverse) +- $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined in [section 3.1.3](#EvaluationofDataAggregationPaths) +- $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined in [section 3.1.3](#EvaluationofDataAggregationPaths) +- $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined in [section 3.2.3.1](#SimpleGrouping) +- $σ(x)$ – instance containing a grouping property that represents a node $x$, defined in [section 6.2.2](#Transformationtraverse) ### 1.1.3 Document Conventions diff --git a/odata-data-aggregation-ext/1 Introduction.md b/odata-data-aggregation-ext/1 Introduction.md index 855df345b..dced88d23 100644 --- a/odata-data-aggregation-ext/1 Introduction.md +++ b/odata-data-aggregation-ext/1 Introduction.md @@ -29,10 +29,10 @@ The following non-exhaustive list contains variable names that are used througho - $p,q,r$ – paths - $S,T$ – transformation sequences - $α$ – aggregate expression, defined in [section ##AggregationAlgorithm] -- $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined [below](#EvaluationofDataAggregationPaths) -- $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined [below](#EvaluationofDataAggregationPaths) -- $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined [below](#SimpleGrouping) -- $σ(x)$ – instance containing a grouping property that represents a node $x$, defined [below](#Transformationtraverse) +- $\Gamma(A,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to a collection $A$, defined in [section ##EvaluationofDataAggregationPaths] +- $γ(u,p)$ – the collection that results from evaluating a [data aggregation path](#DataAggregationPath) $p$ relative to an instance $u$, defined in [section ##EvaluationofDataAggregationPaths] +- $\Pi_G(s)$ – a transformation of a collection that injects grouping properties into every instance of the collection, defined in [section ##SimpleGrouping] +- $σ(x)$ – instance containing a grouping property that represents a node $x$, defined in [section ##Transformationtraverse] ### ##subsubsec Document Conventions From e3e8ebb8fcdc47ff675fffdbacc282c3fbd19559 Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 17:37:43 +0200 Subject: [PATCH 025/116] Sharpen ancestors definition --- .../odata-data-aggregation-ext.html | 123 ++++++++---------- .../odata-data-aggregation-ext.md | 22 +--- .../5 Vocabulary for Data Aggregation.md | 2 +- odata-data-aggregation-ext/7 Examples.md | 20 --- 4 files changed, 56 insertions(+), 111 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index de8c1c2ab..d70102eec 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -4045,9 +4045,10 @@

    5.5 descendants of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a sub-hierarchy of the -hierarchy. The ancestors of a node are its parent nodes, the -parents of its parent nodes, and so on, until no more parent nodes -exist.

    +hierarchy. With a non-standard definition for root, not every node is +necessarily a descendant of a root node. The ancestors of a +node are its parent nodes, the parents of its parent nodes, and so on, +as long as they are a descendant of a root node.

    The term UpNode can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with UpNode and so on until a path to a root is @@ -6450,29 +6451,13 @@

    7.4 -

    the orphan nodes can appear as ancestors:

    -
    GET /service/SalesOrganizations?$apply=ancestors(
    -    $root/SalesOrganizations,SalesOrgHierarchy,NodeID,
    -    filter(ID eq 'Phobos South Pole'),keep start)
    -  &$select=ID
    -

    results in

    -
    {
    -  "@odata.context": "$metadata#SalesOrganizations(ID)",
    -  "value": [
    -    { "ID": "Phobos" },
    -    { "ID": "Phobos South Pole" }
    -  ]
    -}
    -

    An analogous request for the ancestors of Atlantis would fail because -of the cycle.

    Mars, Phobos and Phobos South Pole can be made descendants of the root node by giving Mars a node identifier:

    -
    PATCH /service/SalesOrganizations('Mars')
    -Content-Type: application/json
    -
    -{ "NodeID": "Mars" }
    +
    PATCH /service/SalesOrganizations('Mars')
    +Content-Type: application/json
    +
    +{ "NodeID": "Mars" }

    An attempt to make the island orphan Atlantis a child of the root node fails, because it would introduce cycles into the hierarchy.

    @@ -6490,13 +6475,13 @@

    7.4
    GET /service/Sales?$apply=filter(Amount le 1)
         /aggregate(Amount with sum as Total)

    means "filter first, then aggregate", and results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Total)",
    -  "value": [
    -    { "Total@odata.type": "Decimal", "Total": 2 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Total)",
    +  "value": [
    +    { "Total@odata.type": "Decimal", "Total": 2 }
    +  ]
    +}

    Using filter within $apply does not preclude using it as a normal system query option.

    @@ -6506,16 +6491,16 @@

    7.4 aggregate(Amount with sum as Total)) &$filter=Total ge 4

    results in

    -
    {
    -  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    -  "value": [
    -    { "Product": { "Name": "Paper" },
    -      "Total@odata.type": "Decimal", "Total": 4 },
    -    { "Product": { "Name": "Sugar" },
    -      "Total@odata.type": "Decimal", "Total": 4 }
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Sales(Product(Name),Total)",
    +  "value": [
    +    { "Product": { "Name": "Paper" },
    +      "Total@odata.type": "Decimal", "Total": 4 },
    +    { "Product": { "Name": "Sugar" },
    +      "Total@odata.type": "Decimal", "Total": 4 }
    +  ]
    +}

    Example 115: Revisiting example 16 for using the @@ -6536,18 +6521,18 @@

    7.4
    GET /service/Cities?$apply=groupby((Continent/Name,Country/Name),
                                 aggregate(Population with sum as TotalPopulation))

    results in

    -
    {
    -  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    -                                      TotalPopulation)",
    -  "value": [
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    -    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    -      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    -    ...
    -  ]
    -}
    +
    {
    +  "@odata.context": "$metadata#Cities(Continent(Name),Country(Name),
    +                                      TotalPopulation)",
    +  "value": [
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "China" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1412000000 },
    +    { "Continent": { "Name": "Asia" }, "Country": { "Name": "India" },
    +      "TotalPopulation@odata.type": "Int32", "TotalPopulation": 1408000000 },
    +    ...
    +  ]
    +}

    Example 117: all countries with megacities and their continents

    @@ -6620,22 +6605,22 @@

    7.4
    GET /service/Sales?$apply=groupby((Product/Category/ID),
                           nest(groupby((Customer/ID)) as Customers))

    results in

    -
    {
    -  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    -  "value": [
    -    { "Product": { "Category": { "ID": "PG1" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] },
    -    { "Product": { "Category": { "ID": "PG2" } },
    -      "Customers@odata.context": "#Sales(Customer(ID))",
    -      "Customers": [ { "Customer": { "ID": "C1" } },
    -                     { "Customer": { "ID": "C2" } },
    -                     { "Customer": { "ID": "C3" } } ] }
    -  ]
    -}
    +
    {
    +  "@odata.context":"$metadata#Sales(Product(Category(ID)),Customers())",
    +  "value": [
    +    { "Product": { "Category": { "ID": "PG1" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] },
    +    { "Product": { "Category": { "ID": "PG2" } },
    +      "Customers@odata.context": "#Sales(Customer(ID))",
    +      "Customers": [ { "Customer": { "ID": "C1" } },
    +                     { "Customer": { "ID": "C2" } },
    +                     { "Customer": { "ID": "C3" } } ] }
    +  ]
    +}


    8 diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index aa036b7b4..c1adf2b80 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -2419,7 +2419,7 @@ The recursive hierarchy is described in the model by an annotation of the entity The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. -A node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, until no more parent nodes exist. +A node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. With a non-standard definition for root, not every node is necessarily a descendant of a root node. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, as long as they are a descendant of a root node. The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to a root is constructed. @@ -4376,26 +4376,6 @@ true orphan|Phobos South Pole|Phobos South Pole|Phobos unreachable orphan|Venus|Venus| island orphan|Atlantis|Atlantis|Atlantis -the orphan nodes can appear as ancestors: -``` -GET /service/SalesOrganizations?$apply=ancestors( - $root/SalesOrganizations,SalesOrgHierarchy,NodeID, - filter(ID eq 'Phobos South Pole'),keep start) - &$select=ID -``` -results in -```json -{ - "@odata.context": "$metadata#SalesOrganizations(ID)", - "value": [ - { "ID": "Phobos" }, - { "ID": "Phobos South Pole" } - ] -} -``` - -An analogous request for the ancestors of Atlantis would fail because of the cycle. - Mars, Phobos and Phobos South Pole can be made descendants of the root node by giving Mars a node identifier: ```json PATCH /service/SalesOrganizations('Mars') diff --git a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md index b419ea8ea..e22de5aa8 100644 --- a/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md +++ b/odata-data-aggregation-ext/5 Vocabulary for Data Aggregation.md @@ -178,7 +178,7 @@ The recursive hierarchy is described in the model by an annotation of the entity The term `RecursiveHierarchy` can only be applied to entity types, and MUST be applied with a qualifier, which is used to reference the hierarchy in transformations operating on recursive hierarchies, in [grouping with `rolluprecursive`](#Groupingwithrolluprecursive), and in [hierarchy functions](#HierarchyFunctions). The same entity can serve as different nodes in different recursive hierarchies, given different qualifiers. -A node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, until no more parent nodes exist. +A node is a _child node_ of its parent nodes, a node without child nodes is a _leaf node_. Two nodes with a common parent node are _sibling nodes_ and so are two root nodes. The _descendants_ of a node are its child nodes, their child nodes, and so on, up to and including all leaf nodes that can be reached. A node together with its descendants forms a _sub-hierarchy_ of the hierarchy. With a non-standard definition for root, not every node is necessarily a descendant of a root node. The _ancestors_ of a node are its parent nodes, the parents of its parent nodes, and so on, as long as they are a descendant of a root node. The term `UpNode` can be used in hierarchical result sets to associate with each instance one of its ancestors, which is again annotated with `UpNode` and so on until a path to a root is constructed. diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index 02d4e0429..676aa8047 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -1363,26 +1363,6 @@ true orphan|Phobos South Pole|Phobos South Pole|Phobos unreachable orphan|Venus|Venus| island orphan|Atlantis|Atlantis|Atlantis -the orphan nodes can appear as ancestors: -``` -GET /service/SalesOrganizations?$apply=ancestors( - $root/SalesOrganizations,SalesOrgHierarchy,NodeID, - filter(ID eq 'Phobos South Pole'),keep start) - &$select=ID -``` -results in -```json -{ - "@odata.context": "$metadata#SalesOrganizations(ID)", - "value": [ - { "ID": "Phobos" }, - { "ID": "Phobos South Pole" } - ] -} -``` - -An analogous request for the ancestors of Atlantis would fail because of the cycle. - Mars, Phobos and Phobos South Pole can be made descendants of the root node by giving Mars a node identifier: ```json PATCH /service/SalesOrganizations('Mars') From 1aabf220f9a73137c49373a18903aa20dff72be4 Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 17:42:14 +0200 Subject: [PATCH 026/116] @odata.id -> @id --- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html | 2 +- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md | 2 +- odata-data-aggregation-ext/7 Examples.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index f214f2808..9a8ce6d2f 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -6438,7 +6438,7 @@

    7.4 class="sourceCode json">PUT /service/SalesOrganizations('Switzerland')/Superordinate/$ref Content-Type: application/json -{ "@odata.id": "SalesOrganizations('EMEA Central')" }

    +{ "@id": "SalesOrganizations('EMEA Central')" }

    results in 204 No Content.

    diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index e8f80ad9c..90ed0260b 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -4386,7 +4386,7 @@ Example 111: Move a sales organization Switzerland under the parent EMEA Central PUT /service/SalesOrganizations('Switzerland')/Superordinate/$ref Content-Type: application/json -{ "@odata.id": "SalesOrganizations('EMEA Central')" } +{ "@id": "SalesOrganizations('EMEA Central')" } ``` results in `204 No Content`. ::: diff --git a/odata-data-aggregation-ext/7 Examples.md b/odata-data-aggregation-ext/7 Examples.md index d430ea1cd..c3edd83b6 100644 --- a/odata-data-aggregation-ext/7 Examples.md +++ b/odata-data-aggregation-ext/7 Examples.md @@ -1352,7 +1352,7 @@ Example ##ex: Move a sales organization Switzerland under the parent EMEA Centra PUT /service/SalesOrganizations('Switzerland')/Superordinate/$ref Content-Type: application/json -{ "@odata.id": "SalesOrganizations('EMEA Central')" } +{ "@id": "SalesOrganizations('EMEA Central')" } ``` results in `204 No Content`. ::: From 79cf02030f628d57ab606cd2ce0683d9b6613c7b Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 17:53:25 +0200 Subject: [PATCH 027/116] indentation --- .../odata-data-aggregation-ext.html | 4 ++-- docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 9a8ce6d2f..499132a38 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -5328,7 +5328,7 @@

    7 "AggregatedSales": [ { "Total@type": "Decimal", "Total": 8 } ] }, { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14, "AggregatedSales@context": "#Sales(Total)", - "AggregatedSales": [ { "Total": null } ] }, + "AggregatedSales": [ { "Total": null } ] }, { "ID": "P1", "Name": "Sugar", "Color": "White", "TaxRate": 0.06, "AggregatedSales@context": "#Sales(Total)", "AggregatedSales": [ { "Total@type": "Decimal", "Total": 4 } ] } @@ -5446,7 +5446,7 @@

    7 "TotalAmount@type": "Decimal", "TotalAmount": 8 } ] }, { "Name": "Pencil", "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)", - "AggregatedSales": [ { "SalesCount": 0, "TotalAmount": null } ] }, + "AggregatedSales": [ { "SalesCount": 0, "TotalAmount": null } ] }, { "Name": "Sugar", "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)", "AggregatedSales": [ { "SalesCount": 2, diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md index 90ed0260b..0899746fe 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.md @@ -3242,7 +3242,7 @@ results in "AggregatedSales": [ { "Total@type": "Decimal", "Total": 8 } ] }, { "ID": "P4", "Name": "Pencil", "Color": "Black", "TaxRate": 0.14, "AggregatedSales@context": "#Sales(Total)", - "AggregatedSales": [ { "Total": null } ] }, + "AggregatedSales": [ { "Total": null } ] }, { "ID": "P1", "Name": "Sugar", "Color": "White", "TaxRate": 0.06, "AggregatedSales@context": "#Sales(Total)", "AggregatedSales": [ { "Total@type": "Decimal", "Total": 4 } ] } @@ -3369,7 +3369,7 @@ results in "TotalAmount@type": "Decimal", "TotalAmount": 8 } ] }, { "Name": "Pencil", "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)", - "AggregatedSales": [ { "SalesCount": 0, "TotalAmount": null } ] }, + "AggregatedSales": [ { "SalesCount": 0, "TotalAmount": null } ] }, { "Name": "Sugar", "AggregatedSales@context": "#Sales(SalesCount,TotalAmount)", "AggregatedSales": [ { "SalesCount": 2, From a2a1331eb345283a8cf0f24e6d92eb8310e31956 Mon Sep 17 00:00:00 2001 From: D024504 Date: Fri, 26 May 2023 17:56:13 +0200 Subject: [PATCH 028/116] indentation --- .../odata-data-aggregation-ext.html | 10 +++++----- .../odata-data-aggregation-ext.md | 10 +++++----- odata-data-aggregation-ext/1 Introduction.md | 6 +++--- odata-data-aggregation-ext/7 Examples.md | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html index 8857bdd11..1a67b79a8 100644 --- a/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html +++ b/docs/odata-data-aggregation-ext/odata-data-aggregation-ext.html @@ -596,7 +596,7 @@

    2 data model (for sufficiently capable data providers)
  • Means for the provider to annotate what data can be aggregated, and in which way, allowing consumers to avoid asking questions that the -provider cannot answer.
  • +provider cannot answer

    Implementing any of these two aspects is valuable in itself independent of the other, and implementing both provides additional @@ -612,7 +612,7 @@

    2

    Example 2: The following diagram depicts a simple model that is used throughout this document.

    - +