Home / Products / VGL

Arvenza VGL Ask graph questions of the data you already have.

VGL, the Virtual Graph Layer, lets you query your relational databases and lakehouse tables as one connected graph, without moving a single row. Reporting chains, supply networks and multi-hop joins run against the source, as the person asking, under the access rules you already have.

Read-only by design. One stateless binary that runs without internet access and sends no telemetry.

Rows in HR, procurement and payables tables are mapped into one graph. A question walks the graph hop by hop, with each hop answered by the source database. HR Procurement Payables VGLmapping one connected graph
Tables in three business systems are mapped into one graph. A question walks it hop by hop, and each hop is answered by the source.

Every question answered, straight from a 2.1-million-row ERP

We asked an ERP-shaped database covering HR, procurement and payables the questions finance, procurement and HR leaders ask every week: 100 of them, from head counts to spend traced through the general ledger. VGL answered every one at the source, without copying a row.

100%of questions answered: all 100, across finance, procurement, HR and whole-business analysis
21 msmedian time per question, end to end; 97 of 100 took under 100 ms
92questions answered without moving any graph data over the network, only the answer
2.1Mrows in 17 tables, queried where they live and never copied
Every question, timed — open the plot

Every question, timed

  • One query in your database 90
  • Recursive query in your database 2
  • Whole-graph algorithm 8
Reading the ledgerFinance
21 ms13 questions
Suppliers and what we buyProcurement
27 ms17 questions
The peopleHR
17 ms15 questions
The organisation chartHR
9.5 ms14 questions
Procure to pay, end to endFinance and procurement
24 ms15 questions
Reconciliation and controlFinance and audit
51 ms11 questions
Questions that span the whole businessLeadership
12 ms7 questions
Whole-graph analysisRisk and strategy
53.5 ms8 questions

Each dot is one question, on a log scale of response time; the tick in each row is its median. Hover, tap or focus a row and use the arrow keys to read the questions.

Questions leaders ask
Take one supplier and follow the money all the way to the general ledger.
Finance and procurement11 msOne query in your database
Which cost centres are carrying the most supplier spend?
Finance and procurement103 msOne query in your database
How many distributions are matched to a purchase order line?
Finance and audit76 msOne query in your database
Show me the whole management chain above them, to the top.
HR9 msRecursive query in your database
Which people, if removed, would split the organisation into pieces?
Risk and strategy53 msWhole-graph algorithm
Where are the sites of our highest-spending supplier?
Leadership8 msOne query in your database
What this proves for an enterprise

No copies, no pipelines

Every answer came from the source database at the moment it was asked. Nothing was extracted, loaded or kept in sync.

Checked before it is trusted

The mapping's 67 claims about keys and joins were verified against all 2.1 million rows in 1.8 seconds, before a single question was asked.

Only the answer crosses the network

A management chain for one person took 5.4 ms and moved 0 KB. The same pattern for the whole company fetched 1.17 MB once and took 41 ms. The planner chose each route on its own.

Built for enterprise scale

The ERP test shows everyday questions. These separate tests show how far the same engine goes.

