Edge constraints

Applies to: SQL Server 2019 (15.x) and later Azure SQL Database Azure SQL Managed Instance

Edge constraints can be used to enforce data integrity and specific semantics on the edge tables in SQL Server graph database.

Edge Constraints

By default, edge tables don't enforce anything for the endpoints of the edge. That is, an edge in a graph database could connect any node to any other node, regardless of the type.

SQL Graph supports edge constraints, which enable users to add constraints to their edge tables, thereby enforcing specific semantics and also maintaining data integrity. When a new edge is added to an edge table with edge constraints, the Database Engine enforces that the nodes that the edge is trying to connect, exist in the proper node tables. It's also ensured that a node can't be dropped, if any edge is referencing that node.

Edge Constraint Clauses

A single edge constraint consists of one or more edge constraint clause(s).

CONSTRAINT constraint_name CONNECTION (cause1[, clause2...])
  • An edge constraint clause is a pair of node table names, separated by the TO keyword.
  • The first table name in the edge constraint clause is the name of the FROM node table for the edge relationship.
  • The second table name in the edge constraint clause is the name of the TO node table for the edge relationship.
  • The pair of table names therefore indicates the direction of the edge relationship.
  • As stated previously, an edge constraint can contain one or more edge constraint clauses.

Multiple constraints and clauses

  • Multiple edge constraints, defined for the same edge table, are enforced with an AND operator.
  • Multiple edge constraint clauses, defined within the same edge constraint, are enforced with an OR operator.

Consider the Supplier and Customer nodes in your graph. Each can be related to the Product node by a single, shared edge table: bought. The bought edge table supports Customer-(bought)->Product and Supplier-(bought)->Product relationship types. This can be accomplished using a single edge constraint with multiple edge constraint clauses.

Examples
CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product)

The above example shows one edge constraint, with one edge constraint clause. This constraint supports Customer-(bought)->Product. That is, inserting a bought edge relationship going from a Customer to Product would be allowed. If you try to insert any other combination of nodes, like Supplier-(bought)->Product, even though it may describe a valid relationship in the real world, would fail.

CONSTRAINT EC_BOUGHT CONNECTION (Supplier TO Product, Customer TO Product)

The above example defines one edge constraint with two edge constraint clauses. These constraint clauses allow the bought edge to contain either Supplier-(bought)->Product or Customer-(bought)->Product relationships. Inserting any other types of edge relationships into the bought table would fail.

CONSTRAINT EC_BOUGHT1 CONNECTION (Supplier TO Product)
CONSTRAINT EC_BOUGHT2 CONNECTION (Customer TO Product)

The above example shows two constraints on the same edge table, with each edge constraint specifying one constraint clause. In this situation, SQL would only allow inserts that satisfy BOTH edge constraint clauses simultaneously. This isn't possible. There's no node pair that can satisfy both edge constraint clauses. This edge constraint combination makes the edge table unusable.

For a detailed explanation of where multiple edge constraints can be used in a real-life scenario, see the example "Creating a new edge constraint on existing edge table, with new edge constraint clause" later in this page.

Indexes on edge constraints

Creating an edge constraint doesn't automatically create a corresponding index on $from_id and $to_id columns in the edge table. Manually creating an index on a $from_id, $to_id pair is recommended if you have point lookup queries or OLTP workload.

ON DELETE referential actions on edge constraints

Cascading actions on an edge constraint let users define the actions that the database engine takes when a user deletes the node(s), which the given edge connects. The following referential actions can be defined: NO ACTION The database engine raises an error when you try to delete a node that has connecting edge(s).

CASCADE When a node is deleted from the database, connecting edge(s) are deleted.

Work with edge constraints

You can define an edge constraint in SQL Server by using Transact-SQL. An edge constraint can be defined on a graph edge table only. To create, delete, or modify an edge constraint, you must have ALTER permission on the table.

Create edge constraints

The following examples show you how to create an edge constraint on new or existing tables.

Create an edge constraint on a new edge table

The following example creates an edge constraint on the bought edge table.

-- CREATE node and edge tables
CREATE TABLE Customer
   (
      ID INTEGER PRIMARY KEY
      ,CustomerName VARCHAR(100)
   )
