Skip to content

Find Duplicate Data Tutorial

This means that both structured and unstructured data sources can have multiple objects within them. In a production BigID system there can be millions of objects so we need to filter.

Let’s use the has_duplicates filter to request objects that contain duplicate files. Because any number of objects in your estate could carry duplicates, this sweep runs against the streaming endpoint.

The stream returns a flat JSON array rather than a results wrapper, and it returns far fewer objects than an unfiltered sweep of the catalog. But what are the duplicates? Each duplicate object has a duplicate_id that represents a hash of the file. We can filter objects by this ID to find all the duplicates. Since one hash only ever matches a small number of files, that lookup is bounded, so the paginated /data-catalog endpoint is a good fit. Replace DUPLICATEID in the URL of the request below with the duplicate_id of the first object above to find its duplicates.

Now you have a list of the files that are duplicated, you can delete some of your unneeded copies to save on data storage costs.

# Duplicate Data Tutorial
import requests
import json
base_url = "https://developer.bigid.com/api/v1"
headers = {
"Authorization": "Bearer SAMPLE",
"Content-Type": "application/json"
}
# The cursor is derived from the sort field, so it must not change between pages.
CATALOG_SORT = '[{"field":"fullyQualifiedName","order":"asc"}]'
def stream_catalog_objects(page_size=1000, filter_expr=None):
"""Yield catalog objects via the streaming endpoint, following the cursor."""
url = f"{base_url}/data-catalog/objects/stream"
params = {
"limit": page_size,
"sort": CATALOG_SORT,
"offsetKey": "", # empty string starts at the beginning
}
if filter_expr:
params["filter"] = filter_expr
while True:
res = requests.get(url, headers=headers, params=params, timeout=300)
res.raise_for_status()
page = res.json() # a flat JSON array, not an object
if not page:
return
yield from page
if len(page) < page_size:
return # short page means we reached the end
# The cursor for the next page lives on the last object returned.
next_offset_key = page[-1].get("nextOffsetKey")
if not next_offset_key:
return
params["offsetKey"] = next_offset_key
# 1. Walk every catalog object
total = sum(1 for _ in stream_catalog_objects())
print("Total objects in catalog:", total)
# 2. Stream only the objects that have duplicates
duplicates = list(stream_catalog_objects(filter_expr='has_duplicates="true"'))
print("Objects with duplicates:", len(duplicates))
# Get the duplicate_id of the first object (for example)
duplicate_id = duplicates[0].get("duplicate_id")
# 3. Get all objects that share the same duplicate_id.
# This is a small, bounded lookup, so the paginated endpoint is a good fit.
response = requests.get(
f"{base_url}/data-catalog",
headers=headers,
params={"filter": f'duplicate_id="{duplicate_id}"'}
)
data = response.json()
print("Objects with same duplicate_id:", json.dumps(data, indent=2))
// Duplicate Data Tutorial
const baseUrl = "https://developer.bigid.com/api/v1";
const headers = {
"Authorization": "Bearer SAMPLE",
"Content-Type": "application/json"
};
// The cursor is derived from the sort field, so it must not change between pages.
const CATALOG_SORT = '[{"field":"fullyQualifiedName","order":"asc"}]';
// Yield catalog objects via the streaming endpoint, following the cursor.
async function* streamCatalogObjects({ pageSize = 1000, filter } = {}) {
let offsetKey = ""; // empty string starts at the beginning
while (true) {
const params = new URLSearchParams({
limit: String(pageSize),
sort: CATALOG_SORT,
offsetKey,
});
if (filter) params.set("filter", filter);
const res = await fetch(`${baseUrl}/data-catalog/objects/stream?${params}`, { headers });
if (!res.ok) throw new Error(`Catalog stream failed: ${res.status} ${res.statusText}`);
const page = await res.json(); // a flat JSON array, not an object
if (page.length === 0) return;
yield* page;
if (page.length < pageSize) return; // short page means we reached the end
// The cursor for the next page lives on the last object returned.
const { nextOffsetKey } = page[page.length - 1];
if (!nextOffsetKey) return;
offsetKey = nextOffsetKey;
}
}
// 1. Walk every catalog object
async function countAllCatalogObjects() {
let total = 0;
for await (const _ of streamCatalogObjects()) total++;
console.log("Total objects in catalog:", total);
return total;
}
// 2. Stream only the objects that have duplicates
async function getObjectsWithDuplicates() {
const duplicates = [];
for await (const obj of streamCatalogObjects({ filter: 'has_duplicates="true"' })) {
duplicates.push(obj);
}
console.log("Objects with duplicates:", duplicates.length);
return duplicates;
}
// 3. Get all objects that share the same duplicate_id.
// This is a small, bounded lookup, so the paginated endpoint is a good fit.
async function getObjectsByDuplicateId(duplicateId) { // Use duplicate id of desired object obtained above in step 2
console.log(`Fetching objects for duplicate_id: ${duplicateId}`);
const params = new URLSearchParams({ filter: `duplicate_id="${duplicateId}"` });
const res = await fetch(`${baseUrl}/data-catalog?${params}`, { headers });
const data = await res.json();
console.log("Objects with same duplicate_id:", JSON.stringify(data, null, 2));
return data;
}
// Run all 3 steps
(async () => {
await countAllCatalogObjects();
const duplicates = await getObjectsWithDuplicates();
if (duplicates.length > 0) {
await getObjectsByDuplicateId(duplicates[0].duplicate_id);
}
})();