The Query Builder Blog Series: Part 2 – Designing a Resource Schema

This blog series follows the journey of building the recently released Interactive Google Ads Query Builder tool. Part 1 of this series outlined what we’ll be covering in the series as well as the rationale behind publishing this content. Part 2 will focus on designing a detailed JSON resource schema that will serve as the canonical data set for the Interactive Query Builder Angular application.

Background

As mentioned in Part 1, one of the major benefits of the new Interactive Query Builder is that it provides real-time feedback detailing why fields may or may not be selectable in a given clause of a Google Ads Query Language (GAQL) query.

For example, let’s say you are constructing a GAQL query with ad_group as the main resource in the FROM clause. Both segments.conversion_action and metrics.absolute_top_impression_percentage are selectable on the ad_group resource. However, taking a look at the detailed reference documentation for segments.conversion_action, we can see that there is a list of “Selectable With” fields, and that list does not include metrics.absolute_top_impression_percentage. Therefore, those two fields are incompatible. Regardless of what resource is in the FROM clause, if one of those two fields is present in the query, we know that the other cannot be. That is why metrics.absolute_top_impression_percentage is no longer selectable in the Interactive Query Builder once segments.conversion_action is selected.

Rather than trying to piece together all of this logic at runtime with various back-and-forth server calls, we thought it would be beneficial to feed that data into the application with static JSON files containing the resource schema. What might that optimal schema look like?

Schema Design (definition at the end of the blog post)

A GAQL string requires a single resource in the FROM clause. Given that constraint, the top level JSON schema will be a map from resources to detailed schemas for each resource. For example, the ad_group entry in our schema will look like this:



{

"ad_group": {

"name": "ad_group",
"display_name": "Ad Group",
"description": "An ad group.",
// Array of all attribute and attributed resource fields.
"attributes": [
"ad_group.ad_rotation_mode",
"ad_group.base_ad_group",
"ad_group.campaign",
...
"campaign.ad_serving_optimization_status",
"campaign.advertising_channel_sub_type",
"campaign.advertising_channel_type",
...
"customer.auto_tagging_enabled",
"customer.call_reporting_setting.call_conversion_action",
"customer.call_reporting_setting.call_conversion_reporting_enabled",
...
],
// Array of all metrics selectable with ad_group.
"metrics": [...],
// Array of all segments selectable with ad_group.
"segments": [...],
// Expanded info for all items listed in attributes, metrics, and segments arrays.
"fields": {...}

}




The crux of this enhanced schema is the fields entry. The keys of this object will be all of the attributes, metrics, and segments of the top level resource (e.g. ad_group). The value of each item in this object will be objects containing detailed information about that given field, as well as an additional field called incompatible_fields, an array of the fields that are incompatible with the given field. For example, the metrics.phone_impressions entry of the fields object would look like this:





"metrics.phone_impressions": {
"field_details": {
"name": "metrics.phone_impressions",
"category": "METRIC",
"selectable": true,
"filterable": true,
"sortable": true,
"data_type": "INT64",
"is_repeated": false,
"type_url": "",
"description": "Number of offline phone impressions.",
"enum_values": [],
"selectable_with": [
"ad_group",
"ad_group_ad",
"campaign",
"customer",
"extension_feed_item",
"segments.ad_network_type",
"segments.click_type",
"segments.date",
"segments.day_of_week",
"segments.interaction_on_this_extension",
"segments.keyword.ad_group_criterion",
"segments.keyword.info.match_type",
"segments.keyword.info.text",
"segments.month",
"segments.month_of_year",
"segments.quarter",
"segments.week",
"segments.year"
]
},
"incompatible_fields": [
"segments.slot",
"segments.device",
"segments.external_conversion_source",
"segments.conversion_action_category",
"segments.conversion_lag_bucket",
"segments.hour",
"segments.conversion_action_name",
"segments.conversion_action",
"segments.conversion_adjustment",
"segments.conversion_or_adjustment_lag_bucket"
]
},




The recursive nature of the schema may seem somewhat redundant, as some fields will appear in multiple resources. However, we will ultimately divide this main schema into individual JSON files for each resource to decrease load times, and we will only retrieve a single resource-specific schema depending on the resource in the FROM clause.


Schema Definition

For reference, the full schema definition is below:



interface ResourceSchema {
name: string; // the name of the resource
display_name: string; // the display name of the resource
description: string; // the description of the resource
attributes: string[]; // the resource's fields (including attributed resource fields)
metrics: string[]; // available metrics when the resource is in the FROM clause
segments: string[]; // available segments when the resource is in the FROM clause
fields: { // detailed info about all fields, metrics, and segments
[key: string]: {
field_details: FieldDetails; // details about the field (defined below)
incompatible_fields: string[]; // fields that are incompatible with the current field
}
};
}

interface FieldDetails {
name: string; // the name of the field
category: string; // the field's category (e.g. ATTRIBUTE, METRIC, SEGMENT)
selectable: boolean; // whether or not the field is allowed to be placed in the SELECT clause
filterable: boolean; // whether or not the field is allowed to be placed in the WHERE clause
sortable: boolean; // whether or not the field is allowed to be placed in the ORDER BY clause
data_type: string; // the field's data type
is_repeated: boolean; // whether or not the field is a repeated field
type_url: string; // the field's type_url
description: string; // the field's description
enum_values: string[]; // possible enum values if the field is of type ENUM
selectable_with: string[]; // the list of field the current field is selectable with
}


Conclusion

With that, we now have designed an expanded resource schema containing detailed field information and a list of incompatible fields for each field, which we can use in our Angular application. In part 3, we’ll discuss how to create this schema using the GoogleAdsFieldService.
Hopefully this has deepened your understanding of and shown you what is possible with the Google Ads API. If you have any questions or need additional help, contact us via the forum or at [email protected].