AS NODE;
GO
CREATE TABLE Product
   (
      ID INTEGER PRIMARY KEY
      ,ProductName VARCHAR(100)
   )
AS NODE;
GO
CREATE TABLE bought
   (
      PurchaseCount INT
         ,CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product) ON DELETE NO ACTION
   )
   AS EDGE;

Define referential actions on a new edge table

The following example creates an edge constraint on the bought edge table and defines ON DELETE CASCADE referential action.

-- CREATE node and edge tables
CREATE TABLE Customer
   (
      ID INTEGER PRIMARY KEY
      ,CustomerName VARCHAR(100)
   )
AS NODE;
GO
CREATE TABLE Product
   (
      ID INTEGER PRIMARY KEY
      ,ProductName VARCHAR(100)
   )
AS NODE;
GO
CREATE TABLE bought
   (
      PurchaseCount INT
         ,CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product) ON DELETE CASCADE
   )
   AS EDGE;

Add edge constraint to an existing edge table

The following example uses ALTER TABLE to add an edge constraint to the bought edge table.

-- CREATE node and edge tables
CREATE TABLE Customer
   (
      ID INTEGER PRIMARY KEY
      , CustomerName VARCHAR(100)
   )
   AS NODE;
CREATE TABLE Product
   (
      ID INTEGER PRIMARY KEY
      , ProductName VARCHAR(100)
   )
   AS NODE;
GO
CREATE TABLE bought
   (
      PurchaseCount INT
   )
   AS EDGE;
GO
ALTER TABLE bought ADD CONSTRAINT EC_BOUGHT1 CONNECTION (Customer TO Product);

Create a new edge constraint on existing edge table, with additional edge constraint clauses

The following example uses the ALTER TABLE command to add a new edge constraint with additional edge constraint clauses on the bought edge table.

-- CREATE node and edge tables
CREATE TABLE Customer
   (
      ID INTEGER PRIMARY KEY
      , CustomerName VARCHAR(100)
   )
   AS NODE;
GO
CREATE TABLE Supplier
   (
      ID INTEGER PRIMARY KEY
      , SupplierName VARCHAR(100)
   )
   AS NODE;
GO
CREATE TABLE Product
   (
      ID INTEGER PRIMARY KEY
      , ProductName VARCHAR(100)
   )
   AS NODE;
GO
CREATE TABLE bought
   (
      PurchaseCount INT
      , CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product)
   )
   AS EDGE;
-- Drop the existing edge constraint first and then create a new one.
ALTER TABLE bought DROP CONSTRAINT EC_BOUGHT;
GO
-- User ALTER TABLE to create a new edge constraint.
ALTER TABLE bought ADD CONSTRAINT EC_BOUGHT1 CONNECTION (Customer TO Product, Supplier TO Product);

In the preceding example, there are two edge constraint clauses in the EC_BOUGHT1 constraint, one that connects Customer to Product and other connects Supplier to Product. Both these clauses are applied in disjunction. That is, a given edge has to satisfy either of these two clauses to be allowed in the edge table.

Create a new edge constraint on existing edge table, with new edge constraint clause

The following example uses the ALTER TABLE command to add a new edge constraint with a new edge constraint clause on the bought edge table.

-- CREATE node and edge tables
CREATE TABLE Customer
  (
     ID INTEGER PRIMARY KEY
     , CustomerName VARCHAR(100)
  )
  AS NODE;
GO
CREATE TABLE Supplier
  (
     ID INTEGER PRIMARY KEY
     , SupplierName VARCHAR(100)
  )
  AS NODE;
GO
CREATE TABLE Product
  (
     ID INTEGER PRIMARY KEY
     , ProductName VARCHAR(100)
  )
  AS NODE;
GO
CREATE TABLE bought
  (
     PurchaseCount INT,
        CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product)
  )
  AS EDGE;
GO

In the preceding example, imagine that we now need to also include the Supplier to Product relationship, through the bought edge table. You can try adding a new edge constraint:

ALTER TABLE bought ADD CONSTRAINT EC_BOUGHT1 CONNECTION (Supplier TO Product);

