Add Table from Hosted Service to Another and Create Relationship

6535
12
07-07-2022 03:36 AM
Clubdebambos
MVP Regular Contributor
17 12 6,535

Data can often be provided as a Shapefile with an accompanying file containing the attribute information, or surplus attributes to compliment those in the Shapefile, such as a CSV for example. This is the case for Census Data in Ireland, provided by the Central Statistics Office (CSO), results are available in CSV format and can be related to various boundary types in a polygon Shapefile. This blog will use a subset of the data for illustration purposes, we will focus on Electoral Divisions in County Kildare. The fields have been clipped from 799 available to the first 10, I'm not even sure AGOL could handle that amount of fields? Anyone know if there is a limit, or recommended limit for performance? The data used for the workflow outlined in the blog is available for download at the end of the post. The original data is available on the CSO website.

The ArcGIS API for Python provides no method for publishing a Shapefile and CSV together into one Feature Service from a zipped folder with the data represented as a single Hosted Service containing a Feature Layer and a Table. These have to be added to AGOL separately and published separately, unless I've completely missed something and you can correct me in the comments. This blog post will take a published Table and add as a Table to a Feature Service containing a single Feature Layer. A relationship will be created between the Feature Layer and the Table. While this can be achieved by publishing via ArcGIS Pro with a little preparation beforehand, here, we will use the ArcGIS API for Python only, not everyone has access to ArcGIS Pro. You might also have a situation where you do not want to overwrite a Feature Service just to add a Table.

So let's get the show on the road! We begin with the usual import of the GIS module, we require the FeatureLayerCollection module, and we connect to AGOL.

 

## import the GIS module
from arcgis import GIS
from arcgis.features import FeatureLayerCollection

## the GIS module acts as an entry point to your AGOL
agol = GIS("home")

 

Add the Shapefile zip to AGOL as an Item. This contains the County Kildare Electoral Division boundaries. The zipped folder is available for download at the end of the blog post (CSO_ED2016_Kildare.zip)

 

## path to zipped folder that contains the Shapefile
shp_zip = r"path\to\CSO_ED2016_Kildare.zip"

## add the Shapefile as an item to AGOL
shp_agol = agol.content.add(item_properties={}, data=shp_zip)

 

Add the CSV file to as an Item. The CSV contains census information (10 of the 799 ). The CSV file is available for download at the end of the blog post (SAPS2016_ED3409_Kildare.csv).

 

## path to csv file
csv_file = r"path\to\SAPS2016_ED3409_Kildare.csv"

## add as csv item in AGOL
csv_agol = agol.content.add(item_properties={}, data=csv_file)

 

We used the ContentManager add() method to add these items, you can find more information about this method here.

Publish the Shapefile Item as a Feature Service.

 

## publish hosted feature service
shp_fs = shp_agol.publish()

 

Publish the CSV Item as a Hosted Table.

 

## publishing properties so csv is added as a table and not a spatial layer.
csv_properties = {
    "type:" : "csv",
    "locationType" : "none"
}

## publish hosted table
csv_ht = csv_agol.publish(publish_parameters=csv_properties)

 

We used the Item publish() method to publish our Hosted Services, more information on the publish() method can be found here.

Clubdebambos_0-1657114189152.png

Our next task is to add the Table to the Feature Service

 

## Access the Table in the Hosted Service
tbl = csv_ht.tables[0]

## convert the Table properties to a Dictionary
tbl_def = dict(tbl.properties)

## get the item as a FeatureLayerCollection object
flc = FeatureLayerCollection.fromitem(shp_fs)

## add the Table definition to the Feature Service
flc.manager.add_to_definition({"tables" : [tbl_def]})

 

Check the Feature Service and see if the Table was added.

Clubdebambos_1-1657114341349.png

And check the Data tab to see that the fields have been added.

Clubdebambos_2-1657114370738.png

Notice that the data is missing. Essentially we have added the Table schema to our Feature Service. Next, we add/append the data from the original CSV.

 

## use analyze as per append docs when using a csv
csv_data = agol.content.analyze(
    item = csv_agol.id,
    file_type = "csv",
    geocoding_service = None,
    location_type = None
)

## reload the Feature Service with the Table added
shp_fs = agol.content.get(shp_fs.id)
## access the Table
tbl = shp_fs.tables[0]

