Skip to content

Query table statistics

View on GitHub

Query a table to get aggregated statistics back for a specific field.

Image of Query table statistics sample

Use case

For example, a county boundaries table with population information can be queried to return aggregated results for total, average, maximum, and minimum population, rather than downloading the values for every county and calculating statistics manually.

How to use the sample

Pan and zoom to define the extent for the query. Use the "Include Cities Outside Extent" toggle to control whether the query only includes features in the visible extent. Use the "Include Cities Under 5M" toggle to filter the results to only those cities with a population greater than 5 million people. Tap "Query Statistics" to perform the query and view the statistics. The query will return population-based statistics from the combined results of all features matching the query criteria.

How it works

  1. Create a ServiceFeatureTable with a URL to the feature service.
  2. Create StatisticsQueryParameters using an array of StatisticDefinition objects. Depending on the state of the two toggles, additional parameters are set.
  3. Query the feature table by passing the parameters to queryStatistics(using:)
  4. Display each StatisticRecord in the returned QueryStatisticsResult.

Relevant API

  • ServiceFeatureTable
  • StatisticDefinition
  • StatisticRecord
  • StatisticsQueryParameters
  • StatisticsQueryResult
  • StatisticType

Tags

analysis, average, bounding geometry, filter, intersect, maximum, mean, minimum, query, spatial query, standard deviation, statistics, sum, variance

Sample Code

QueryTableStatisticsView.swift
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright 2025 Esri
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import ArcGIS
import SwiftUI

struct QueryTableStatisticsView: View {
    /// The view model for the sample.
    @State private var model = Model()

    /// The current viewpoint of the map view.
    @State private var viewpoint: Viewpoint?

    /// The statistic records resulting from a query.
    @State private var statisticRecords: [StatisticRecord] = []

    /// A Boolean value indicating whether queries will include cities outside
    /// of the map view's current extent.
    @State private var includesCitiesOutsideExtent = false

    /// A Boolean value indicating whether queries will include cities with
    /// populations under 5 million.
    @State private var includesCitiesUnder5M = false

    /// A Boolean value indicating whether there is an ongoing query operation.
    @State private var isQuerying = false

    /// A Boolean value indicating whether the settings view is showing.
    @State private var isShowingSettings = false

    /// A Boolean value indicating whether the statistic list is showing.
    @State private var isShowingStatistics = false

    /// An error thrown from a query.
    @State private var queryError: Error?

    var body: some View {
        MapView(map: model.map, viewpoint: viewpoint)
            .onViewpointChanged(kind: .boundingGeometry) { viewpoint = $0 }
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Button("Query Statistics") {
                        isQuerying = true
                    }
                    .disabled(isQuerying)
                    .task(id: isQuerying) {
                        // Queries the statistics when the button is pressed.
                        guard isQuerying else {
                            return
                        }
                        defer { isQuerying = false }

                        do {
                            let extent = includesCitiesOutsideExtent ? nil : viewpoint?.targetGeometry
                            statisticRecords = try await model.queryStatistics(
                                within: extent,
                                includesCitiesUnder5M: includesCitiesUnder5M
                            )

                            isShowingStatistics = true
                        } catch {
                            self.queryError = error
                        }
                    }
                    .sheet(isPresented: $isShowingStatistics) {
                        StatisticList(records: statisticRecords)
                            .presentationDetents([.medium, .large])
                    }

                    Spacer()

                    Button("Settings", systemImage: "gear") {
                        isShowingSettings = true
                    }
                    .labelStyle(.iconOnly)
                    .popover(isPresented: $isShowingSettings) {
                        Form {
                            Toggle("Include Cities Outside Extent", isOn: $includesCitiesOutsideExtent)
                            Toggle("Include Cities Under 5M", isOn: $includesCitiesUnder5M)
                        }
                        .frame(idealWidth: 320, idealHeight: 160)
                        .presentationCompactAdaptation(.popover)
                    }
                }
            }
            .errorAlert(presentingError: $queryError)
    }
}

/// A view that displays the statistics from a given list of statistic records.
private struct StatisticList: View {
    /// The statistic records containing the statistics to display.
    let records: [StatisticRecord]

    /// The action to dismiss the view.
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        NavigationStack {
            List(records, id: \.objectID) { record in
                Section {
                    let statistics = record.statistics.sorted(by: { $0.key < $1.key })
                    ForEach(statistics, id: \.key) { name, value in
                        if let integer = value as? Int {
                            LabeledContent(name, value: integer, format: .number)
                        } else if let double = value as? Double {
                            LabeledContent(name, value: double, format: .number)
                        }
                    }
                    .textSelection(.enabled)
                }
            }
            .navigationTitle("Statistics")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done") {
                        dismiss()
                    }
                }
            }
        }
    }
}

/// The view model for this sample.
@Observable
private final class Model: Sendable {
    /// A map with a streets basemap.
    let map: Map = {
        let map = Map(basemapStyle: .arcGISStreets)

        let envelope = Envelope(
            xRange: -14_803_800...5_233_700,
            yRange: -15_950_300...17_262_800,
            spatialReference: .webMercator
        )
        map.initialViewpoint = Viewpoint(boundingGeometry: envelope)

        return map
    }()

    /// The service feature table containing the data to query.
    private let serviceFeatureTable = ServiceFeatureTable(url: .sampleWorldCitiesLayer)

    /// The parameters to use in the statistics query.
    private let statisticsQueryParameters = StatisticsQueryParameters(
        // Creates a statistic definition for the "POP" field and each of the statistic types.
        statisticDefinitions: StatisticDefinition.StatisticType.allCases
            .map { .init(fieldName: "POP", statisticType: $0) }
    )

    init() {
        // Adds the table to the map as an operational layer.
        let featureLayer = FeatureLayer(featureTable: serviceFeatureTable)
        map.addOperationalLayer(featureLayer)
    }

    /// Queries the statistics of the data in the given extent.
    /// - Parameters:
    ///   - extent: The geometry to query within.
    ///   - includesCitiesUnder5M: A Boolean value indicating whether queries
    ///   will include cities with populations under 5 million.
    /// - Returns: The statistic records resulting from the query.
    func queryStatistics(
        within extent: Geometry?,
        includesCitiesUnder5M: Bool
    ) async throws -> [StatisticRecord] {
        // Sets up the parameters for the query.
        statisticsQueryParameters.geometry = extent
        statisticsQueryParameters.whereClause = includesCitiesUnder5M ? "" : "POP_RANK = 1"

        // Queries the feature table using the parameters.
        let result = try await serviceFeatureTable.queryStatistics(using: statisticsQueryParameters)

        // Gets the statistic records from the query result.
        let statisticRecords = Array(result.statisticRecords())

        return statisticRecords
    }
}

private extension StatisticRecord {
    /// The object identifier for this statistic record.
    var objectID: ObjectIdentifier { .init(self) }
}

private extension StatisticDefinition.StatisticType {
    /// All the statistic types.
    static var allCases: [Self] {
        return [average, count, maximum, minimum, standardDeviation, sum, variance]
    }
}

private extension URL {
    /// The URL to the "Cities" layer on the "SampleWorldCities" map service.
    static var sampleWorldCitiesLayer: URL {
        URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/SampleWorldCities/MapServer/0")!
    }
}

#Preview {
    QueryTableStatisticsView()
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.