16Mrelationships across 2 million records, analysed by six graph algorithms in one scan, in under 3 GB of memory
5Mrecords in one graph: a ten-level traversal from one record took 6.1 ms, against 5.4 ms at 50,000
300Krecords merged into one graph from two different database engines, 100,000 of them found in both, answered in 1.60 s
1.4 sto rank 2 million records by influence with PageRank, across 16 million relationships
Browse all 100 questions and the queries behind them
#QuestionTimeAnswered by
Reading the ledger Finance
1How many invoices are in the system?
Query
MATCH (i:Invoice) RETURN count(*) AS invoices
11 msOne query in your database
2What is the total value of every invoice we hold?
Query
MATCH (i:Invoice) RETURN sum(i.InvoiceAmount) AS total
18 msOne query in your database
3What is the largest single invoice, and the smallest?
Query
MATCH (i:Invoice) RETURN max(i.InvoiceAmount) AS largest, min(i.InvoiceAmount) AS smallest
21 msOne query in your database
4What is the average invoice value?
Query
MATCH (i:Invoice) RETURN avg(i.InvoiceAmount) AS average
17 msOne query in your database
5Show me the ten largest invoices.
Query
MATCH (i:Invoice) RETURN i.InvoiceNumber, i.InvoiceAmount ORDER BY i.InvoiceAmount DESC LIMIT 10
28 msOne query in your database
6Show me the largest unpaid invoices.
Query
MATCH (i:Invoice) WHERE i.PaymentStatus <> 'PAID' RETURN i.InvoiceNumber, i.InvoiceAmount, i.PaymentStatus ORDER BY i.InvoiceAmount DESC LIMIT 10
26 msOne query in your database
7How much money is sitting in unpaid invoices?
Query
MATCH (i:Invoice) WHERE i.PaymentStatus <> 'PAID' RETURN sum(i.InvoiceAmount) AS outstanding
21 msOne query in your database
8Break the invoice book down by payment status.
Query
MATCH (i:Invoice) RETURN i.PaymentStatus, count(*) AS n, sum(i.InvoiceAmount) AS value ORDER BY value DESC
22 msOne query in your database
9Break it down by validation status as well.
Query
MATCH (i:Invoice) RETURN i.ValidationStatus, count(*) AS n ORDER BY n DESC
21 msOne query in your database
10Which currencies do we invoice in, and how much in each?
Query
MATCH (i:Invoice) RETURN i.InvoiceCurrency, count(*) AS n, sum(i.InvoiceAmount) AS total ORDER BY total DESC
23 msOne query in your database
11What kinds of invoice do we receive?
Query
MATCH (i:Invoice) RETURN i.InvoiceType, count(*) AS n ORDER BY n DESC
21 msOne query in your database
12Find invoice INV-000000041 and tell me what it is.
Query
MATCH (i:Invoice) WHERE i.InvoiceNumber = 'INV-000000041' RETURN i.InvoiceNumber, i.InvoiceAmount, i.InvoiceDate, i.PaymentStatus
16 msOne query in your database
13Which large invoices are still unvalidated? That is the pair I care about.
Query
MATCH (i:Invoice) WHERE i.InvoiceAmount > 900 AND i.ValidationStatus <> 'VALIDATED' RETURN i.InvoiceNumber, i.InvoiceAmount, i.ValidationStatus ORDER BY i.InvoiceAmount DESC LIMIT 10
19 msOne query in your database
Suppliers and what we buy Procurement
14How many suppliers do we have on file?
Query
MATCH (s:Supplier) RETURN count(*) AS suppliers
8 msOne query in your database
15What types of supplier are they?
Query
MATCH (s:Supplier) RETURN s.SupplierType, count(*) AS n ORDER BY n DESC
9 msOne query in your database
16Which suppliers did we spend the most with?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice) RETURN s.SupplierName, sum(i.InvoiceAmount) AS spend ORDER BY spend DESC LIMIT 10
51 msOne query in your database
17Which suppliers have sent us the most invoices?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice) RETURN s.SupplierName, count(*) AS invoices ORDER BY invoices DESC LIMIT 10
40 msOne query in your database
18Which suppliers have sent us more than thirty-three invoices?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice) WITH s.SupplierName AS supplier, count(*) AS n WHERE n > 33 RETURN supplier, n ORDER BY n DESC LIMIT 20
42 msOne query in your database
19Which suppliers send us the largest invoices on average?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice) RETURN s.SupplierName, avg(i.InvoiceAmount) AS average ORDER BY average DESC LIMIT 10
59 msOne query in your database
20Which suppliers have sites in Singapore?
Query
MATCH (s:Supplier)-[:HAS_SITE]->(t:SupplierSite) WHERE t.Country = 'SG' RETURN s.SupplierName ORDER BY s.SupplierName LIMIT 20
23 msOne query in your database
21How many purchase orders have we raised?
Query
MATCH (p:PurchaseOrder) RETURN count(*) AS orders
15 msOne query in your database
22What is the total value we have committed on purchase orders?
Query
MATCH (p:PurchaseOrder) RETURN sum(p.OrderedAmount) AS committed
22 msOne query in your database
23How do purchase orders break down by status?
Query
MATCH (p:PurchaseOrder) RETURN p.Status, count(*) AS n, sum(p.OrderedAmount) AS value ORDER BY value DESC
27 msOne query in your database
24Which suppliers have the largest open orders with us?
Query
MATCH (s:Supplier)-[:SUPPLIES]->(p:PurchaseOrder) WHERE p.Status = 'OPEN' RETURN s.SupplierName, sum(p.OrderedAmount) AS open_value ORDER BY open_value DESC LIMIT 10
27 msOne query in your database
25How many lines are on our purchase orders altogether?
Query
MATCH (l:PurchaseOrderLine) RETURN count(*) AS lines
17 msOne query in your database
26What is on purchase order 7?
Query
MATCH (p:PurchaseOrder)-[:PO_HAS_LINE]->(l:PurchaseOrderLine) WHERE p.POHeaderId = 7 RETURN l.LineNumber, l.ItemDescription, l.Quantity, l.UnitPrice ORDER BY l.LineNumber
8 msOne query in your database
27What is each line on that order actually worth?
Query
MATCH (l:PurchaseOrderLine) WHERE l.POHeaderId = 7 RETURN l.LineNumber, l.Quantity * l.UnitPrice AS line_value ORDER BY l.LineNumber
7 msOne query in your database
28Which purchase order lines are the most expensive per unit?
Query
MATCH (l:PurchaseOrderLine) RETURN l.POHeaderId, l.LineNumber, l.UnitPrice ORDER BY l.UnitPrice DESC LIMIT 10
27 msOne query in your database
29Who raised the purchase orders — which buyers are busiest?
Query
MATCH (w:Worker)-[:BUYER_OF]->(p:PurchaseOrder) RETURN w.DisplayName, count(*) AS orders ORDER BY orders DESC LIMIT 10
47 msOne query in your database
30Which buyers have committed the most money?
Query
MATCH (w:Worker)-[:BUYER_OF]->(p:PurchaseOrder) RETURN w.DisplayName, sum(p.OrderedAmount) AS committed ORDER BY committed DESC LIMIT 10
70 msOne query in your database
The people HR
31How many people do we employ?
Query
MATCH (w:Worker) RETURN count(*) AS workers
9 msOne query in your database
32What kinds of worker are they — employees, contingent?
Query
MATCH (w:Worker) RETURN w.WorkerType, count(*) AS n ORDER BY n DESC
13 msOne query in your database
33How many assignments are active?
Query
MATCH (a:Assignment) RETURN count(*) AS assignments
12 msOne query in your database
34How many departments are there?
Query
MATCH (d:Department) RETURN count(*) AS departments
8 msOne query in your database
35Which departments are the biggest?
Query
MATCH (a:Assignment)-[:IN_DEPARTMENT]->(d:Department) RETURN d.DepartmentName, count(*) AS headcount ORDER BY headcount DESC LIMIT 10
22 msOne query in your database
36Which departments have fewer than forty-two people?
Query
MATCH (a:Assignment)-[:IN_DEPARTMENT]->(d:Department) WITH d.DepartmentName AS department, count(*) AS headcount WHERE headcount < 42 RETURN department, headcount ORDER BY headcount ASC LIMIT 20
17 msOne query in your database
37What jobs do people hold, and how many in each?
Query
MATCH (a:Assignment)-[:HAS_JOB]->(j:Job) RETURN j.JobName, count(*) AS n ORDER BY n DESC LIMIT 15
21 msOne query in your database
38Which job families are we made of?
Query
MATCH (a:Assignment)-[:HAS_JOB]->(j:Job) RETURN j.JobFamily, count(*) AS n ORDER BY n DESC
21 msOne query in your database
39Which offices do people work from?
Query
MATCH (a:Assignment)-[:AT_LOCATION]->(l:Location) RETURN l.LocationName, count(*) AS n ORDER BY n DESC LIMIT 15
18 msOne query in your database
40Which countries do we have people in?
Query
MATCH (a:Assignment)-[:AT_LOCATION]->(l:Location) RETURN l.LocationCountry, count(*) AS n ORDER BY n DESC
20 msOne query in your database
41How many people sit in each business unit?
Query
MATCH (a:Assignment)-[:ASSIGNED_TO_BU]->(b:BusinessUnit) RETURN b.BusinessUnitName, count(*) AS n ORDER BY n DESC LIMIT 15
31 msOne query in your database
42Which legal entities do our business units belong to?
Query
MATCH (b:BusinessUnit)-[:BU_OF_EMPLOYER]->(e:LegalEmployer) RETURN e.LegalEmployerName, count(*) AS business_units ORDER BY business_units DESC
17 msOne query in your database
43What is the shape of a single person's record — take person 42.
Query
MATCH (w:Worker)-[:HAS_ASSIGNMENT]->(a:Assignment) WHERE w.PersonId = 42 RETURN w.DisplayName, w.WorkerNumber, a.AssignmentNumber, a.FullPartTime
7 msOne query in your database
44Which department is person 42 in?
Query
MATCH (w:Worker)-[:HAS_ASSIGNMENT]->(a:Assignment)-[:IN_DEPARTMENT]->(d:Department) WHERE w.PersonId = 42 RETURN d.DepartmentName
7 msOne query in your database
45Who joined most recently?
Query
MATCH (w:Worker) RETURN w.DisplayName, w.HireDate ORDER BY w.HireDate DESC LIMIT 10
13 msOne query in your database
The organisation chart HR
46Who does assignment 49999 report to?
Query
MATCH (a:Assignment)-[:REPORTS_TO]->(m:Assignment) WHERE a.AssignmentId = 49999 RETURN m.AssignmentNumber
8 msOne query in your database
47Show me the whole management chain above them, to the top.
Query
MATCH (a:Assignment)-[:REPORTS_TO*1..10]->(m:Assignment) WHERE a.AssignmentId = 49999 RETURN DISTINCT m.AssignmentNumber ORDER BY m.AssignmentNumber
9 msRecursive query in your database
48How many managers sit above them within three levels?
Query
MATCH (a:Assignment)-[:REPORTS_TO*1..3]->(m:Assignment) WHERE a.AssignmentId = 49999 RETURN count(DISTINCT m.AssignmentId) AS within_three
10 msOne query in your database
49Who reports directly to assignment 6?
Query
MATCH (a:Assignment)-[:REPORTS_TO]->(m:Assignment) WHERE m.AssignmentId = 6 RETURN a.AssignmentNumber ORDER BY a.AssignmentNumber
7 msOne query in your database
50How many people sit under assignment 6, three levels down?
Query
MATCH (a:Assignment)-[:REPORTS_TO*1..3]->(m:Assignment) WHERE m.AssignmentId = 6 RETURN count(DISTINCT a.AssignmentId) AS total_below
10 msOne query in your database
51Which managers have the most direct reports?
Query
MATCH (a:Assignment)-[:REPORTS_TO]->(m:Assignment) RETURN m.AssignmentNumber, count(*) AS direct_reports ORDER BY direct_reports DESC LIMIT 10
23 msOne query in your database
52Which managers have more than three direct reports?
Query
MATCH (a:Assignment)-[:REPORTS_TO]->(m:Assignment) WITH m.AssignmentNumber AS manager, count(*) AS reports WHERE reports > 3 RETURN manager, reports ORDER BY manager LIMIT 20
27 msOne query in your database
53How many reporting relationships are there in total?
Query
MATCH (a:Assignment)-[:REPORTS_TO]->(m:Assignment) RETURN count(*) AS reporting_lines
21 msOne query in your database
54How many people sit within three levels of the top twenty-four?
Query
MATCH (a:Assignment)-[:REPORTS_TO*1..3]->(m:Assignment) WHERE m.AssignmentId < 25 RETURN count(DISTINCT a.AssignmentId) AS within_three_levels
12 msOne query in your database
55Which of the top managers has the most people within three levels?
Query
MATCH (a:Assignment)-[:REPORTS_TO*1..3]->(m:Assignment) WHERE m.AssignmentId < 25 RETURN m.AssignmentNumber, count(DISTINCT a.AssignmentId) AS org_size ORDER BY org_size DESC LIMIT 10
13 msOne query in your database
56Which department does a given department roll up into?
Query
MATCH (d:Department)-[:PART_OF]->(p:Department) WHERE d.DepartmentId = 900 RETURN p.DepartmentName
8 msOne query in your database
57Show me the whole department tree above it.
Query
MATCH (d:Department)-[:PART_OF*1..6]->(p:Department) WHERE d.DepartmentId = 900 RETURN DISTINCT p.DepartmentName ORDER BY p.DepartmentName
9 msRecursive query in your database
58Which departments have the most sub-departments beneath them?
Query
MATCH (d:Department)-[:PART_OF]->(p:Department) RETURN p.DepartmentName, count(*) AS children ORDER BY children DESC LIMIT 10
8 msOne query in your database
59How many departments does each business unit hold?
Query
MATCH (d:Department)-[:DEPARTMENT_IN_BU]->(b:BusinessUnit) RETURN b.BusinessUnitName, count(*) AS departments ORDER BY departments DESC LIMIT 10
8 msOne query in your database
Procure to pay, end to end Finance and procurement
60How many invoice lines do we hold?
Query
MATCH (l:InvoiceLine) RETURN count(*) AS invoice_lines
17 msOne query in your database
61How many accounting distributions?
Query
MATCH (d:InvoiceDistribution) RETURN count(*) AS distributions
24 msOne query in your database
62What is the total value distributed to the ledger?
Query
MATCH (d:InvoiceDistribution) RETURN sum(d.DistributionAmount) AS distributed
35 msOne query in your database
63What is on invoice 41, line by line?
Query
MATCH (i:Invoice)-[:INVOICE_HAS_LINE]->(l:InvoiceLine) WHERE i.InvoiceId = 41 RETURN l.LineNumber, l.LineType, l.LineAmount ORDER BY l.LineNumber
8 msOne query in your database
64How is invoice 41 split across the ledger?
Query
MATCH (l:InvoiceLine)-[:HAS_DISTRIBUTION]->(d:InvoiceDistribution) WHERE l.InvoiceId = 41 RETURN d.DistributionLineNumber, d.DistributionAmount, d.AccountingStatus ORDER BY d.DistributionLineNumber
8 msOne query in your database
65Which supplier does invoice 41 belong to?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice) WHERE i.InvoiceId = 41 RETURN s.SupplierName, s.SupplierNumber
7 msOne query in your database
66Take one supplier and follow the money all the way to the general ledger.
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice)-[:INVOICE_HAS_LINE]->(l:InvoiceLine)-[:HAS_DISTRIBUTION]->(d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount) WHERE s.SupplierId = 1999 RETURN g.NaturalAccount, sum(d.DistributionAmount) AS charged ORDER BY charged DESC LIMIT 10
11 msOne query in your database
67What have our top suppliers cost us, traced all the way through the ledger?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice)-[:INVOICE_HAS_LINE]->(l:InvoiceLine)-[:HAS_DISTRIBUTION]->(d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount) WHERE s.SupplierId < 50 RETURN s.SupplierName, sum(d.DistributionAmount) AS spend ORDER BY spend DESC LIMIT 10
26 msOne query in your database
68Which cost centres are carrying the most supplier spend?
Query
MATCH (d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount)-[:IN_COST_CENTER]->(c:CostCenter) RETURN c.CostCenterName, sum(d.DistributionAmount) AS spend ORDER BY spend DESC LIMIT 10
103 msOne query in your database
69Which departments own those cost centres — so who is actually spending?
Query
MATCH (d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount)-[:IN_COST_CENTER]->(c:CostCenter)-[:OWNED_BY]->(dep:Department) RETURN dep.DepartmentName, sum(d.DistributionAmount) AS spend ORDER BY spend DESC LIMIT 10
104 msOne query in your database
70Show me spend by department, from the ledger back up.
Query
MATCH (d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount)-[:IN_COST_CENTER]->(c:CostCenter)-[:OWNED_BY]->(dep:Department) WHERE dep.DepartmentId < 20 RETURN dep.DepartmentName, sum(d.DistributionAmount) AS spend ORDER BY spend DESC
25 msOne query in your database
71What natural accounts are we posting to?
Query
MATCH (g:GlAccount) RETURN g.NaturalAccount, count(*) AS combinations ORDER BY combinations DESC LIMIT 15
9 msOne query in your database
72How does spend split by account type?
Query
MATCH (d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount) RETURN g.AccountType, sum(d.DistributionAmount) AS spend ORDER BY spend DESC
75 msOne query in your database
73How many cost centres do we run?
Query
MATCH (c:CostCenter) RETURN count(*) AS cost_centres
8 msOne query in your database
74How do distributions break down by accounting status?
Query
MATCH (d:InvoiceDistribution) RETURN d.AccountingStatus, count(*) AS n ORDER BY n DESC
46 msOne query in your database
Reconciliation and control Finance and audit
75How many distributions are matched to a purchase order line?
Query
MATCH (d:InvoiceDistribution)-[:MATCHED_TO]->(l:PurchaseOrderLine) RETURN count(*) AS matched
76 msOne query in your database
76And how many distributions are there in total — so how many are unmatched?
Query
MATCH (d:InvoiceDistribution) RETURN count(*) AS all_distributions
26 msOne query in your database
77What value have we matched to purchase orders?
Query
MATCH (d:InvoiceDistribution)-[:MATCHED_TO]->(l:PurchaseOrderLine) RETURN sum(d.DistributionAmount) AS matched_value
80 msOne query in your database
78Which purchase order lines are absorbing the most invoiced value?
Query
MATCH (d:InvoiceDistribution)-[:MATCHED_TO]->(l:PurchaseOrderLine) RETURN l.POHeaderId, l.LineNumber, sum(d.DistributionAmount) AS invoiced ORDER BY invoiced DESC LIMIT 10
371 msOne query in your database
79What have we actually been billed against each line of purchase order 7?
Query
MATCH (d:InvoiceDistribution)-[:MATCHED_TO]->(l:PurchaseOrderLine) WHERE l.POHeaderId = 7 RETURN l.LineNumber, sum(d.DistributionAmount) AS billed ORDER BY l.LineNumber
10 msOne query in your database
80How many of our suppliers have actually billed us?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice) RETURN count(DISTINCT s.SupplierId) AS suppliers_who_billed
51 msOne query in your database
81How many invoices have at least one line? (Compare with the 200,000 total.)
Query
MATCH (i:Invoice)-[:INVOICE_HAS_LINE]->(l:InvoiceLine) RETURN count(DISTINCT i.InvoiceId) AS invoices_with_lines
77 msOne query in your database
82What do the invoice lines total?
Query
MATCH (l:InvoiceLine) RETURN sum(l.LineAmount) AS line_total
22 msOne query in your database
83And what do the accounting distributions total?
Query
MATCH (d:InvoiceDistribution) RETURN sum(d.DistributionAmount) AS distribution_total
36 msOne query in your database
84Which accounting dates are we posting into?
Query
MATCH (d:InvoiceDistribution) RETURN d.AccountingDate, count(*) AS n ORDER BY n DESC LIMIT 10
54 msOne query in your database
85How do distributions break down by type?
Query
MATCH (d:InvoiceDistribution) RETURN d.DistributionLineType, count(*) AS n ORDER BY n DESC
48 msOne query in your database
Questions that span the whole business Leadership
86Which departments are our purchase orders actually being raised by?
Query
MATCH (dep:Department)<-[:IN_DEPARTMENT]-(a:Assignment)<-[:HAS_ASSIGNMENT]-(w:Worker)-[:BUYER_OF]->(p:PurchaseOrder) WHERE w.PersonId < 500 RETURN dep.DepartmentName, count(*) AS orders_raised ORDER BY orders_raised DESC LIMIT 10
18 msOne query in your database
87And how much have each of those departments committed?
Query
MATCH (dep:Department)<-[:IN_DEPARTMENT]-(a:Assignment)<-[:HAS_ASSIGNMENT]-(w:Worker)-[:BUYER_OF]->(p:PurchaseOrder) WHERE w.PersonId < 500 RETURN dep.DepartmentName, sum(p.OrderedAmount) AS committed ORDER BY committed DESC LIMIT 10
17 msOne query in your database
88Follow a purchase order line through to the invoice distribution that paid it.
Query
MATCH (d:InvoiceDistribution)-[:MATCHED_TO]->(l:PurchaseOrderLine) WHERE l.POHeaderId = 7 RETURN l.LineNumber, d.DistributionAmount, d.AccountingStatus ORDER BY l.LineNumber LIMIT 20
9 msOne query in your database
89Where are the sites of our highest-spending supplier?
Query
MATCH (s:Supplier)-[:HAS_SITE]->(t:SupplierSite) WHERE s.SupplierId = 1999 RETURN s.SupplierName, t.SiteCode, t.Country ORDER BY t.SiteCode
8 msOne query in your database
90Give me spend by cost centre, for the cost centres one department owns.
Query
MATCH (d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount)-[:IN_COST_CENTER]->(c:CostCenter)-[:OWNED_BY]->(dep:Department) WHERE dep.DepartmentId = 5 RETURN c.CostCenterName, sum(d.DistributionAmount) AS spend ORDER BY spend DESC
12 msOne query in your database
91How many distinct GL accounts has a single supplier's spend touched?
Query
MATCH (s:Supplier)-[:BILLED]->(i:Invoice)-[:INVOICE_HAS_LINE]->(l:InvoiceLine)-[:HAS_DISTRIBUTION]->(d:InvoiceDistribution)-[:CHARGED_TO]->(g:GlAccount) WHERE s.SupplierId = 1999 RETURN count(DISTINCT g.CodeCombinationId) AS accounts_touched
10 msOne query in your database
92Show me the ten costliest invoice lines and what they were for.
Query
MATCH (l:InvoiceLine) RETURN l.InvoiceId, l.LineNumber, l.Description, l.LineAmount ORDER BY l.LineAmount DESC LIMIT 10
38 msOne query in your database
Whole-graph analysis Risk and strategy
93How many separate organisations does the reporting structure actually contain?
Query
CALL vgl.wcc('Assignment', 'REPORTS_TO')
51 msWhole-graph algorithm
94Who is most central in the management structure?
Query
CALL vgl.pagerank('Assignment', 'REPORTS_TO')
58 msWhole-graph algorithm
95Are there natural communities in the reporting structure?
Query
CALL vgl.louvain('Assignment', 'REPORTS_TO')
62 msWhole-graph algorithm
96Which people, if removed, would split the organisation into pieces?
Query
CALL vgl.bridges('Assignment', 'REPORTS_TO')
53 msWhole-graph algorithm
97How many separate trees does the department hierarchy form?
Query
CALL vgl.wcc('Department', 'PART_OF')
12 msWhole-graph algorithm
98Which departments are most central in the hierarchy?
Query
CALL vgl.pagerank('Department', 'PART_OF')
13 msWhole-graph algorithm
99What does the supplier-to-purchase-order network look like?
Query
CALL vgl.wcc('Supplier', 'SUPPLIES')
68 msWhole-graph algorithm
100How far is assignment 49999 from the top of its reporting line, and by what route?
Query
CALL vgl.shortestPath('Assignment', 'REPORTS_TO', 49999, 13)
54 msWhole-graph algorithm