## append in data
tbl.append(
    item_id = csv_agol.id,
    upload_format = "csv",
    source_info = csv_data,
    upsert = False
)

 

We used the ContentManager analyze() method to prepare the data for the append() method. More on the analyze() method can be found here, and the append() method for a Table object can be found here. Refresh or navigate back to the Data tab for the Table in the Feature Service and check if the data has been successfully added?

Clubdebambos_3-1657114581405.png

Looking good so far. Now we want to create a relationship between the Feature Layer and the Table within the Feature Service. In order to do this we need to define the relationship properties for both. There needs to be a field that contains common attributes in both layer and table. In the Feature Layer  this needs to be a field that contains unique attributes (primary key) and a field (foreign key) in the Table that uses one or multiple of each of the unique values from the Feature Layer field. This will determine if the relationship should be one-to-one or one-to-many. We have a one-to-one relationship here with the CSOED_34_1 Feature Layer field containing unique attributes and having exactly one match in the GEOGDESC field of the Table.

 

## get the Feature Layer and its ID
lyr = shp_fs.layers[0]
lyr_id = lyr.properties.id

## get the Table ID
tbl_id = tbl.properties.id

## a dictionary containing the relationship properties for the Feature Layer
lyr_rel_dict = {
    "name": "Layer_to_Table",
    "relatedTableId": int(tbl_id),
    "cardinality": "esriRelCardinalityOneToOne",
    "role": "esriRelRoleOrigin",
    "keyField": "CSOED_34_1",
    "composite": True
}

## a dictionary containing the relationship properties for the Table
tbl_rel_dict = {
    "name": "Table_to_Layer",
    "relatedTableId": int(lyr_id),
    "cardinality": "esriRelCardinalityOneToOne",
    "role": "esriRelRoleDestination",
    "keyField": "GEOGDESC",
    "composite": True
}

## update the relationship properties in the Feature Layer and Table
lyr.manager.add_to_definition({"relationships" : [lyr_rel_dict]})
tbl.manager.add_to_definition({"relationships" : [tbl_rel_dict]})

 

The cardinality is set as one-to-one, this means that each record in the spatial Feature Layer can have only one related record in the related Table. Setting the composite to True means each feature in the destination table is expected to be related to an origin feature.

Let's check that the relationship was created successfully. Open the data in a Map and click on a Polygon. The Popup Window should contain a link Show Related Data, click it.

Clubdebambos_0-1657115023721.png

Look at that! 

Clubdebambos_1-1657115083313.png

We have shown that the ArcGIS API for Python can be used to add a Table to a Feature Service that already exists. This can be useful if you do not have access to ArcGIS Pro or if you have a mature Feature Service that you don't want to overwrite but want to add a Table to the service. We also explored adding a relationship data between a Feature Layer and a Table within the same Feature Service.

Here' all the code...

## import the GIS module
from arcgis import GIS
from arcgis.features import FeatureLayerCollection

## the GIS module acts as an entry point to your AGOL
agol = GIS("home")

## path to zipped folder that contains the Shapefile
shp_zip = r"path\to\CSO_ED2016_Kildare.zip"

## add the Shapefile as an item to AGOL
shp_agol = agol.content.add(item_properties={}, data=shp_zip)

## path to csv file
csv_file = r"path\to\SAPS2016_ED3409_Kildare.csv"

## add as csv item in AGOL
csv_agol = agol.content.add(item_properties={}, data=csv_file)

## publish hosted feature service
shp_fs = shp_agol.publish()

## publishing properties so csv is added as a table and not a spatial layer.
csv_properties = {
    "type:" : "csv",
    "locationType" : "none"
}

## publish hosted table
csv_ht = csv_agol.publish(publish_parameters=csv_properties)

## Access the Table in the Hosted Service
tbl = csv_ht.tables[0]

## convert the Table properties to a Dictionary
tbl_def = dict(tbl.properties)

## get the item as a FeatureLayerCollection object
flc = FeatureLayerCollection.fromitem(shp_fs)

## add the Table definition to the Feature Service
flc.manager.add_to_definition({"tables" : [tbl_def]})

## use analyze as per append docs when using a csv
csv_data = agol.content.analyze(
    item = csv_agol.id,
    file_type = "csv",
    geocoding_service = None,
    location_type = None
)

