37

I'm trying to create a call to odata webservice that would select only distinct values of some property. Is there any good example of how to do this?

igorti
  • 3,828
  • 3
  • 22
  • 29
  • possible duplicate of [The method Distinct is not supported](http://stackoverflow.com/questions/13127173/the-method-distinct-is-not-supported) – nlips Dec 13 '14 at 12:27

2 Answers2

32

Currently the OData protocol doesn't support the distinct operator, or any other operator which would help with such query (assuming you're looking for disctinct values of a primitive property on some entity). You should be able to workaround this by implementing a service operation on the server which performs such query on the underlying provider which usually has that ability. And then the client can call that service operation instead.

Vitek Karas MSFT
  • 13,130
  • 1
  • 34
  • 30
25

Note: I know this is old, but still shows up in search responses. There is now a good solution to this problem.

In OData v4, there is support for $apply, amongst other things, $apply can be used to return a distinct set of fields from a result set.

See related discussion: Applying Distinct to ODataQuery

from the OData v4 spec,
The query option $apply takes a sequence of set transformations, separated by forward slashes to express that they are consecutively applied, e.g. the result of each transformation is the input to the next transformation $apply is a function that requires

tl;dr;
Basically, if I have a table that has many fields, but I want to return just the distinct records of a number of specific fields the first step is to identify the distinct columns, lets call them Name and Id.
Simply use the $apply function to get a distinct result set containing just the Names and Id columns with syntax like this:

http://url.to/tableresource?$apply=groupby((Name,Id))

Lets do this by example, TuberInspections has 1000s of rows, but only a few contractors, I want to display the names of the contractors in a drop down list to use in a filtering query.

My distinct columns are ContractorName and ContractorId

GET /odata/TuberInspections?$apply=groupby((ContractorName,ContractorId)) HTTP/1.1
Host: localhost:1230

Response:
    { 
        "@odata.context": "http://localhost:1230/odata/$metadata#TuberInspections(ContractorName,ContractorId)",
        "value": [
            {
                "@odata.id": null,
                "ContractorId": 11534,
                "ContractorName": "Kermit d'Frog"
            },
            {
                "@odata.id": null,
                "ContractorId": 11539,
                "ContractorName": "Carlos Spicy Wiener"
            },
            {
                "@odata.id": null,
                "ContractorId": 16827,
                "ContractorName": "Jen Gelfling"
            }
        ]
    }

If your data structure is no flat enough, there is limited OOTB support for retrieving simple nested results structures.

If his data structure is hard to consume and you have control over the API, then you should consider creating a custom Function to return the specific recordset that you desire, that is trivial exercise that has many different options available to you, but outside of the scope of this response.

This response is directed at how to query an existing OData service for distinct values, not how to modify a service to provide such information.

Currently only a single navigation path is supported with the following syntax. The above query can also be written as:

GET /odata/TuberInspections?$apply=groupby((Contractor/EntityID,Contractor/Initials))
Host: localhost:1230

Response:
    { 
         "@odata.context": "http://localhost:1230/odata/$metadata#TuberInspections(Contractor(EntityID,Initials))",
         "@odata.count": 3,
         "value": [
             {
                 "@odata.id": null,
                 "Contractor": {
                     "@odata.id": null,
                     "Initials": "KdF",
                     "EntityID": 11534
                 }
             },
             {
                 "@odata.id": null,
                 "Contractor": {
                     "@odata.id": null,
                     "Initials": "SW",
                     "EntityID": 11539
                 }
             },
             {
                 "@odata.id": null,
                 "Contractor": {
                     "@odata.id": null,
                     "Initials": "Jen",
                     "EntityID": 16827
                 }
             }
         ]
    } 

There are more advanced (complicated) variations to nested or stacked $apply transformations that include returning a count of the number of unique rows that correspond to each of the distinct results.

Including a $count of distinct records within the group is sort of documented here System Query Option $apply - groupby and in response to OData v4 groupby with $count

GroupBy supports an aggregate extension, so we can use this in the previous URL, remember to alias the projected column name.

groupby((Contractor/EntityID,Contractor/Initials),aggregate($count as Items))

the response now includes the count:

       {
            "@odata.id": null,
            "Items": 260,
            "Contractor": {
                "@odata.id": null,
                "Initials": "SW",
                "EntityID": 11539
            }
        },

For more information please post specific questions and tag with OData-v4 and you'll find all the help you need :)

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • That seems akin to using select a,b group by a,b to get what should be select distinct a,b. I still like the concept of a distinct operator - I don't want to implement a data transformation service. – absmiths Sep 09 '19 at 15:21
  • In it's essence, distinct and group by take the same server resources and are the same computation, it makes sense to only have one response type to cover the two scenarios. The issue has been how to define the standard response within the specification. In many cases where a distinct query is used, it can be useful to have the count of items that match, if the structure is not altered, then one has to assume as part of the OData spec, that each row has a unique id at the corresponding resource, but because we have only requested the unique values, there is no unique key. – Chris Schaller Sep 09 '19 at 23:41
  • If you have control over the service code, then by all means create a function that returns just the distinct record set that you desire :) this response is more around how to query an existing or 3rd party API for a distinct record set. I am 100% behind custom endpoints to facilitate ease of use at the client end. – Chris Schaller Sep 09 '19 at 23:43
  • @ChrisSchaller in the second to last paragraph, you mention being able to also return the count of the unique rows. could you show me what that url would look like? having trouble finding it in the odata documentation – A. Dixon Oct 28 '20 at 16:45
  • @A.Dixon just use the standard `aggregate` component of the `groupby` function, it's detailed here https://stackoverflow.com/a/58620281/1690217 but I'll add an example into my answer – Chris Schaller Oct 29 '20 at 01:33
  • Just for some closure (for me) @A.Dixon I use this pattern a lot with faceted search interfaces, so when showing the list of facets (or slices) to choose from I include the the count of expected items. – Chris Schaller Oct 29 '20 at 23:49
  • @ChrisSchaller that’s what I’m needing it for. To show the expected number of child properties for the filter. See my question here: https://stackoverflow.com/q/64392114/9069327 – A. Dixon Oct 30 '20 at 01:15
  • I tried using this with Dynamics 365 OData but it doesn't seem to work. It just ignores the `$apply` function and returns the full result set. – FullStackOverflowDev Jan 14 '21 at 15:25
  • 1
    $apply is not in the list of supported query options for Dynamics 365: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/odata#supported-features-from-the-odata-specification – Chris Schaller Jan 14 '21 at 20:46