Measured on an ERP-shaped schema of 2,142,623 rows in 17 tables, mapped to 17 node labels and 21 relationship types, one question at a time, end to end. Bring your own questions: a proof of concept runs the same harness on your data.

A graph without a graph database

VGL keeps no copy of your data. It plans each question, sends the work to the systems that hold the data, and assembles the answer.

Query

Standard graph queries
Write openCypher. Answers come back over the Bolt protocol, so standard graph drivers and tools connect without changes.
A plan for every question
A cost model chooses between pushing a join to the source, pushing a recursive query to the source, walking a cached topology in memory, or running a whole-graph algorithm. explain shows the choice and the SQL.
Graph algorithms
PageRank, community detection, connected components, clustering, betweenness, bridges, shortest paths and hierarchies, on every core.
One graph across sources
A single node label can span several databases, with identities reconciled by rule.

Security

Runs as the caller
Queries run as the person asking, so the source's own row-level security still applies. Connections are pooled per user and never shared.
No leaks through paths
A traversal cannot reveal a path through a record the caller is not allowed to see.
Audited
A structured audit log records every SQL statement sent, and every refusal.
A cache that holds keys only
Cached topology never contains property values. Every answer says how fresh it is, and optional persistence is encrypted with a key you hold.

Sources

Where your data already lives
The major relational databases, analytical and search engines, an embedded analytics engine, and open lakehouse tables on object storage.
Mapping as code
One YAML file in version control, with no credentials in it. Checks run in your build pipeline and fail it when the mapping and the data disagree.