## reload the Feature Service with the Table added
shp_fs = agol.content.get(shp_fs.id)
## access the Table
tbl = shp_fs.tables[0]

## append in data
tbl.append(
    item_id = csv_agol.id,
    upload_format = "csv",
    source_info = csv_data,
    upsert = False
)

## get the Feature Layer and its ID
lyr = shp_fs.layers[0]
lyr_id = lyr.properties.id

## get the Table ID
tbl_id = tbl.properties.id

## a dictionary containing the relationship properties for the Feature Layer
lyr_rel_dict = {
    "name": "Layer_to_Table",
    "relatedTableId": int(tbl_id),
    "cardinality": "esriRelCardinalityOneToOne",
    "role": "esriRelRoleOrigin",
    "keyField": "CSOED_34_1",
    "composite": True
}

## a dictionary containing the relationship properties for the Table
tbl_rel_dict = {
    "name": "Table_to_Layer",
    "relatedTableId": int(lyr_id),
    "cardinality": "esriRelCardinalityOneToOne",
    "role": "esriRelRoleDestination",
    "keyField": "GEOGDESC",
    "composite": True
}

## update the relationship properties in the Feature Layer and Table
lyr.manager.add_to_definition({"relationships" : [lyr_rel_dict]})
tbl.manager.add_to_definition({"relationships" : [tbl_rel_dict]})
12 Comments
jcarlson
MVP Esteemed Contributor

This is fantastic. Thank you for taking the time to write all this up, I will definitely be using parts of this process.

MehdiPira1
Esri Contributor

That's very helpful.

Thank you @Clubdebambos .

KimOllivier
Honored Contributor

A great supplement to the official documentation. I have been looking everywhere for an example just like this. I have one to many, so am looking forward to this simple change to see it working for me.

nate0102
Occasional Contributor

So much value in this article.

@Clubdebambos thank you immensely for taking the time to share!

ParkerWelch
Occasional Contributor

EDITED 1/9/2025 The feature service I was working with ended up having some issues that prevented other ArcGIS Online products to fail, specifically as part of ArcGIS Indoors. Per Chrome dev tools, I was receiving Status Code of 500 when trying to load a web application.

To fix everything, I ended up needing to download the hosted feature service, unpack locally, and start fresh by publishing a new feature service. instead of overwriting the old one. Same data, but this time a clean service, since I published the feature service with the table initially instead of using the method described below to append the table later. 

There is another commenter who mentioned that although the script appeared to work, there has been some buggy behavior after the fact, including a 500 status code.

Because of all this, I cannot recommend that others use this methodology. I will leave the script here in case someone wants to investigate, but again, I recommend trying another method if you need to append a table to a feature service.

Does anyone know how to get ESRI eyes on this? 


ORIGINAL POST

Came to say that I had a similar but not exactly the same use case for this and it was very helpful.

I had published a Feature Service with several spatial feature layers but wanted to add a standalone table to the service later on. I didn't want to Overwrite the entire service because I had a lot of apps and layer config in place. Since there was no table in the service to begin with, I couldn't use any of the append or update tools either.

The key in the script was the Feature Layer Collection object, which has an append method. So I staged the table, uploaded to AGO manually, then adapted the code a bit to append that table layer to an existing feature layer collection. Worked like a charm. Code below...

 

 

 

 

from arcgis import GIS
from arcgis.features import FeatureLayerCollection

#I ran this in ArcGIS Pro project Jupyter Notebook, so the "Home" value logs 
# into the same AGO org I am logged into
gis = GIS("Home")

#get table item
table_id = "{your table item id goes here}"
table_fs = gis.content.get(table_id)

#get target feature layer
target_id = "{your target feature layer item id}"
target_fs = gis.content.get(target_id )
target_flc = FeatureLayerCollection.fromitem(target_fs )

# I only have one table in this feature layer so I know it's index is 0. 
# If you have multiple tables in your feature layer, you'll want to get 
# the right index
table_def = dict(table_fs.tables[0].properties)

#the magical step
target_flc.manager.add_to_definition({"tables" : [table_def]})

 

 

 

 

 

KristoferJohnston
Emerging Contributor

Hi @ParkerWelch 