However, adding a new edge constraint isn't the correct solution. We created two separate edge constraints on the bought edge table, EC_BOUGHT and EC_BOUGHT1. Both these edge constraints have different edge constraint clauses. If an edge table has more than one edge constraint on it, a given edge has to satisfy ALL edge constraints to be allowed in the edge table. Since no edge can satisfy both EC_BOUGHT and EC_BOUGHT1 here, the above ALTER TABLE statement fails if there are any rows at all in the bought edge table.

For this edge constraint to be created successfully, the prescribed way is to follow a sequence as shown in this sample:

-- First, add the desired ("super-set") constraint:
ALTER TABLE bought ADD CONSTRAINT EC_BOUGHT_NEW CONNECTION (Customer TO Product, Supplier TO Product);
GO

-- Then, drop the older edge constraint:
ALTER TABLE bought DROP CONSTRAINT EC_BOUGHT;
GO

-- If needed, you can rename the new edge constraint to match the original name:
EXECUTE sp_rename '[dbo].[EC_BOUGHT_NEW]', '[dbo].[EC_BOUGHT]';

The fact that we added the new "super-set" constraint first without dropping the earlier one, allows the operation to be a metadata-only operation - it doesn't need to check all the existing data in the bought table, as it encompasses the existing constraint.

With this, for a given edge to be allowed in the bought edge, it has to satisfy either of the edge constraint clauses in EC_BOUGHT_NEW constraint. Hence any edge that is trying to connect valid Customer to Product or Supplier to Product nodes, is allowed.

Delete edge constraints

The following example first identifies the name of the edge constraint and then deletes the constraint.

-- CREATE node and edge tables
CREATE TABLE Customer
   (
      ID INTEGER PRIMARY KEY
      , CustomerName VARCHAR(100)
   )
   AS NODE;
GO
CREATE TABLE Product
   (
      ID INTEGER PRIMARY KEY
      , ProductName VARCHAR(100)
   ) AS NODE;
GO
CREATE TABLE bought
   (
      PurchaseCount INT
      , CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product)
    )
    AS EDGE;
GO

-- Return the name of edge constraint.
SELECT name
   FROM sys.edge_constraints
   WHERE type = 'EC' AND parent_object_id = OBJECT_ID('bought');
GO

-- Delete the primary key constraint.
ALTER TABLE bought
DROP CONSTRAINT EC_BOUGHT;

Modify edge constraints

To modify an edge constraint using Transact-SQL, you must first delete the existing edge constraint, and then re-create it with the new definition.

View edge constraints

The visibility of the metadata in catalog views is limited to securables that a user either owns, or on which the user was granted some permission. For more information, see Metadata Visibility Configuration.

The example returns all edge constraints and their properties for the edge table bought in the tempdb database.

-- CREATE node and edge tables
CREATE TABLE Customer
   (
      ID INTEGER PRIMARY KEY
      , CustomerName VARCHAR(100)
   )
   AS NODE;
GO
CREATE TABLE Supplier
   (
      ID INTEGER PRIMARY KEY
      , SupplierName VARCHAR(100)
   )
   AS NODE;
   GO
CREATE TABLE Product
   (
      ID INTEGER PRIMARY KEY
      , ProductName VARCHAR(100)
   )
   AS NODE;

-- CREATE edge table with edge constraints.
CREATE TABLE bought
   (
      PurchaseCount INT
      , CONSTRAINT EC_BOUGHT CONNECTION (Customer TO Product, Supplier TO Product)
   )
   AS EDGE;

-- Query sys.edge_constraints and sys.edge_constraint_clauses to view
-- edge constraint properties.
SELECT
   EC.name AS edge_constraint_name
   , OBJECT_NAME(EC.parent_object_id) AS edge_table_name
   , OBJECT_NAME(ECC.from_object_id) AS from_node_table_name
   , OBJECT_NAME(ECC.to_object_id) AS to_node_table_name
   , is_disabled
   , is_not_trusted
FROM sys.edge_constraints EC
   INNER JOIN sys.edge_constraint_clauses ECC
   ON EC.object_id = ECC.object_id
WHERE EC.parent_object_id = object_id('bought');

Next steps