AdGroupCriterionOperation - Update ListingGroupType UNIT to SUBDIVISION

139 views
Skip to first unread message

Jebron Lames

unread,
Feb 19, 2024, 11:17:00 AM2/19/24
to Google Ads API and AdWords API Forum
Hello,

I am trying to get a better idea of how I can modify the structure of the existing product_group_view for a specific ad group.

This is a bit of a loaded response, so I will try to be as detailed as possible.

How can I correctly identify the "others" node when converting a UNIT to a Subdivision?
version: 14.1 REST

Context: 
I am able to add partitions as children Subdivided Nodes via the Google Ads API.
I am able to remove partitions  that are children of subdivided nodes via the Google Ads API.
----current case I'm working on----
user wishes to subdivide a unit node, so convert to Subdivision (Reason for post)**.

(screen shots of my app's current UI) - campaign was created and added a UNIT partition. Now I want to subdivide it and turn it into a Subdivision that holds product Ids.

add_subdivision.png
Screenshot 2024-02-19 191606.png

My current process is
remove UNIT node | create Subdivision Node & everything else case | add selected children nodes.

//ISSUE
I am getting an error that the 'everything else node' already exists. This also flags that my subdivided node needs an "others case". Meanwhile the node does get removed (haha), so I have to re-add it again .

Here is my process:
1. before the mutations I am checking:
if we need to create a subdivision.

2. if the node is type UNIT, perform a remove operation on that selected node.
 if (partitionIsUnit && partitionData.length > 0) {
      createSubdivision = true;
      // prepare for removal operation
      removePartitionsOperation.push(selectedNode?
    }

3. re-create a similar partition under a temp id, remove cpc_bid_micros prop, and type is now Subdivision. Also create "others" case partition with temp id, ad cpc_micros and type is UNIT connected to same parent. We dynamically determine the case_value based on the selected node to be partitioned.
// create new node with mostly similar details but now as Subdivision
const updateSelectedNodeType = new resources.AdGroupCriterion({
    resource_name: tempSubdivisionResource,
    ad_group: adGroupResource,
    listing_group: {
      parent_ad_group_criterion: parentNode,
      type: enums.ListingGroupType.SUBDIVISION,
      case_value: caseValue,
    },
    negative: false,
    status: enums.AdGroupCriterionStatus.ENABLED,
  });
//create everything else node. set cpc micros and type is UNIT
  const everythingElseNode = new resources.AdGroupCriterion({
    resource_name: everythingElseResource,
    ad_group: adGroupResource,
    cpc_bid_micros: toMicros(0.4),
    listing_group: {
      parent_ad_group_criterion: parentNode,
      type: enums.ListingGroupType.UNIT,
      case_value: everythingElseCaseValue,
    },
    negative: false,
    status: enums.AdGroupCriterionStatus.ENABLED,
  });

  const updatePartitionToSubdivision =
    {
      entity: "ad_group_criterion",
      operation: "create",
      resource: {
        ...updateSelectedNodeType,
      },
    };

  createSubdivisionOperation.push(updatePartitionToSubdivision);

  const createEverythingElseNodeOperation =
    {
      entity: "ad_group_criterion",
      operation: "create",
      resource: {
        ...everythingElseNode,
      },
    };
  createSubdivisionOperation.push(createEverythingElseNodeOperation);

perform operations in order of:
 I.remove, 
II.create subdivision, 
III. others case, 
IV. children of new subdivision.
await customer.adGroupCriteria.remove(removePartitionsOperation);
const partitionMutationOperation = [
      ...createSubdivisionOperation,
      ...addPartitionOperation,
    ];
await customer.mutateResources(partitionMutationOperation);

Am I also expected to find the "everything else node" in the product_group_view and delete it and how can I identify I am deleting the correct "others" node?

Would really appreciate some insight into your recommended steps for updating a UNIT node to a SUBDIVISION.

kind regards,
Michael H.


Google Ads API Forum Advisor

unread,
Feb 19, 2024, 3:26:43 PM2/19/24
to dumbfr...@gmail.com, adwor...@googlegroups.com
Hi,

Thank you for reaching out to the Google Ads API support team.

By reviewing your concern, I understand that you want to modify the structure of the existing product_group_view for a specific ad group. I agree that you need to change the node’s ListingGroupType from UNIT to SUBDIVISION in order to continue with the subdivision you want to achieve.

Please find the response to your queries respectively.


How can I correctly identify the "others" node when converting a UNIT to a Subdivision?

Please refer to the ad_group_criterion resource to get the information on the nodes. 


I am getting an error that the 'everything else node' already exists. This also flags that my subdivided node needs an "others case". Meanwhile the node does get removed (haha), so I have to re-add it again .

I understand that you are converting your unit node to subdivision. From the provided code snippet, I could see that the subdivision has only everythingElse node. Please note that the subdivision must always be completely partitioned, which must contain a node representing 'Other'. Could you please try adding the 'Other' node to the subdivision.


Am I also expected to find the "everything else node" in the product_group_view and delete it and how can I identify I am deleting the correct "others" node?   

Please refer to the ad_group_criterion resource to get the information on the nodes.

You may also refer to this code, if you want to replace the existing listing group tree on an ad group.

Hope this helps.
 
This message is in relation to case "ref:!00D1U01174p.!5004Q02rzEgV:ref"

Thanks,
 
Google Logo Google Ads API Team


Jebron Lames

unread,
Feb 20, 2024, 11:17:19 AM2/20/24
to Google Ads API and AdWords API Forum
So, after I successfully remove the node and begin to recreate it as a subdivision with children, I am getting an error at index 1 where it says the 'listing_group already exists'. This is my "other's node" operation.

//new subdivided node
const updateSelectedNodeType = new resources.AdGroupCriterion({
    resource_name: tempSubdivisionResource,
    ad_group: adGroupResource,
    listing_group: {
      parent_ad_group_criterion: parentNode,
      type: enums.ListingGroupType.SUBDIVISION,
      case_value: caseValue,
    },
    negative: false,
    status: enums.AdGroupCriterionStatus.ENABLED,
  });
//everything else node

  const everythingElseNode = new resources.AdGroupCriterion({
    resource_name: everythingElseResource,
    ad_group: adGroupResource,
    cpc_bid_micros: toMicros(0.4),
    listing_group: {
      parent_ad_group_criterion: parentNode,
      type: enums.ListingGroupType.UNIT,
      case_value: everythingElseCaseValue,
    },
    negative: false,
    status: enums.AdGroupCriterionStatus.ENABLED,
  });
 //partition object with set cpc.
        const newPartition = new resources.AdGroupCriterion({
          ad_group: `customers/${customer}/adGroups/${adGroupId}`,
          resource_name: criterionResourceOperation,
          cpc_bid_micros: toMicros(0.4),
          negative: false,
          status: enums.AdGroupCriterionStatus.ENABLED,
          listing_group: {
            parent_ad_group_criterion: parentAdGroupCriterion,
            type: enums.ListingGroupType.UNIT,
            case_value: caseValue,
          },
        });


0//tempid -1
 const updatePartitionToSubdivision: MutateOperation<resources.IAdGroupCriterion> =
    {
      entity: "ad_group_criterion",
      operation: "create",
      resource: {
        ...updateSelectedNodeType,
      },
    };

1//tempid -2 - ERROR - LISTING_GROUP ALREADY EXISTS
  const createEverythingElseNodeOperation: MutateOperation<resources.IAdGroupCriterion> =
    {
      entity: "ad_group_criterion",
      operation: "create",
      resource: {
        ...everythingElseNode,
      },
    };
 
2//tempid -3
 const addPartitionNodes: MutateOperation<resources.IAdGroupCriterion> =
          {
            entity: "ad_group_criterion",
            operation: "create",
            resource: newPartition,
          };


How do I find a node that doesn't currently exist in my active tree, remove it and make sure I am not removing the wrong node? 

I tried to query:
    query = `
        SELECT
        ad_group_criterion.criterion_id,
        ad_group_criterion.listing_group.case_value.${caseValue},
        ad_group_criterion.listing_group.parent_ad_group_criterion,
        ad_group_criterion.listing_group.path,
        ad_group_criterion.listing_group.type
        FROM product_group_view
        WHERE ad_group_criterion.listing_group.type = 'UNIT'
        AND ad_group_criterion.listing_group.case_value.${caseValue} IS NULL
        AND ad_group_criterion.listing_group.parent_ad_group_criterion = '${selectedNode?.listing_group?.parent_ad_group_criterion}'
        `;

with this line:  AND ad_group_criterion.listing_group.parent_ad_group_criterion = '${selectedNode?.listing_group?.parent_ad_group_criterion}'

I get the one partition from my active tree that is already the "others node" for an already subdivided partition (done during creation).

when I remove the line targeting parent criterion, I am left with about 900 partitions. So I am really interested in learning how this works. In terms of the data relationship and structure, how does one identify the "others" node of a specific partition when 1. the other's node does not currently exist in the current product_group_view where ad_group.id = ${adgroupId}?

my tree is like this for said ag_group > product_group_view:
I already have a brand node subdivided by product item id.
I now want to do the same the other unit node(**).
S = Subdivision
U = UNIT
** = convert UNIT to SUBDIVISION operation

                                                       all products(S)
                                                                |
                                   **brand(U) -------------------------brand (S)
                                                                |                   |
                                                                |                  item_id(U)
                                                                |                    |
                                                      ev else (all)           ev else (brand1) (U)

Can you please confirm that i have to find the "other's node" and delete it despite it not existing in my current tree? 
2. I saw in your response that I should querying the ad_group_criterion resource? not the product_group_view? Is there a specific advantage using the ad_group_criterion in this case?

Kind Regards,
Michael H.

Google Ads API Forum Advisor

unread,
Feb 20, 2024, 6:53:05 PM2/20/24
to dumbfr...@gmail.com, adwor...@googlegroups.com
Hi,

Thank you for getting back to us.

After reviewing your concern, I understand that you’ve encountered an LISTING_GROUP_ALREADY_EXISTS error. It means the listing group cannot be added to the ad group because it already exists.


How do I find a node that doesn't currently exist in my active tree, remove it and make sure I am not removing the wrong node? 
  • Please note that it is generally not recommended to delete nodes that don't exist in your active tree. Such attempts can lead to errors or unexpected behavior.
  • Please focus on creating the intended subdivision structure using the ad_group_criterion resource with appropriate listing group types and paths. 
Can you please confirm that I have to find the "other's node" and delete it despite it not existing in my current tree? 
  • You don't necessarily need to find and delete an "Others" node that doesn't exist in your current tree. While the "Others" node captures everything not explicitly covered by your subdivisions, it's automatically created if it doesn't exist when you add a new subdivision.
I saw in your response that I should query the ad_group_criterion resource? not the product_group_view? Is there a specific advantage using the ad_group_criterion in this case?
  • Yes, querying the ad_group_criterion resource is generally recommended for managing listing groups and subdivisions within ad groups. This resource provides more specific and relevant information about individual ad_group criteria, including their listing group details.
  • The product_group_view resource offers a broader view of product groups across campaigns, ad groups, and customer levels.

Jebron Lames

unread,
Feb 21, 2024, 2:51:13 PM2/21/24
to Google Ads API and AdWords API Forum
Thanks for getting back to me again. I just want to say I appreciate your responses and deeply apologize as this is quite a complex and sensitive process given the constraints.

in your response:
  • You don't necessarily need to find and delete an "Others" node that doesn't exist in your current tree. While the "Others" node captures everything not explicitly covered by your subdivisions, it's automatically created if it doesn't exist when you add a new subdivision.
Here are 3 separate examples (server-side logs) of trying to work with the API to convert a unit to a subdivision.
In each test I am:  In the 1st case, I pass just 2 objects (resource -1 & resource -2) as I was curious to see if I didn't add an "others" case, if it would be added. Each subsequent test I pass 3 objects to handle the "others" case.
Note: To create this campaign with the Google Ads Api I had to have a root Node -> everything else root node -> brand node > everything else in brand node -> product_item_id node.
 {
//SUBDIVISION
    entity: 'ad_group_criterion',
    operation: 'create',
    resource: {
      resource_name: 'customers/2595901092/adGroupCriteria/157149367644~-1',
      ad_group: 'customers/2595901092/adGroups/157149367644',
      listing_group: [Object],
      negative: false,
      status: 2
    }
  },
  {
// UNIT

    entity: 'ad_group_criterion',
    operation: 'create',
    resource: {
      resource_name: 'customers/2595901092/adGroupCriteria/157149367644~-2',
      ad_group: 'customers/2595901092/adGroups/157149367644',
      cpc_bid_micros: 400000,
      listing_group: [Object],
      negative: false,
      status: 2
    }
  },
  {
// UNIT

    entity: 'ad_group_criterion',
    operation: 'create',
    resource: v {
      ad_group: 'customers/2595901092/adGroups/157149367644',
      resource_name: 'customers/2595901092/adGroupCriteria/157149367644~-3',
      cpc_bid_micros: 400000,
      negative: false,
      status: 2,
      listing_group: [Object]
    }
  }

case_value (in order):
[
//brand
  { product_brand: { value: 'american fyre designs' } },

//everything else in brand - 1st test does not contain this.
  { product_brand: {} },

  {
// product under brand
    product_item_id: { value: 'shopify_us_5960181579931_37204494057627' }
  }
]
Test 1 - Do not add 'Others Node' because it is "automatically created" according to the previous explanation (2 operations)
Ed {
  errors: [
    Sd {
      error_code: { criterion_error: 108 },
      message: 'Subdivided listing groups must have an "others" case.',
      trigger: { int64_value: Long { low: -1, high: -1, unsigned: false } },
      location: [
    Pd { field_name: 'mutate_operations', index: 0 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' },
    Pd { field_name: 'type' }
  ],
    },
    Sd {
      error_code: { criterion_error: 106 },
      message: 'Ad group is invalid due to the listing groups it contains.',
      trigger: { int64_value: Long { low: -1, high: -1, unsigned: false } },
      location: [
    Pd { field_name: 'mutate_operations', index: 1 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' }
  ]
    }
  ],
request_id: '9V0lNLIqNwZz2PtuK5Dmtw'

Test 2 - Add everything else node again with case_value: {} (empty object) 
Ed {
  errors: [
    Sd {
      error_code: criterion_error: 108,
      message: 'Subdivided listing groups must have an "others" case.',
      trigger: { int64_value: Long { low: -1, high: -1, unsigned: false } },
      location: [
    Pd { field_name: 'mutate_operations', index: 0 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' },
    Pd { field_name: 'type' }
  ],
    },
    Sd {
      error_code: { field_error: 2 },
      message: 'The required field was not present.',
      location: [
    Pd { field_name: 'mutate_operations', index: 1 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' },
    Pd { field_name: 'case_value' }
  ]
    },
    Sd {
      error_code: criterion_error: 106,
      message: 'Ad group is invalid due to the listing groups it contains.',
      trigger: { int64_value: Long { low: -1, high: -1, unsigned: false } },
      location: [
    Pd { field_name: 'mutate_operations', index: 2 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' }
  ]
    }
  ],
  request_id: 'iQhvrUQYbKU3Zpc6o2V84g'
}

Test 3 - Set case_value property to empty object (i.e. product_brand: {} )  
Ed {
  errors: [
    Sd {
      error_code: criterion_error: 108,
      message: 'Subdivided listing groups must have an "others" case.',
      trigger: { int64_value: Long { low: -1, high: -1, unsigned: false } },
      location: [
    Pd { field_name: 'mutate_operations', index: 0 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' },
    Pd { field_name: 'type' }
  ],
    },
    Sd {
      error_code: criterion_error: 110,
      message: 'Listing group cannot be added to the ad group because it already exists.',
      location: [
    Pd { field_name: 'mutate_operations', index: 1 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' }
  ]
    },
    Sd {
      error_code: criterion_error: 106,
      message: 'Ad group is invalid due to the listing groups it contains.',
      trigger: { int64_value: Long { low: -1, high: -1, unsigned: false } },
      location: [
    Pd { field_name: 'mutate_operations', index: 2 },
    Pd { field_name: 'ad_group_criterion_operation' },
    Pd { field_name: 'create' },
    Pd { field_name: 'listing_group' }
  ]
    }
  ],
  request_id: 'Ko3EEIBggXM-f63Pyu6ofw'
}

The only thing I can think of is that when I initially created the partitions, I did create a single brand partition as all products > brands > item id.  So, in that initial process I had to create an "others" brand node. Now when I try to add a new "others" node and its case_value : {product_brand: {} }, its conflicting with the existing one in the ad_group.

The question would then be why does the others node for (ex.  dimplex -> everything else in dimple) conflict with the attempt to create "american fyre design" -> everything else in "american fyre design"? And then if this is the case, what is the way to add another partition under that catch. This is becoming increasingly more likely to be the case in my opinion.

I went over to the Google Ads UI and added a subdivision to the UNIT node and came back on my app and logged the new tree based on case_value.

Included in the log are the brands, single brand catch-all and root:
[
  {
    product_brand: { value: 'fire magic' },
    product_condition: null,
    product_type: null,
    product_item_id: null
  },
  {
    product_brand: { value: 'dimplex' },
    product_condition: null,
    product_type: null,
    product_item_id: null
  },
  {}, <-- root
  {
    product_brand: { value: null }, <-- catch all 
    product_condition: null,
    product_type: null,
    product_item_id: null
  },
  {
    product_brand: { value: 'primo' },
    product_condition: null,
    product_type: null,
    product_item_id: null
  },
 //other product_item_ids
]

It seems this may be the case, but between the "listing group already exists" and the "must have others case", I am seeking advice.

Thanks again and I will keep trying to figure this out. 

regards,
Michael 

Google Ads API Forum Advisor

unread,
Feb 22, 2024, 2:01:29 AM2/22/24
to dumbfr...@gmail.com, adwor...@googlegroups.com
Hi,

Thank you for getting back to us.

I understand that you are seeking assistance for LISTING_GROUP_ALREADY_EXISTS error and LISTING_GROUP_SUBDIVISION_REQUIRES_OTHERS_CASE error. Kindly double-check the ID of the listing group you are trying to add. Make sure it doesn't already exist in the ad group you are adding it to. You can check for this by looking for repeated creation operations for the same ID or name. 

As per the API documentation, a subdivision introduces a new level in the tree, while units are leaves of the tree. Each subdivision must always be completely partitioned, so it must contain a node representing 'Other'. To avoid error, add 'Other' node to the subdivision. You may refer to this guide for more information.

Regarding the above conflict between 'Others' nodes, the Google Ads API requires a single "Others" node per ad group. Having two nodes with the same name "Others" in the same ad group creates the conflict you're encountering. Since you want to create "Others" node for each brand, the earlier created 'Others' node might be redundant. You can remove it from the brands partition. If you want to keep separate "Others" nodes, ensure they have distinct case_value fields. This helps differentiate them in the API calls. Hope this helps.

Jebron Lames

unread,
Feb 22, 2024, 9:05:50 AM2/22/24
to Google Ads API and AdWords API Forum
I figured it out and can now convert a UNIT to SUBDIVISION with additional UNIT nodes. Firstly, thank you for getting back each time. 

For those whom I hope this helps...

first error:  I was pointing the "others" node parent resource to that of the root node criterion_id and not the new subdivision.

second error:  I was subdividing the partition by product_item_id, I incorrectly configured my code to mimic the case_value property of the selected node and not what it was going to be subdivided by.

The example is the order of how I am doing it (order of operations).  I want to note though that I only create a subdivision if a user decides to subdivide a node (i.e. brand or product type) that is currently a UNIT.  case_value for all nodes are dynamically determined based on the selectedNode and to be added UNIT nodes. If it's already a subdivision, then this process is skipped.

const updateSelectedNodeType = new resources.AdGroupCriterion({
    resource_name: tempSubdivisionResource,
    ad_group: adGroupResource,
    listing_group: {
      parent_ad_group_criterion: parentNode,
      type: enums.ListingGroupType.SUBDIVISION,
      case_value: caseValue,
    },
    negative: false,
    status: enums.AdGroupCriterionStatus.ENABLED,
  });

  const everythingElseNode = new resources.AdGroupCriterion({
    resource_name: everythingElseResource,
    ad_group: adGroupResource,
    cpc_bid_micros: toMicros(0.4),
    listing_group: {
      parent_ad_group_criterion: tempSubdivisionResource,
      type: enums.ListingGroupType.UNIT,
      case_value: everythingElseCaseValue,
    },
    negative: false,
    status: enums.AdGroupCriterionStatus.ENABLED,
  });

  const updatePartitionToSubdivision: MutateOperation<resources.IAdGroupCriterion> =
    {
      entity: "ad_group_criterion",
      operation: "create",
      resource: {
        ...updateSelectedNodeType,
      },
    };

  createSubdivisionOperation.push(updatePartitionToSubdivision);

  const createEverythingElseNodeOperation: MutateOperation<resources.IAdGroupCriterion> =
    {
      entity: "ad_group_criterion",
      operation: "create",
      resource: {
        ...everythingElseNode,
      },
    };
  createSubdivisionOperation.push(createEverythingElseNodeOperation);
//partition object with set cpc, but can be changed for user.
        const newPartition = new resources.AdGroupCriterion({
          ad_group: `customers/${customer}/adGroups/${adGroupId}`,
          resource_name: criterionResourceOperation,
          cpc_bid_micros: toMicros(0.4),
          negative: false,
          status: enums.AdGroupCriterionStatus.ENABLED,
          listing_group: {
            parent_ad_group_criterion: parentAdGroupCriterion,
            type: enums.ListingGroupType.UNIT,
            case_value: caseValue,
          },
        });
//finalize for operation
        const addPartitionNodes: MutateOperation<resources.IAdGroupCriterion> =
          {
            entity: "ad_group_criterion",
            operation: "create",
            resource: newPartition,
          };

        //push for batch operation
        addPartitionOperation.push(addPartitionNodes);


mutation: [
  {

    entity: 'ad_group_criterion',
    operation: 'create',
    resource: {
      resource_name: 'customers/2595901092/adGroupCriteria/157149367644~-1',
      ad_group: 'customers/2595901092/adGroups/157149367644',
      listing_group: [Object],
      negative: false,
      status: 2
    }
  },
  {
    entity: 'ad_group_criterion',
    operation: 'create',
    resource: {
      resource_name: 'customers/2595901092/adGroupCriteria/157149367644~-2',
      ad_group: 'customers/2595901092/adGroups/157149367644',
      cpc_bid_micros: 400000,
      listing_group: [Object],
      negative: false,
      status: 2
    }
  },
  {
    operation: 'create',
    resource: v {
      ad_group: 'customers/2595901092/adGroups/157149367644',        
      resource_name: 'customers/2595901092/adGroupCriteria/157149367644~-3',
      cpc_bid_micros: 400000,
      negative: false,
      status: 2,
      listing_group: [Object]
    }
  }
] [
//case values:
  { product_brand: { value: 'bromic' } },
  { product_item_id: {} },
  {
    product_item_id: { value: 'shopify_us_5695534235803_36317916823707' }
  }
]
Operation Success! Subdivision created.

Purely comes down to a misunderstanding on my part. Thanks for working with me on this.

Regards,
Michael H.



Reply all
Reply to author
Forward
0 new messages