Was wondering if I could get your help, I have used the code you provided and it has worked on trial data, however when I implement this on existing network data (hosted feature class), the table does not show on the overview feature list, but when I do go onto ArcGIS Rest API the table does show. Likewise when I open on a web viewer the table in question does show.

Have you come across this issue as well? or know of any way we can get the table to show on the feature overview list. 

AustinAverill
Frequent Contributor

In response to @KristoferJohnston comment regarding the feature service not displaying the added table properly - check out @MarkC1 post at this link: https://community.esri.com/t5/arcgis-online-questions/feature-layer-added-to-feature-service-does-no....

 

It appears this is a sort of bug with the site that occurs whenever the feature service was published through Pro/Desktop. Even though the actual service definition of the item has changed, the GUI continues to display information based off of the originally published Service Definition from Pro. Following @MarkC1  solution seems to force the GUI to revert to querying the service definition directly rather than another source, providing the intended result. 

Iron_Mark
Regular Contributor

Hi @ParkerWelch 

I used you code to add the service definition from a hosted feature table/csv to a hosted feature layer.

I can see it adds it in the overview tab of the hosted feature layer and the data tab has the correct columns (empy, they'll need to be appended).

If I move from the data tab to the overview tab I get this error:

Unable to load https://Mygis.cloud.esriuk.com/server/rest/services/Hosted/TEST_Polygon/FeatureServer/3/metadata status: 500

 

Do you know what is the issue? I am also unable to then append the csv data into the table.

Any help would be greatly appreciated

 

ParkerWelch
Occasional Contributor

@Iron_Mark not really sure what could be happening there. a status code of 500 is not particularly helpful as I understand, since it can be a sort of catch all. I edited a comment above that recommends people not use this script for the use case I did, since it also encountered 500 status code when the service was pulled into other web applications. Best of luck in troubleshooting. 

vsingh10
Occasional Explorer

Hi all, using the code from this script I came up with a much simpler script that simply copies an existing table in the service instead of uploading the csv file. This is if you want to add a blank table and populate the data in the future. 

Linking my post on SE here in case anyone finds it helpful: https://gis.stackexchange.com/questions/409680/adding-an-additional-layer-to-existing-hosted-feature...

Iron_Mark
Regular Contributor

Hi @Clubdebambos ,

Have you recently tried your code?

It doesn't seem to work, even using your sample data.

Once you add the table definition to the feature service

flc.manager.add_to_definition({"tables" : [tbl_def]})

you get the following error when navigating from the data tab to the overview tab

Unable to load https://My-portal.cloud.esriuk.com/server/rest/services/Hosted/Testlayer/FeatureServer/1/metadata status: 404

You still see the table added to the service, but when you do the append it returns False.

Also, when adding the relationship to the feature layer using 

lyr.manager.add_to_definition({"relationships" : [lyr_rel_dict]})

It returns an error

Exception: Field 'relationships' cannot be updated.
(Error Code: 500)

This has been reported also by another user here in the community

Any clue?

Clubdebambos
MVP Regular Contributor

Hi @Iron_Mark,

Yes, I used it yesterday for a workflow and no issues and I just ran the example in this post and all good at 2.4.0 (with exception of deprecation warning for the add() method in favour of folders).

I never got the notifications of previous comments here so apologies for that, but I have never had any bugs or buggy behaviour downstream after adding a table to a live hosted feature service. I either grab an existing Table from another service, or create the JSON for a new table and apply that. There can be some catches every now and again to remove relationships from the table definition, but that will cause the script to fail before the table is added. If others have downstream bugs it would be a matter of aligning the table definition (JSON) to what your use/app expects.

From your URL, it seems like you are using Portal rather than AGOL and perhaps via a managed service with Esri UK? I have not tested with Portal and I only use in AGOL. I could test over the coming days in Portal and let you know.

All the best,

Glen

Contributors
About the Author
GIS Consultant with extensive experience utilising skills for GIS project management, quality map production, data management, geospatial analysis, standardisation, and workflow automation across a range of industries such as agriculture, oil & gas, telecommunications and engineering. Going beyond map production there is a passion for the technologies behind GIS, the databases, the programming languages, the analysis and statistical output, and continued professional development associated with GIS. You will mainly find me contributing in the Python and ArcGIS API for Python Communities.