Skip to main content
Unique indexes allow you to enforce uniqueness constraints on your data, ensuring that no two records share the same value for a given field or edge relationship.

Unique Index on Node Fields

To enforce uniqueness on a node field, use the UNIQUE INDEX keywords before the field name:
N::User {
    UNIQUE INDEX email: String,
    name: String,
    age: U8
}
This creates a unique secondary index on the email field, preventing duplicate email addresses across all User nodes.

Syntax

N::NodeType {
    UNIQUE INDEX field_name: Type,
    UNIQUE INDEX field_name: Type DEFAULT default_value,
}

Combining with Default Values

You can combine unique indexes with default values:
N::Document {
    UNIQUE INDEX slug: String,
    INDEX view_count: I32 DEFAULT 0,
    content: String,
}

Querying by Unique Index

Nodes with unique indexes can be queried using object syntax, just like regular indexes:
QUERY getUserByEmail(email: String) =>
    user <- N<User>({email: email})
    RETURN user

Unique Edges

Unique edges ensure that only one edge of a given type can exist between two specific nodes. This is useful for relationships that should be singular, such as “best friend” or “primary contact”.

Syntax

E::EdgeType UNIQUE {
    From: NodeType,
    To: NodeType,
}
The UNIQUE modifier is placed between the edge name and the edge body.

Example

N::User {
    UNIQUE INDEX username: String,
    name: String,
}

E::BestFriend UNIQUE {
    From: User,
    To: User,
}
With this schema, each user can only have one BestFriend edge pointing to another user. Attempting to create a second BestFriend edge from the same user will enforce the uniqueness constraint.

Unique Edges with Properties

Unique edges can also have properties:
E::PrimaryContact UNIQUE {
    From: Company,
    To: Person,
    Properties: {
        assigned_date: String,
        priority: I32
    }
}

Complete Example

N::Employee {
    UNIQUE INDEX employee_id: String,
    UNIQUE INDEX email: String,
    name: String,
    department: String,
}

N::Project {
    UNIQUE INDEX project_code: String,
    name: String,
}

E::LeadOf UNIQUE {
    From: Employee,
    To: Project,
}

E::WorksOn {
    From: Employee,
    To: Project,
}
In this example:
  • Each employee has a unique employee_id and email
  • Each project has a unique project_code
  • Each project can only have one lead (via the UNIQUE edge constraint)
  • Employees can work on multiple projects (non-unique WorksOn edge)

Constraint Violations

When inserting data that violates a unique constraint, HelixDB will return an error. This applies to both unique node field indexes and unique edges.

Node Field Violations

If you attempt to insert a node with a value that already exists for a unique indexed field, the operation will fail:
// First insert succeeds
emp1 <- AddN<Employee>({
    employee_id: "EMP001",
    email: "[email protected]",
    name: "Alice"
})

// Second insert with same email fails with an error
emp2 <- AddN<Employee>({
    employee_id: "EMP002",
    email: "[email protected]",  // Duplicate - constraint violation
    name: "Bob"
})
The second operation will return an error indicating that a node with that email already exists.

Edge Violations

Similarly, attempting to create a duplicate unique edge will fail:
// First edge succeeds
lead1 <- AddE<LeadOf>::From(alice_id)::To(project_id)

// Second edge from the same node fails
lead2 <- AddE<LeadOf>::From(alice_id)::To(other_project_id)  // Constraint violation
Use unique constraints to enforce data integrity at the database level. Handle the returned errors in your application to provide appropriate feedback to users, such as “This email is already registered.”