Working with it

VGL Studio
A web workspace with single sign-on: design mappings visually, write queries with completion, see results as a graph, and save and share questions.
Ask in plain language
Studio drafts queries from plain questions using the schema only, never your rows, and never runs a draft on its own. An optional assistant can run them for you within a row budget an administrator sets.
For AI agents
An MCP server lets agents explore the graph. Returning rows is off unless you turn it on, and capped when you do.
In your editor
An editor extension checks mappings as you save, and shows query plans and where each statistic came from.

Map once, then ask

A mapping says which tables hold nodes and which columns hold relationships. This one turns an employee table into a reporting chain.

The query then walks up to ten levels of management in one line. VGL can send it to your database as a single recursive query, so only the answer crosses the network.

Longer questions stay short: a spend question that crosses five procurement and payables tables is five lines of openCypher, where the SQL VGL generates for it runs to 38.

mapping.yaml
nodes:
  - label: Employee
    source: hr
    table: hr.employee
    key: [id]
    properties:
      name:  name            # graph property : column
      title: title

edges:
  - type: REPORTS_TO
    source: hr
    table: hr.employee       # the edge lives in the same table
    key: [id]
    from: { label: Employee, key: [id] }
    to:   { label: Employee, key: [manager_id] }
management-chain.cypher
MATCH (e:Employee)-[:REPORTS_TO*1..10]->(m:Employee)
WHERE e.name = $name
RETURN m.name, m.title

Where VGL fits

Keep plain SQL when…

  • The question is a flat report or a dashboard
  • A single join answers it
  • You need to write data: VGL never writes to your sources

Choose VGL when…

  • Questions follow relationships across several hops or several systems
  • Data has to stay where it is, under the security it already has
  • You want graph algorithms over live business data
  • AI agents need to explore how your records connect

One stateless binary

No runtime to install and about 8 MB of memory at idle. There is nothing to back up: the mapping lives in version control and the data stays in your databases.

Offline by default

VGL runs without internet access and sends no telemetry, so it fits restricted and air-gapped networks.

Behind your sign-on

Put VGL Studio or your existing gateway in front for authentication. VGL takes the caller's identity from it and runs every query as that person.

Bring us your hardest relationship question

We'll map a slice of your schema and answer it on your own data, without copying a row.

Arrange a Conversation