diff --git a/Gemfile.lock b/Gemfile.lock index 07956b34..44c1ad97 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,47 +1,55 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.5.1) + activesupport (7.1.3.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bigdecimal (3.1.7) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.11.1) + coffee-script-source (1.12.2) colorator (1.1.0) - commonmarker (0.23.5) - concurrent-ruby (1.1.10) - dnsruby (1.61.9) - simpleidn (~> 0.1) + commonmarker (0.23.10) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + dnsruby (1.72.1) + simpleidn (~> 0.2.1) + drb (2.2.1) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.8.1) - faraday (2.4.0) - faraday-net_http (~> 2.0) - ruby2_keywords (>= 0.0.4) - faraday-net_http (2.1.0) - ffi (1.15.5) + execjs (2.9.1) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http + ffi (1.16.3-x64-mingw-ucrt) forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (227) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.2) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.2.0) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) + gemoji (4.1.0) + github-pages (231) + github-pages-health-check (= 1.18.2) + jekyll (= 3.9.5) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.4.0) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) + jekyll-github-metadata (= 2.16.1) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) @@ -68,32 +76,32 @@ GEM jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.2) + jemoji (= 0.13.0) + kramdown (= 2.4.0) kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) + liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.13.6, < 2.0) - rouge (= 3.26.0) + rouge (= 3.30.0) terminal-table (~> 1.4) - github-pages-health-check (1.17.9) + github-pages-health-check (1.18.2) addressable (~> 2.3) dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) typhoeus (~> 1.3) - html-pipeline (2.14.2) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (0.9.5) + i18n (1.14.4) concurrent-ruby (~> 1.0) - jekyll (3.9.2) + jekyll (3.9.5) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -102,27 +110,27 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) + jekyll-avatar (0.8.0) jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) + jekyll-coffeescript (1.2.2) coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) + coffee-script-source (~> 1.12) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.2.0) - commonmarker (~> 0.23.4) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) jekyll (~> 3.9.0) jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 4.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.13.0) + jekyll-github-metadata (2.16.1) jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) + octokit (>= 4, < 7, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) @@ -193,41 +201,41 @@ GEM jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.2) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) + liquid (4.0.4) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.8.0) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.16.2) - nokogiri (1.13.8) - mini_portile2 (~> 2.8.0) + minitest (5.22.3) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nokogiri (1.16.4-x64-mingw-ucrt) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.7) - racc (1.6.0) - rb-fsevent (0.11.1) + public_suffix (5.0.5) + racc (1.7.3) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) - rouge (3.26.0) - ruby2_keywords (0.0.5) + rexml (3.2.6) + rouge (3.30.0) rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) @@ -242,20 +250,19 @@ GEM unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (1.2.10) - thread_safe (~> 0.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.2) + unf_ext (0.0.9.1-x64-mingw-ucrt) unicode-display_width (1.8.0) - webrick (1.7.0) - zeitwerk (2.6.0) + uri (0.13.0) + webrick (1.8.1) PLATFORMS - ruby + x64-mingw-ucrt DEPENDENCIES github-pages @@ -263,4 +270,4 @@ DEPENDENCIES webrick BUNDLED WITH - 2.0.2 + 2.5.9 diff --git a/GenerateCodeBlocks.java b/GenerateCodeBlocks.java new file mode 100644 index 00000000..72987d71 --- /dev/null +++ b/GenerateCodeBlocks.java @@ -0,0 +1,168 @@ +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GenerateCodeBlocks { + private static String[] KEYWORDS = { "MATCH", "PATHS", "DATE", "TIME", "TIMEZONE", "INTEGER", + "BOOLEAN", + "STRING", "ARRAY_AGG", "LISTAGG", "LABELS", "HAS_LABEL", "LABEL", "ALL_DIFFERENT", "IN_DEGREE", + "OUT_DEGREE", + "CEILING", "CEIL", "FLOOR", "ROUND", "JAVA_REGEXP_LIKE", "LOWER", "SUBSTRING", "UPPER", "HOUR", "TOP", + "SHORTEST", + "PROPERTIES", "VERTEX", "EDGE", "GRAPH_TABLE", "PROPERTY", "GRAPH", "TABLES", "DESTINATION", "COLUMNS", "CHEAPEST", "COST", + "ONE", "PER", "STEP", "INTERVAL", "PREFIX", "WALK", "ACYCLIC", "SIMPLE", "TRAIL", + "LABELED", "KEEP", "ACCESS", "ADD", "ALL", "ALTER", "AND", "ANY", "ASC", "AUDIT", "BETWEEN", + "BY", "CHAR", "CHECK", "CLUSTER", "COLUMN", "COMMENT", "COMPRESS", "CONNECT", + "CREATE", "CURRENT", "DATE", "DECIMAL", "DEFAULT", "DELETE", "DESC", "DISTINCT", + "DROP", "ELSE", "EXCLUSIVE", "EXISTS", "FILE", "FLOAT", "FOR", "FROM", "GRANT", + "GROUP", "HAVING", "IDENTIFIED", "IN", "INDEX", "INITIAL", + "INSERT", "INTEGER", "INTERSECT", "INTO", "IS", "LEVEL", "LIKE", "LOCK", "LONG", + "MINUS", "MODE", "MODIFY", + "NOT", "NULL", "NUMBER", "OF", "OFFLINE", "ONLINE", "OPTION", + "ORDER", "OR", "PRIVILEGES", "PUBLIC", "RAW", "RENAME", + "RESOURCE", "REVOKE", "ROWID", "ROWNUM", "ROWS", "ROW", "SELECT", "SESSION", + "SET", "SHARE", "SIZE", "SMALLINT", "START", "SUCCESSFUL", "SYNONYM", "SYSDATE", + "TABLE", "THEN", "TO", "TRIGGER", "UID", "UNION", "UNIQUE", "UPDATE", "USER", + "VALIDATE", "VALUES", "VARCHAR", "VARCHAR2", "VIEW", "WHENEVER", "WHERE", "WITH", + "SUM", "ABS", "ID", "PATH", "ON", "AS"}; + + private static String regex; + + + public static class StringLengthComparator implements Comparator { + @Override + public int compare(String s1, String s2) { + return Integer.compare(s2.length(), s1.length()); + } + } + + public static void convertMarkdownToHTML(String inputFilePath, String outputFilePath) { + try { + // Create BufferedReader to read input file + BufferedReader reader = new BufferedReader(new FileReader(inputFilePath)); + + // Create BufferedWriter to write output file + BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath)); + + // Initialize flag to track if within code block + boolean inCodeBlock = false; + + // Placeholder HTML code + // String htmlStart = "
"; + String buttonCode = "
\n" + + "\n" + + + "\n" + + + "
"; + // String htmlEnd = "
"; + String codeBlock = ""; + String pgql = ""; + String sql = ""; + String[] tosplit; + + // Loop through each line in the file + String line; + while ((line = reader.readLine()) != null) { + // Check if line starts a code block + if (line.startsWith("```sql")) { + inCodeBlock = true; + } else if (inCodeBlock && line.startsWith("```")) { + tosplit = codeBlock.split("(?i)--SQL"); + if (tosplit.length == 2) { + writer.write(buttonCode); + pgql = getHtmlForQuery(tosplit[0].substring(6), true) + "\n"; + sql = getHtmlForQuery(tosplit[1], false) + "\n"; + } else { + pgql = "```sql\n" + tosplit[0] + "```\n"; + sql = ""; + } + writer.write(sql); + writer.write(pgql); + codeBlock = ""; + inCodeBlock = false;// Reset codeBlock for next block + } else { + // If within a code block, accumulate code lines + if (inCodeBlock) { + codeBlock += line + "\n"; // Add newline character + } else { + // If not in a code block, write the line as is + if (line.startsWith("#permalink:")) + line = line.substring(1); + writer.write(line); + writer.newLine(); + } + } + } + + // Close reader and writer + reader.close(); + writer.close(); + + System.out.println("Conversion completed successfully."); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static String getHtmlForQuery(String query, boolean isPgql) { + return "
" + + + "
"
+                + replaceWithHTML(query)
+                + "
"; + } + + + public static String replaceWithHTML(String input) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(input); + StringBuilder result = new StringBuilder(); + + while (matcher.find()) { + String match = matcher.group(); + String replacement; + + if (match.matches("('.*?'|\\b\\d+\\b)")) { + replacement = "" + match + ""; + } else if (match.matches("(?=([^'\"]|'[^']*'|\"[^\"]*\")*$)([\\+\\-\\*\\/=%><\\|])")) { + replacement = "" + match + ""; + } else if (isKeyword(match)) { + replacement = "" + match + ""; + }else{ + replacement = match; + } + + matcher.appendReplacement(result, replacement); + } + + matcher.appendTail(result); + return result.toString(); + } + + private static boolean isKeyword(String str) { + for (String keyword : KEYWORDS) { + if (str.equals(keyword)) { + return true; + } + } + return false; + } + + public static void main(String[] args) { + // Provide input and output file paths + String inputFilePath = "./pre-pages/pgql-2.0-spec.md"; + String outputFilePath = "./pages/pgql-2.0-spec.md"; + + Arrays.sort(KEYWORDS, new StringLengthComparator()); + regex = "('.*?'|\\b\\d+\\b)|(?=([^'\"]|'[^']*'|\"[^\"]*\")*$)([\\+\\-\\*\\/=%><\\|])|(?:" + String.join("|", KEYWORDS) + ")"; + // Call the convertMarkdownToHTML method + convertMarkdownToHTML(inputFilePath, outputFilePath); + } +} diff --git a/css/customstyles.css b/css/customstyles.css index 9b3ca986..b0115bc1 100644 --- a/css/customstyles.css +++ b/css/customstyles.css @@ -1173,3 +1173,50 @@ h4.panel-title { padding-top: 0px; margin-top: 0px; } + + +.tab-content { + display: none; + padding: 0px; + margin: 0px 0px 0px 0px; + border: 1px solid #ccc; + } + + /* Style the active tab content */ + .tab-content.active { + display: contents; + padding: 0px; + margin: 0px; + } + + div.tab-content div.highlight pre { + margin: 0px 0px 25px 0px; + } + + /* Style the tabs */ + .tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; + } + + /* Style the tab buttons */ + .tab button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 5px 10px; + transition: 0.3s; + } + + /* Change background color of buttons on hover */ + .tab button:hover { + background-color: #ddd; + } + + /* Style the active tab button */ + .tab button.active { + background-color: #ccc; + } \ No newline at end of file diff --git a/js/customscripts.js b/js/customscripts.js index cf742edd..2d05b60a 100644 --- a/js/customscripts.js +++ b/js/customscripts.js @@ -53,3 +53,34 @@ $(function() { } }); }); + +// Function to switch between tabs +function openTab(evt, tabName) { + var i, tabcontent, tablinks; + + // Hide all tab content + tabcontent = document.getElementsByClassName("tab-content"); + console.log(tabcontent); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Remove 'active' class from all tab buttons + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + // Show the specific tab content + document.getElementsByName(tabName).forEach(e => e.style.display = "contents") + + // Add 'active' class to the clicked tab button + buttonid = evt.currentTarget.getAttribute("name"); + + buttons = document.getElementsByName(buttonid); + + for (i = 0; i < buttons.length; i++) { + buttons[i].className += " active"; + } + +} diff --git a/pages/pgql-2.0-spec.md b/pages/pgql-2.0-spec.md index 34d1aa03..1b5ceb09 100644 --- a/pages/pgql-2.0-spec.md +++ b/pages/pgql-2.0-spec.md @@ -846,10 +846,19 @@ The following query matches all the vertices with the label `Person` and retriev {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT n.name, n.dob -FROM MATCH (n:Person) ON student_network -``` +
+ + +
+SELECT name, dob 
+FROM GRAPH_TABLE(student_network 
+  MATCH (n IS Person) 
+  COLUMNS(n.name, n.dob))
+
+
+SELECT n.name, n.dob
+FROM MATCH (n:Person) ON student_network
+
``` +-----------------------+ @@ -877,10 +886,19 @@ For example: {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT a.name AS a, b.name AS b -FROM MATCH (a:Person) -[e:knows]-> (b:Person) ON student_network -``` +
+ + +
+SELECT a, b 
+FROM GRAPH_TABLE(student_network 
+  MATCH (a IS Person)-[e is knows]->(b is Person)
+  COLUMNS(a.name as a, b.name as b))
+
+
+SELECT a.name AS a, b.name AS b
+FROM MATCH (a:Person) -[e:knows]-> (b:Person) ON student_network
+
``` +---------------------+ @@ -908,10 +926,19 @@ For example: {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT n.name, n.dob -FROM MATCH (n:Person|University) ON student_network -``` +
+ + +
+SELECT name, dob 
+FROM GRAPH_TABLE(student_network 
+  MATCH (n IS Person|university)
+  COLUMNS(n.name, n.dob))
+
+
+SELECT n.name, n.dob
+FROM MATCH (n:Person|University) ON student_network
+
``` +--------------------------+ @@ -934,10 +961,19 @@ For example: {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT n.name, n.dob -FROM MATCH (n) ON student_network -``` +
+ + +
+SELECT name, dob 
+FROM GRAPH_TABLE(student_network
+  MATCH (n)
+  COLUMNS(n.name, n.dob))
+
+
L
+SELECT n.name, n.dob
+FROM MATCH (n) ON student_network
+
``` +--------------------------+ @@ -961,11 +997,21 @@ For example, "find all persons that have a date of birth (dob) greater than 1995 {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT n.name, n.dob -FROM MATCH (n) ON student_network -WHERE n.dob > DATE '1995-01-01' -``` +
+ + +
+SELECT name, dob 
+FROM GRAPH_TABLE(student_network 
+  MATCH (n) 
+  WHERE n.dob > DATE '1995-01-01'
+  COLUMNS(n.name, n.dob))
+
+
+SELECT n.name, n.dob
+FROM MATCH (n) ON student_network
+WHERE n.dob > DATE '1995-01-01'
+
``` +---------------------+ @@ -984,11 +1030,21 @@ Another example is to "find people that Kathrine knows and that are old than her {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT m.name AS name, m.dob AS dob -FROM MATCH (n) -[e]-> (m) ON student_network -WHERE n.name = 'Kathrine' AND n.dob <= m.dob -``` +
+ + +
+SELECT name, dob 
+FROM GRAPH_TABLE(student_network 
+  MATCH (n) -[e]-> (m) 
+  WHERE n.name = 'Kathrine' AND n.dob <= m.dob 
+  COLUMNS(m.name, m.dob))
+
+
+SELECT m.name AS name, m.dob AS dob
+FROM MATCH (n) -[e]-> (m) ON student_network
+WHERE n.name = 'Kathrine' AND n.dob <= m.dob
+
``` +-------------------+ @@ -1011,12 +1067,22 @@ For example, "find people that Lee knows and that are a student at the same univ {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT p2.name AS friend, u.name AS university -FROM MATCH (u:University) <-[:studentOf]- (p1:Person) -[:knows]-> (p2:Person) -[:studentOf]-> (u) - ON student_network -WHERE p1.name = 'Lee' -``` +
+ + +
+SELECT friend, university 
+FROM GRAPH_TABLE(student_network 
+  MATCH (u IS university) <-[IS studentOf]- (p1 IS Person) -[IS knows]-> (p2 IS Person) -[IS studentOf]-> (u) 
+  WHERE p1.name = 'Lee' 
+  COLUMNS(p2.name AS friend, u.name AS university))
+
+
+SELECT p2.name AS friend, u.name AS university
+FROM MATCH (u:University) <-[:studentOf]- (p1:Person) -[:knows]-> (p2:Person) -[:studentOf]-> (u)
+       ON student_network
+WHERE p1.name = 'Lee'
+
``` +------------------------+ @@ -1031,13 +1097,25 @@ Note that the first and last vertex pattern both have the variable `u`. This mea The same query as above may be expressed through multiple comma-separated path patterns, like this: -```sql -SELECT p2.name AS friend, u.name AS university -FROM MATCH (p1:Person) -[:knows]-> (p2:Person) ON student_network - , MATCH (p1) -[:studentOf]-> (u:University) ON student_network - , MATCH (p2) -[:studentOf]-> (u) ON student_network -WHERE p1.name = 'Lee' -``` +
+ + +
+SELECT friend, university 
+FROM GRAPH_TABLE(student_network 
+  MATCH (p1 IS Person) -[IS knows]-> (p2 IS Person), 
+        (p1) -[IS studentOf]-> (u IS University), 
+        (p2) -[IS studentOf]-> (u) 
+  WHERE p1.name = 'Lee' 
+  COLUMNS(p2.name AS friend, u.name AS university))
+
+
+SELECT p2.name AS friend, u.name AS university
+FROM MATCH (p1:Person) -[:knows]-> (p2:Person) ON student_network
+   , MATCH (p1) -[:studentOf]-> (u:University) ON student_network
+   , MATCH (p2) -[:studentOf]-> (u) ON student_network
+WHERE p1.name = 'Lee'
+
``` +------------------------+ @@ -1057,12 +1135,22 @@ For example, "find friends of friends of Lee" (friendship being defined by the p {% include image.html file="example_graphs/student_network.png" %} -```sql -SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3 -FROM MATCH (p1:Person) -[:knows]-> (p2:Person) -[:knows]-> (p3:Person) - ON student_network -WHERE p1.name = 'Lee' -``` +
+ + +
+SELECT p1, p2, p3 
+FROM GRAPH_TABLE(student_network 
+  MATCH (p1 IS Person) -[IS knows]-> (p2 IS Person) -[IS knows]-> (p3 IS Person) 
+  WHERE p1.name = 'Lee' 
+  COLUMNS(p1.name AS p1, p2.name AS p2, p3.name AS p3))
+
+
+SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3
+FROM MATCH (p1:Person) -[:knows]-> (p2:Person) -[:knows]-> (p3:Person)
+       ON student_network
+WHERE p1.name = 'Lee'
+
``` +-----------------------+ @@ -1079,12 +1167,22 @@ If such binding of vertices to multiple variables is not desired, one can use ei For example, the predicate `p1 <> p3` in the query below adds the restriction that Lee, which has to bind to variable `p1`, cannot also bind to variable `p3`: -```sql -SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3 -FROM MATCH (p1:Person) -[:knows]-> (p2:Person) -[:knows]-> (p3:Person) - ON student_network -WHERE p1.name = 'Lee' AND p1 <> p3 -``` +
+ + +
+SELECT p1, p2, p3 
+FROM GRAPH_TABLE(student_network 
+  MATCH (p1 IS Person) -[IS knows]-> (p2 IS Person) -[IS knows]-> (p3 IS Person) 
+  WHERE p1.name = 'Lee' AND NOT VERTEX_EQUAL(p1, p3) 
+  COLUMNS(p1.name AS p1, p2.name AS p2, p3.name AS p3))
+
+
+SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3
+FROM MATCH (p1:Person) -[:knows]-> (p2:Person) -[:knows]-> (p3:Person)
+       ON student_network
+WHERE p1.name = 'Lee' AND p1 <> p3
+
``` +-----------------------+ @@ -1096,12 +1194,22 @@ WHERE p1.name = 'Lee' AND p1 <> p3 An alternative is to use the [ALL_DIFFERENT predicate](#all_different-predicate), which can take any number of vertices or edges as input and specifies non-equality between all of them: -```sql -SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3 -FROM MATCH (p1:Person) -[:knows]-> (p2:Person) -[:knows]-> (p3:Person) - ON student_network -WHERE p1.name = 'Lee' AND ALL_DIFFERENT(p1, p3) -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(student_network 
+  MATCH (p1 IS Person) -[ IS knows]-> (p2 IS Person) -[ IS knows]-> (p3 IS Person) 
+  WHERE p1.name = 'Lee' AND ALL_DIFFERENT(p1, p3) 
+  COLUMNS(p1.name AS p1, p2.name AS p2, p3.name AS p3))
+
+
+SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3
+FROM MATCH (p1:Person) -[:knows]-> (p2:Person) -[:knows]-> (p3:Person)
+       ON student_network
+WHERE p1.name = 'Lee' AND ALL_DIFFERENT(p1, p3)
+
``` +-----------------------+ @@ -1115,19 +1223,29 @@ Besides vertices binding to multiple variables, it is also possible for edges to For example, "find two people that both know Riya": -```sql -SELECT p1.name AS p1, p2.name AS p2, e1 = e2 -FROM MATCH (p1:Person) -[e1:knows]-> (riya:Person) ON student_network - , MATCH (p2:Person) -[e2:knows]-> (riya) ON student_network -WHERE riya.name = 'Riya' -``` +
+ + +
+SELECT p1, p2, e1_equals_e2 
+FROM GRAPH_TABLE(student_network 
+  MATCH (p1 IS Person) -[e1 IS knows]-> (riya IS Person), (p2 IS Person) -[e2 IS knows]-> (riya) 
+  WHERE riya.name = 'Riya'  
+  COLUMNS(p1.name AS p1, p2.name AS p2, EDGE_EQUAL(e1, e2) AS e1_equals_e2))
+
+
+SELECT p1.name AS p1, p2.name AS p2, e1 = e2 AS e1_equals_e2
+FROM MATCH (p1:Person) -[e1:knows]-> (riya:Person) ON student_network
+   , MATCH (p2:Person) -[e2:knows]-> (riya) ON student_network
+WHERE riya.name = 'Riya'
+
``` -+-------------------------------+ -| p1 | p2 | e1 = e2 | -+-------------------------------+ -| Kathrine | Kathrine | true | -+-------------------------------+ ++------------------------------------+ +| p1 | p2 | e1_equals_e2 | ++------------------------------------+ +| Kathrine | Kathrine | true | ++------------------------------------+ ``` Above, the only solution has Kathrine bound to both variables `p1` and `p2` and the single edge between Kathrine and Riya is bound to both `e1` and `e2`, which is why `e1 = e2` in the `SELECT` clause returns `true`. @@ -1140,10 +1258,19 @@ Any-directed edge patterns match edges in the graph no matter if they are incomi An example query with two any-directed edge patterns is: -```sql -SELECT * -FROM MATCH (n) -[e1]- (m) -[e2]- (o) ON student_network -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(student_network 
+  MATCH (n) -[e1]- (m) -[e2]- (o) 
+  COLUMNS(n.name AS n, m.name AS m, o.name AS o))
+
+
+SELECT n.name AS n, m.name AS m, o.name AS o
+FROM MATCH (n) -[e1]- (m) -[e2]- (o) ON student_network
+
Note that in case there are both incoming and outgoing data edges between two data vertices, there will be separate result bindings for each of the edges. @@ -1209,10 +1336,19 @@ A `SELECT` clause consists of the keyword `SELECT` followed by either an optiona Consider the following example: -```sql -SELECT n, m, n.age AS age -FROM MATCH (n:Person) -[e:friend_of]-> (m:Person) ON my_graph -``` +
+ + +
+SELECT n, m, age
+FROM GRAPH_TABLE(student_network 
+  MATCH (n) -[e1]- (m) -[e2]- (o) 
+  COLUMNS(VERTEX_ID(n) AS n, VERTEX_ID(m) AS m, n.age AS age))
+
+
+SELECT ID(n) AS n, ID(m) AS m, n.age AS age
+FROM MATCH (n:Person) -[e:friend_of]-> (m:Person) ON my_graph
+
Per each matched subgraph, the query returns two vertices `n` and `m` and the value for property age of vertex `n`. Note that edge `e` is omitted from the result even though it is used for describing the pattern. @@ -1222,16 +1358,26 @@ The `DISTINCT` modifier allows for filtering out duplicate results. The operatio It is possible to assign a variable name to any of the selection expression, by appending the keyword `AS` and a variable name. The variable name is used as the column name of the result set. In addition, the variable name can be later used in the `ORDER BY` clause. See the related section later in this document. -```sql -SELECT n.age * 2 - 1 AS pivot, n.name, n -FROM MATCH (n:Person) -> (m:Car) ON my_graph -ORDER BY pivot -``` +
+ + +
+SELECT age * 2 - 1 AS pivot, name
+FROM GRAPH_TABLE(my_graph 
+  MATCH(n IS Person) -> (m IS Car)
+  COLUMNS (n.age, n.name))
+ORDER BY pivot
+
+
+SELECT n.age * 2 - 1 AS pivot, n.name
+FROM MATCH (n:Person) -> (m:Car) ON my_graph
+ORDER BY pivot
+
### SELECT * `SELECT *` is a special `SELECT` clause. The semantic of `SELECT *` is to select all the variables in the graph pattern. - +Note that with the `GRAPH_TABLE` operator, it is not possible to select all variables in the `COLUMNS` clause. Instead, all needed properties has to be explicitly selected. Consider the following query: ```sql @@ -1259,25 +1405,35 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT label(n), n.* -FROM MATCH (n) ON financial_transactions -ORDER BY "number", "name" -``` +
+ + +
+SELECT *  
+FROM GRAPH_TABLE (financial_transactions 
+  MATCH(n) 
+  COLUMNS(n.*)) 
+ORDER BY "number", "name"
+
+
+SELECT n.*
+FROM MATCH (n) ON financial_transactions
+ORDER BY "number", "name"
+
``` -+-----------------------------+ -| label(n) | number | name | -+-----------------------------+ -| Account | 1001 | | -| Account | 2090 | | -| Account | 8021 | | -| Account | 10039 | | -| Person | | Camille | -| Person | | Liam | -| Person | | Nikita | -| Company | | Oracle | -+-----------------------------+ ++------------------+ +| number | name | ++------------------+ +| 1001 | | +| 2090 | | +| 8021 | | +| 10039 | | +| | Camille | +| | Liam | +| | Nikita | +| | Oracle | ++------------------+ ``` Label expressions are taken into account such that only properties are selected that belong to the specified vertex or @@ -1285,23 +1441,34 @@ edge labels: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT label(n), n.* -FROM MATCH (n:Person) ON financial_transactions -ORDER BY "name" -``` - -``` -+--------------------+ -| label(n) | name | -+--------------------+ -| Person | Camille | -| Person | Liam | -| Person | Nikita | -+--------------------+ +
+ + +
+SELECT  *  
+FROM GRAPH_TABLE (financial_transactions 
+  MATCH(n IS Person) 
+  COLUMNS(n.*)) 
+ORDER BY "name"
+
+
+SELECT n.*
+FROM MATCH (n:Person) ON financial_transactions
+ORDER BY "name"
+
+ +``` ++---------+ +| name | ++---------+ +| Camille | +| Liam | +| Nikita | ++---------+ ``` A `PREFIX` can be specified to avoid duplicate column names in case all properties of multiple vertex or edge variables are selected. +Note: `PREFIX` cannot be used within the GRAPH_TABLE operator. For example: @@ -1403,11 +1570,21 @@ GraphReference ::= For example: -```sql -SELECT p.first_name, p.last_name -FROM MATCH (p:Person) ON my_graph -ORDER BY p.first_name, p.last_name -``` +
+ + +
+SELECT first_name, last_name 
+FROM GRAPH_TABLE(my_graph
+  MATCH(p IS Person)
+  COLUMNS(p.first_name, p.last_name))
+ORDER BY first_name, last_name
+
+
+SELECT p.first_name, p.last_name
+FROM MATCH (p:Person) ON my_graph
+ORDER BY p.first_name, p.last_name
+
Above, the pattern `(p:Person)` is matched on graph `my_graph`. @@ -1418,6 +1595,7 @@ PGQL itself does not (yet) provide syntax for specifying a default graph, but Ja - Oracle's in-memory analytics engine PGX has the API `PgxGraph.queryPgql("SELECT ...")` such that the default graph corresponds to `PgxGraph.getName()` such that `ON` clauses can be omitted from queries. - Oracle's PGQL-on-RDBMS provides the API `PgqlConnection.setGraph("myGraph")` for setting the default graph such that the `ON` clauses can be omitted from queries. +Note: graph names have to be explicitly provided for the GRAPH_TABLE operator. If a default graph is provided then the `ON` clause can be omitted: ```sql @@ -1435,11 +1613,21 @@ Therefore, it is not possible for two `MATCH` clauses to have `ON` clauses with There can be multiple topology constraints in the `FROM` clause of a PGQL query. In such a case, vertex terms that have the same variable name correspond to the same vertex entity. For example, consider the following two lines of topology constraints: -```sql -SELECT * - FROM MATCH (n) -[e1]-> (m1) ON my_graph, - MATCH (n) -[e2]-> (m2) ON my_graph -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph
+  MATCH (n) -[e1]-> (m1),
+      (n) -[e2]-> (m2)
+  COLUMNS(n.*))
+
+
+SELECT n.*
+ FROM MATCH (n) -[e1]-> (m1) ON my_graph,
+      MATCH (n) -[e2]-> (m2) ON my_graph
+
Here, the vertex term `(n)` in the first constraint indeed refers to the same vertex as the vertex term `(n)` in the second constraint. It is an error, however, if two edge terms have the same variable name, or, if the same variable name is assigned to an edge term as well as to a vertex term in a single query. @@ -1449,26 +1637,55 @@ There are various ways in which a particular graph pattern can be specified. First, a single path pattern can be written as a chain of edge terms such that two consecutive edge terms share the common vertex term in between. For example: -```sql -SELECT * -FROM MATCH (n1) -[e1]-> (n2) -[e2]-> (n3) -[e3]-> (n4) ON my_graph -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n1) -[e1]-> (n2) -[e2]-> (n3) -[e3]-> (n4)
+  COLUMNS(n1.*))
+
+
+SELECT n1.*
+FROM MATCH (n1) -[e1]-> (n2) -[e2]-> (n3) -[e3]-> (n4) ON my_graph
+
The above graph pattern is equivalent to the graph pattern specified by the following set of comma-separate path patterns: -```sql -SELECT * -FROM MATCH (n1) -[e1]-> (n2) ON my_graph, - MATCH (n2) -[e2]-> (n3) ON my_graph, - MATCH (n3) -[e3]-> (n4) ON my_graph -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph
+  MATCH (n1) -[e1]-> (n2),
+      (n2) -[e2]-> (n3),
+      (n3) -[e3]-> (n4)
+  COLUMNS(n1.*))
+
+
+SELECT n1.*
+FROM MATCH (n1) -[e1]-> (n2) ON my_graph,
+     MATCH (n2) -[e2]-> (n3) ON my_graph,
+     MATCH (n3) -[e3]-> (n4) ON my_graph
+
Second, it is allowed to reverse the direction of an edge in the pattern, i.e. right-to-left instead of left-to-right. Therefore, the following is a valid graph pattern: -```sql -SELECT * -FROM MATCH (n1) -[e1]-> (n2) <-[e2]- (n3) ON my_graph -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph
+  MATCH (n1) -[e1]-> (n2) <-[e2]- (n3)
+  COLUMNS(n1.*))
+
+
+SELECT n1.*
+FROM MATCH (n1) -[e1]-> (n2) <-[e2]- (n3) ON my_graph
+
Please mind the edge directions in the above query – vertex `n2` is a common outgoing neighbor of both vertex `n1` and vertex `n3`. @@ -1513,21 +1730,41 @@ ColonOrIsKeyword ::= ':' Colons and `IS` keywords can be used interchangeably. +Note: With `GRAPH_TABLE` operator only `IS` is allowed. + Take the following example: -```sql -SELECT * -FROM MATCH (x:Person) -[e IS likes|knows]-> (y:Person) ON my_graph -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph
+  MATCH (x IS Person) -[e IS likes|knows]-> (y IS Person)
+  COLUMNS(x.*))
+
+
+SELECT x.*
+FROM MATCH (x:Person) -[e IS likes|knows]-> (y:Person) ON my_graph
+
Here, we specify that vertices `x` and `y` have the label `Person` and that the edge `e` has the label `likes` or the label `knows`. A label expression can be specified even when a variable is omitted. For example: -```sql -SELECT * -FROM MATCH (IS Person) -[:likes|knows]-> (IS Person) ON my_graph -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph
+  MATCH (x IS Person) -[IS likes|knows]-> (IS Person)
+  COLUMNS(x.*))
+
+
+SELECT x.*
+FROM MATCH (x IS Person) -[:likes|knows]-> (IS Person) ON my_graph
+
There are also built-in functions and predicates available for labels: @@ -1548,23 +1785,45 @@ WhereClause ::= 'WHERE' For example: -```sql -SELECT y.name - FROM MATCH (x) -> (y) ON my_graph - WHERE x.name = 'Jake' - AND y.age > 25 -``` +
+ + +
+SELECT name
+FROM GRAPH_TABLE(my_graph
+  MATCH(x) -> (y)
+  WHERE x.name = 'Jake'
+    AND y.age > 25
+  COLUMNS(y.name AS name))
+
+
+SELECT y.name
+  FROM MATCH (x) -> (y) ON my_graph
+ WHERE x.name = 'Jake'
+   AND y.age > 25
+
Here, the first filter describes that the vertex `x` has a property `name` and its value is `Jake`. Similarly, the second filter describes that the vertex `y` has a property `age` and its value is larger than `25`. Here, in the filter, the dot (`.`) operator is used for property access. For the detailed syntax and semantic of expressions, see [Functions and Expressions](#functions-and-expressions). Note that the ordering of constraints does not have an affect on the result, such that query from the previous example is equivalent to: -```sql -SELECT y.name - FROM MATCH (x) -> (y) ON my_graph -WHERE y.age > 25 - AND x.name = 'Jake' -``` +
+ + +
+SELECT name
+FROM GRAPH_TABLE(my_graph
+  MATCH(x) -> (y)
+  WHERE y.age > 25
+    AND x.name = 'Jake'
+  COLUMNS(y.name AS name))
+
+
+SELECT y.name
+ FROM MATCH (x) -> (y) ON my_graph
+WHERE y.age > 25
+  AND x.name = 'Jake'
+
## GRAPH_TABLE Operator @@ -1765,15 +2024,30 @@ An example is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT a.number AS a, - b.number AS b, - COUNT(e) AS pathLength, - ARRAY_AGG(e.amount) AS amounts -FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND b.number = 2090 -``` +
+ + +
+SELECT  a, b, pathLength, amounts 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->* (b IS Account) 
+  KEEP ANY SHORTEST 
+  WHERE a.number = 10039 
+    AND b.number = 2090 
+  COLUMNS(a.number AS a,
+          b.number AS b, 
+          COUNT(EDGE_ID(e)) AS pathLength,
+          ARRAY_AGG(e.amount) AS amounts))
+
+
+SELECT a.number AS a,
+       b.number AS b,
+       COUNT(e) AS pathLength,
+       ARRAY_AGG(e.amount) AS amounts
+FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE a.number = 10039 AND b.number = 2090
+
``` +------------------------------------------------------+ @@ -1788,13 +2062,25 @@ Shortest path finding is explained in more detail in [Shortest Path](#shortest-p Another example is: -```sql -SELECT LISTAGG(x.number, ', ') AS account_numbers, SUM(e.amount) AS total_amount -FROM MATCH SHORTEST 4 PATHS (a:Account) ((x:Account) <-[e:transaction]-)+ (a) - ON financial_transactions -WHERE a.number = 10039 -ORDER BY SUM(e.amount) -``` +
+ + +
+SELECT account_numbers, total_amount 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) ((x IS Account) <-[e IS transaction]-)+ (a) 
+  KEEP SHORTEST 4 PATHS 
+  WHERE a.number = 10039 
+  COLUMNS(LISTAGG(x.number, ', ') AS account_numbers, SUM(e.amount) AS total_amount)) 
+ORDER BY total_amount
+
+
+SELECT LISTAGG(x.number, ', ') AS account_numbers, SUM(e.amount) AS total_amount
+FROM MATCH SHORTEST 4 PATHS (a:Account) ((x:Account) <-[e:transaction]-)+ (a)
+       ON financial_transactions
+WHERE a.number = 10039
+ORDER BY SUM(e.amount)
+
``` +-----------------------------------------------------------------+ @@ -1832,13 +2118,25 @@ An example where we test for path existence is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT dst.number -FROM MATCH ANY (src:Account) -[e]->+ (dst:Account) - ON financial_transactions -WHERE src.number = 8021 -ORDER BY dst.number -``` +
+ + +
+SELECT number 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (src IS Account) -[e]->+ (dst IS Account) 
+  KEEP ANY 
+  WHERE src.number = 8021 
+  COLUMNS(dst.number)) 
+ORDER BY number
+
+
+SELECT dst.number
+FROM MATCH ANY (src:Account) -[e]->+ (dst:Account)
+       ON financial_transactions
+WHERE src.number = 8021
+ORDER BY dst.number
+
``` +--------+ @@ -1855,12 +2153,24 @@ An example where we return data along the path is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT dst.number, LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) -FROM MATCH ANY (src:Account) -[e]->+ (dst:Account) ON financial_transactions -WHERE src.number = 8021 -ORDER BY dst.number -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (src IS Account) -[e]->+ (dst IS Account) 
+  KEEP ANY 
+  WHERE src.number = 8021 
+  COLUMNS(dst.number, LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount))) 
+ORDER BY number
+
+
+SELECT dst.number, LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount)
+FROM MATCH ANY (src:Account) -[e]->+ (dst:Account) ON financial_transactions
+WHERE src.number = 8021
+ORDER BY dst.number
+
``` +---------------------------------------------------------------+ @@ -1907,13 +2217,25 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) AS total_amount -FROM MATCH ALL SHORTEST (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND b.number = 2090 -ORDER BY total_amount -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->* (b IS Account) 
+  KEEP ALL SHORTEST 
+  WHERE a.number = 10039 AND b.number = 2090 
+  COLUMNS(LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) AS total_amount)) 
+ORDER BY total_amount
+
+
+SELECT LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) AS total_amount
+FROM MATCH ALL SHORTEST (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE a.number = 10039 AND b.number = 2090
+ORDER BY total_amount
+
``` +--------------------------------------------------+ @@ -1936,30 +2258,60 @@ AnyShortestPathSearch ::= 'ANY' 'SHORTEST' ? ? For example: -```sql -SELECT src, SUM(e.weight), dst -FROM MATCH ANY SHORTEST (src) -[e]->* (dst) ON financial_transactions -WHERE src.age < dst.age -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH ANY SHORTEST (src) -[e]->* (dst)
+  WHERE src.age < dst.age
+  COLUMNS(src, SUM(e.weight), dst))
+
+
+SELECT src, SUM(e.weight), dst
+FROM MATCH ANY SHORTEST (src) -[e]->* (dst) ON financial_transactions
+WHERE src.age < dst.age
+
Another example is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT COUNT(e) AS num_hops - , p1.name AS start - , ARRAY_AGG ( CASE - WHEN dst IS LABELED Account - THEN CAST(dst.number AS STRING) - ELSE dst.name +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH(p1 IS Person) (-[e]- (dst))* (p2 IS Person) 
+  KEEP ANY SHORTEST 
+  WHERE p1.name = 'Camille' 
+    AND p2.name = 'Liam' 
+  COLUMNS(COUNT(edge_id(e)) AS num_hops , 
+          p1.name AS start , 
+          ARRAY_AGG ( CASE 
+                        WHEN dst IS LABELED Account 
+                          THEN CAST(dst.number AS STRING) 
+                        ELSE dst.name 
+                      END 
+                    ) AS path)) 
+ORDER BY num_hops
+
+
+SELECT COUNT(e) AS num_hops
+     , p1.name AS start
+     , ARRAY_AGG ( CASE
+                     WHEN dst IS LABELED Account
+                       THEN CAST(dst.number AS STRING)
+                     ELSE dst.name
                    END
-                 ) AS path
-FROM MATCH ANY SHORTEST (p1:Person) (-[e]- (dst))* (p2:Person)
-       ON financial_transactions
-WHERE p1.name = 'Camille' AND p2.name = 'Liam'
-ORDER BY num_hops
-```
+                 ) AS path
+FROM MATCH ANY SHORTEST (p1:Person) (-[e]- (dst))* (p2:Person)
+       ON financial_transactions
+WHERE p1.name = 'Camille' AND p2.name = 'Liam'
+ORDER BY num_hops
+
``` +------------------------------------------+ @@ -1973,18 +2325,39 @@ Filters on vertices and edges along paths can be specified by adding a `WHERE` c For example, the following query matches a shortest path (if one exists) such that each edge along the path has a property `weight` with a value greater than `10`: -```sql -SELECT src, ARRAY_AGG(e.weight), dst -FROM MATCH ANY SHORTEST (src) (-[e]-> WHERE e.weight > 10)* (dst) ON my_graph -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph 
+  MATCH (src) (-[e]-> WHERE e.weight > 10)* (dst) 
+  KEEP ANY SHORTEST 
+  COLUMNS(src.*, ARRAY_AGG(e.weight), dst.*))
+
+
+SELECT src.*, ARRAY_AGG(e.weight), dst.*
+FROM MATCH ANY SHORTEST (src) (-[e]-> WHERE e.weight > 10)* (dst) ON my_graph
+
Note that this is different from a `WHERE` clause that is placed outside of the quantified pattern: -```sql -SELECT src, ARRAY_AGG(e.weight), dst -FROM MATCH ANY SHORTEST (src) -[e]->* (dst) ON my_graph -WHERE SUM(e.cost) < 100 -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph 
+  MATCH (src) -[e]->* (dst) 
+  KEEP ANY SHORTEST
+  WHERE e.weight > 10
+  COLUMNS(src.*, ARRAY_AGG(e.weight), dst.*))
+
+
+SELECT src.*, ARRAY_AGG(e.weight), dst.*
+FROM MATCH ANY SHORTEST (src) -[e]->* (dst) ON my_graph
+WHERE SUM(e.cost) < 100
+
Here, the filter is applied only _after_ a shortest path is matched such that if the `WHERE` condition is not satisfied, the path is filtered out and no other path is considered even though another path may exist that does satisfy the `WHERE` condition. @@ -2003,11 +2376,22 @@ NumberOfPaths ::= For example the following query will output the sum of the edge weights along each of the shortest 3 paths between each of the matched source and destination pairs: -```sql -SELECT src, SUM(e.weight), dst -FROM MATCH SHORTEST 3 PATHS (src) -[e]->* (dst) ON my_graph -WHERE src.age < dst.age -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(my_graph 
+  MATCH (src) -[e]->* (dst)
+  KEEP SHORTEST 3 PATHS
+  WHERE src.age < dst.age
+  COLUMNS(src.*, SUM(e.weight), dst.*))
+
+
+SELECT src.*, SUM(e.weight), dst.*
+FROM MATCH SHORTEST 3 PATHS (src) -[e]->* (dst) ON my_graph
+WHERE src.age < dst.age
+
Notice that the sum aggregation is computed for each matching path. In other words, the number of rows returned by the query is equal to the number of paths that match, which is at most three times the number of possible source-destination pairs. @@ -2015,11 +2399,22 @@ query is equal to the number of paths that match, which is at most three times t The `ARRAY_AGG` construct allows users to output properties of edges/vertices along the path. For example, in the following query: -```sql -SELECT src, ARRAY_AGG(e.weight), ARRAY_AGG(v1.age), ARRAY_AGG(v2.age), dst -FROM MATCH SHORTEST 3 PATHS (src) ((v1) -[e]-> (v2))* (dst) ON my_graph -WHERE src.age < dst.age -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (src) ((v1) -[e]-> (v2))* (dst) 
+  KEEP SHORTEST 3 PATHS 
+  WHERE src.age < dst.age 
+  COLUMNS(src.*, ARRAY_AGG(e.weight), ARRAY_AGG(v1.age), ARRAY_AGG(v2.age), dst.*))
+
+
+SELECT src.*, ARRAY_AGG(e.weight), ARRAY_AGG(v1.age), ARRAY_AGG(v2.age), dst.*
+FROM MATCH SHORTEST 3 PATHS (src) ((v1) -[e]-> (v2))* (dst) ON my_graph
+WHERE src.age < dst.age
+
the `ARRAY_AGG(e.weight)` outputs a list containing the weight property of all the edges along the path, @@ -2028,7 +2423,7 @@ the `ARRAY_AGG(v1.cost)` outputs a list containing the age property of all the v the `ARRAY_AGG(v2.cost)` outputs a list containing the age property of all the vertices along the path except the first one. -Users can also compose shortest path constructs with other matching operators: +Users can also compose shortest path constructs with other matching operators (Note that this behavior is not allowed with `GRAPH_TABLE` operator): ```sql SELECT ARRAY_AGG(e1.weight), ARRAY_AGG(e2.weight) @@ -2042,15 +2437,29 @@ Another example is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT COUNT(e) AS num_hops - , SUM(e.amount) AS total_amount - , ARRAY_AGG(e.amount) AS amounts_along_path -FROM MATCH SHORTEST 7 PATHS (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND a = b -ORDER BY num_hops, total_amount -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->* (b IS Account) 
+  KEEP SHORTEST 7 PATHS 
+  WHERE a.number = 10039 AND VERTEX_EQUAL(a, b) 
+  COLUMNS(COUNT(EDGE_ID(e)) AS num_hops , 
+        SUM(e.amount) AS total_amount , 
+        ARRAY_AGG(e.amount) AS amounts_along_path))
+ORDER BY num_hops, total_amount
+
+
+SELECT COUNT(e) AS num_hops
+     , SUM(e.amount) AS total_amount
+     , ARRAY_AGG(e.amount) AS amounts_along_path
+FROM MATCH SHORTEST 7 PATHS (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE a.number = 10039 AND a = b
+ORDER BY num_hops, total_amount
+
``` +--------------------------------------------------------------------------------------------+ @@ -2071,15 +2480,29 @@ The following example shows how such paths could be filtered out, such that we o {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT COUNT(e) AS num_hops - , SUM(e.amount) AS total_amount - , ARRAY_AGG(e.amount) AS amounts_along_path -FROM MATCH SHORTEST 7 PATHS (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND a = b AND COUNT(DISTINCT e) = COUNT(e) AND COUNT(e) > 0 -ORDER BY num_hops, total_amount -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->* (b IS Account) 
+  KEEP SHORTEST 7 PATHS 
+  WHERE a.number = 10039 AND VERTEX_EQUAL(a, b) AND COUNT(DISTINCT EDGE_ID(e)) = COUNT(EDGE_ID(e)) AND COUNT(EDGE_ID(e)) > 0 
+  COLUMNS(COUNT(EDGE_ID(e)) AS num_hops , 
+          SUM(e.amount) AS total_amount , 
+          ARRAY_AGG(e.amount) AS amounts_along_path)) 
+ORDER BY num_hops, total_amount
+
+
+SELECT COUNT(e) AS num_hops
+     , SUM(e.amount) AS total_amount
+     , ARRAY_AGG(e.amount) AS amounts_along_path
+FROM MATCH SHORTEST 7 PATHS (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE a.number = 10039 AND a = b AND COUNT(DISTINCT e) = COUNT(e) AND COUNT(e) > 0
+ORDER BY num_hops, total_amount
+
``` +------------------------------------------------------------+ @@ -2288,13 +2711,24 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) AS total_amount -FROM MATCH ALL (a:Account) -[e:transaction]->{,7} (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND b.number = 2090 -ORDER BY total_amount -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->{,7} (b IS Account) 
+  KEEP ALL WHERE a.number = 10039 AND b.number = 2090 
+  COLUMNS(LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) AS total_amount)) 
+ORDER BY total_amount
+
+
+SELECT LISTAGG(e.amount, ' + ') || ' = ', SUM(e.amount) AS total_amount
+FROM MATCH ALL (a:Account) -[e:transaction]->{,7} (b:Account)
+      ON financial_transactions
+WHERE a.number = 10039 AND b.number = 2090
+ORDER BY total_amount
+
``` +--------------------------------------------------------------------------------+ @@ -2368,12 +2802,23 @@ It is possible to mix vertical and horizontal aggregation in a single query. For {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT SUM(COUNT(e)) AS sumOfPathLengths -FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND (b.number = 1001 OR b.number = 2090) -``` +
+ + +
+SELECT SUM(countOfPathLengths) AS sumOfPathLengths 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) - [e IS transaction] -> * (b IS Account) 
+  KEEP ANY SHORTEST 
+  WHERE a.number = 10039 AND ( b.number = 1001 OR b.number = 2090 ) 
+  COLUMNS(COUNT(EDGE_ID(e)) AS countOfPathLengths))
+
+
+SELECT SUM(COUNT(e)) AS sumOfPathLengths
+FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE a.number = 10039 AND (b.number = 1001 OR b.number = 2090)
+
``` +------------------+ @@ -2396,17 +2841,33 @@ An example of a horizontal aggregation in `WHERE` is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT b.number AS b, - COUNT(e) AS pathLength, - ARRAY_AGG(e.amount) AS transactions -FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE a.number = 10039 AND - (b.number = 8021 OR b.number = 1001 OR b.number = 2090) AND - COUNT(e) <= 2 -ORDER BY pathLength -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->* (b IS Account) 
+  KEEP ANY SHORTEST 
+  WHERE a.number = 10039 AND 
+        (b.number = 8021 OR b.number = 1001 OR b.number = 2090) AND 
+        COUNT(EDGE_ID(e)) <= 2 
+  COLUMNS(b.number AS b,
+          COUNT(EDGE_ID(e)) AS pathLength, 
+          ARRAY_AGG(e.amount) AS transactions)) 
+ORDER BY pathLength
+
+
+SELECT b.number AS b,
+       COUNT(e) AS pathLength,
+       ARRAY_AGG(e.amount) AS transactions
+FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE a.number = 10039 AND
+      (b.number = 8021 OR b.number = 1001 OR b.number = 2090) AND
+      COUNT(e) <= 2
+ORDER BY pathLength
+
``` +--------------------------------------+ @@ -2424,16 +2885,30 @@ An example of a horizontal aggregation in `GROUP BY` is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT COUNT(e) AS pathLength, - COUNT(*) AS cnt -FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account) - ON financial_transactions -WHERE (a.number = 10039 OR a.number = 8021) AND - (b.number = 1001 OR b.number = 2090) -GROUP BY COUNT(e) -ORDER BY pathLength -``` +
+ + +
+SELECT pathLength, COUNT(*) AS cnt 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[e IS transaction]->* (b IS Account) 
+  KEEP ANY SHORTEST 
+  WHERE (a.number = 10039 OR a.number = 8021) AND 
+        (b.number = 1001 OR b.number = 2090) 
+  COLUMNS(COUNT(EDGE_ID(e)) AS pathLength)) 
+GROUP BY pathLength 
+ORDER BY pathLength
+
+
+SELECT COUNT(e) AS pathLength,
+       COUNT(*) AS cnt
+FROM MATCH ANY SHORTEST (a:Account) -[e:transaction]->* (b:Account)
+       ON financial_transactions
+WHERE (a.number = 10039 OR a.number = 8021) AND
+      (b.number = 1001 OR b.number = 2090)
+GROUP BY COUNT(e)
+ORDER BY pathLength
+
``` +------------------+ @@ -2503,12 +2978,23 @@ An example with `TRAIL` is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path -FROM MATCH ALL TRAIL PATHS (a:account) (-[:transaction]-> (x)){2,} (b:Account) - ON financial_transactions -WHERE a.number = 8021 AND b.number = 1001 -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS account) (-[ IS transaction]-> (x)){2,} (b IS Account) 
+  KEEP ALL TRAIL PATHS 
+  WHERE a.number = 8021 AND b.number = 1001 
+  COLUMNS(CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path))
+
+
+SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path
+FROM MATCH ALL TRAIL PATHS (a:account) (-[:transaction]-> (x)){2,} (b:Account)
+       ON financial_transactions
+WHERE a.number = 8021 AND b.number = 1001
+
``` +-----------------------------------------------+ @@ -2525,12 +3011,23 @@ An example with `ACYCLIC` is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path -FROM MATCH SHORTEST 10 ACYCLIC PATHS (a:account) (-[:transaction]-> (x))+ (b) - ON financial_transactions -WHERE a.number = 10039 AND b.number = 1001 -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS account) (-[ IS transaction]-> (x))+ (b) 
+  KEEP SHORTEST 10 ACYCLIC PATHS 
+  WHERE a.number = 10039 AND b.number = 1001 
+  COLUMNS(CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path))
+
+
+SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path
+FROM MATCH SHORTEST 10 ACYCLIC PATHS (a:account) (-[:transaction]-> (x))+ (b)
+       ON financial_transactions
+WHERE a.number = 10039 AND b.number = 1001
+
``` +-----------------------+ @@ -2547,12 +3044,23 @@ An example with `SIMPLE` is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path -FROM MATCH ANY SIMPLE PATH (a:account) (-[:transaction]-> (x))+ (a) - ON financial_transactions -WHERE a.number = 10039 -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS account) (-[ IS transaction]-> (x))+ (a) 
+  KEEP ANY SIMPLE PATH 
+  WHERE a.number = 10039 
+  COLUMNS(CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path))
+
+
+SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path
+FROM MATCH ANY SIMPLE PATH (a:account) (-[:transaction]-> (x))+ (a)
+       ON financial_transactions
+WHERE a.number = 10039
+
``` +----------------------------------------+ @@ -2608,13 +3116,24 @@ An example where the keywords `ONE ROW PER MATCH` are explicitly specified is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT a.number, p.name -FROM MATCH (a:Account) -[:owner]-> (p:Person) - ON financial_transactions - ONE ROW PER MATCH -ORDER BY a.number -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[ IS owner]-> (p IS Person) 
+  ONE ROW PER MATCH 
+  COLUMNS(a.number, p.name)) 
+ORDER BY number
+
+
+SELECT a.number, p.name
+FROM MATCH (a:Account) -[:owner]-> (p:Person)
+       ON financial_transactions
+       ONE ROW PER MATCH
+ORDER BY a.number
+
``` +------------------+ @@ -2671,14 +3190,27 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT v.number AS account_nr, ELEMENT_NUMBER(v) AS elem_nr -FROM MATCH ANY (a1:Account) -[:transaction]->* (a2:Account) - ON financial_transactions - ONE ROW PER VERTEX ( v ) -WHERE a1.number = 1001 AND a2.number = 8021 -ORDER BY ELEMENT_NUMBER(v) -``` +
+ + +
+SELECT *
+FROM GRAPH_TABLE(financial_transactions
+  MATCH (a1 IS Account) -[ IS transaction]->* (a2 IS Account)
+  KEEP ANY
+  WHERE a1.number = 1001 AND a2.number = 8021
+  ONE ROW PER VERTEX ( v )
+  COLUMNS(v.number AS account_nr, ELEMENT_NUMBER(v) AS elem_nr))
+ORDER BY elem_nr
+
+
+SELECT v.number AS account_nr, ELEMENT_NUMBER(v) AS elem_nr
+FROM MATCH ANY (a1:Account) -[:transaction]->* (a2:Account)
+       ON financial_transactions
+       ONE ROW PER VERTEX ( v )
+WHERE a1.number = 1001 AND a2.number = 8021
+ORDER BY ELEMENT_NUMBER(v)
+
``` +----------------------+ @@ -2745,16 +3277,31 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr - , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr - , ELEMENT_NUMBER(v2) AS v2_elem_nr -FROM MATCH ANY (a1:Account) -[:transaction]->+ (a2:Account) - ON financial_transactions - ONE ROW PER STEP ( v1, e, v2 ) -WHERE a1.number = 1001 AND a2.number = 8021 -ORDER BY ELEMENT_NUMBER(e) -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH  (a1 IS Account) -[ IS transaction]->+ (a2 IS Account) 
+  KEEP ANY 
+  WHERE a1.number = 1001 AND a2.number = 8021 
+  ONE ROW PER STEP ( v1, e, v2 ) 
+  COLUMNS(v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr, 
+          ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr,
+          ELEMENT_NUMBER(v2) AS v2_elem_nr)) 
+ORDER BY e_elem_nr
+
+
+SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr
+     , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr
+     , ELEMENT_NUMBER(v2) AS v2_elem_nr
+FROM MATCH ANY (a1:Account) -[:transaction]->+ (a2:Account)
+       ON financial_transactions
+       ONE ROW PER STEP ( v1, e, v2 )
+WHERE a1.number = 1001 AND a2.number = 8021
+ORDER BY ELEMENT_NUMBER(e)
+
``` +------------------------------------------------------------------------------+ @@ -2776,16 +3323,31 @@ The following example is the same as above but with the direction of the edge pa {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr - , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr - , ELEMENT_NUMBER(v2) AS v2_elem_nr -FROM MATCH ANY (a2:Account) <-[:transaction]-+ (a1:Account) - ON financial_transactions - ONE ROW PER STEP ( v1, e, v2 ) -WHERE a1.number = 1001 AND a2.number = 8021 -ORDER BY ELEMENT_NUMBER(e) -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH  (a2 IS Account) <-[ IS transaction]-+ (a1 IS Account) 
+  KEEP ANY 
+  WHERE a1.number = 1001 AND a2.number = 8021 
+  ONE ROW PER STEP ( v1, e, v2 ) 
+  COLUMNS(v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr, 
+          ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr,
+          ELEMENT_NUMBER(v2) AS v2_elem_nr)) 
+ORDER BY e_elem_nr
+
+
+SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr
+     , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr
+     , ELEMENT_NUMBER(v2) AS v2_elem_nr
+FROM MATCH ANY (a2:Account) <-[:transaction]-+ (a1:Account)
+       ON financial_transactions
+       ONE ROW PER STEP ( v1, e, v2 )
+WHERE a1.number = 1001 AND a2.number = 8021
+ORDER BY ELEMENT_NUMBER(e)
+
``` +------------------------------------------------------------------------------+ @@ -2851,11 +3413,21 @@ The `GROUP BY` clause starts with the keywords GROUP BY and is followed by a com Consider the following query: -```sql -SELECT n.first_name, COUNT(*), AVG(n.age) -FROM MATCH (n:Person) ON my_graph -GROUP BY n.first_name -``` +
+ + +
+SELECT first_name, COUNT(*), AVG(age) 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n IS Person)
+  COLUMNS(n.first_name, n.age)) 
+GROUP BY first_name
+
+
+SELECT n.first_name, COUNT(*), AVG(n.age)
+FROM MATCH (n:Person) ON my_graph
+GROUP BY n.first_name
+
Matches are grouped by their values for `n.first_name`. For each group, the query selects `n.first_name` (i.e. the group key), the number of solutions in the group (i.e. `COUNT(*)`), and the average value of the property age for vertex n (i.e. `AVG(n.age)`). @@ -2865,11 +3437,21 @@ It is possible that the `GROUP BY` clause consists of multiple terms. In such a Consider the following query: -```sql -SELECT n.first_name, n.last_name, COUNT(*) -FROM MATCH (n:Person) ON my_graph -GROUP BY n.first_name, n.last_name -``` +
+ + +
+SELECT first_name, last_name, COUNT(*) 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n IS Person) 
+  COLUMNS(n.first_name, n.last_name)) 
+GROUP BY first_name, last_name
+
+
+SELECT n.first_name, n.last_name, COUNT(*)
+FROM MATCH (n:Person) ON my_graph
+GROUP BY n.first_name, n.last_name
+
Matches will be grouped together only if they hold the same values for `n.first_name` and the same values for `n.last_name`. @@ -2886,12 +3468,23 @@ The group for which all the group keys are null is a valid group and takes part To filter out such a group, use the [HAVING clause](#having-clause). Foror example: -```sql -SELECT n.prop1, n.prop2, COUNT(*) -FROM MATCH (n) ON my_graph -GROUP BY n.prop1, n.prop2 -HAVING n.prop1 IS NOT NULL AND n.prop2 IS NOT NULL -``` +
+ + +
+SELECT prop1, prop2, COUNT(*) 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  COLUMNS(n.prop1, n.prop2))
+GROUP BY prop1, prop2 
+HAVING prop1 IS NOT NULL AND prop2 IS NOT NULL
+
+
+SELECT n.prop1, n.prop2, COUNT(*)
+FROM MATCH (n) ON my_graph
+GROUP BY n.prop1, n.prop2
+HAVING n.prop1 IS NOT NULL AND n.prop2 IS NOT NULL
+
### Repetition of Group Expression in Select or Order Expression @@ -2899,12 +3492,23 @@ Group expressions may be repeated in select or order expressions. Consider the following query: -```sql -SELECT n.age, COUNT(*) -FROM MATCH (n) ON my_graph -GROUP BY n.age -ORDER BY n.age -``` +
+ + +
+SELECT age, COUNT(*) 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  COLUMNS(n.age)) 
+GROUP BY age 
+ORDER BY age
+
+
+SELECT n.age, COUNT(*)
+FROM MATCH (n) ON my_graph
+GROUP BY n.age
+ORDER BY n.age
+
Here, the group expression `n.age` is repeated in the SELECT and ORDER BY. @@ -2969,16 +3573,33 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT label(owner), - COUNT(*) AS numTransactions, - SUM(out.amount) AS totalOutgoing, - LISTAGG(out.amount, ', ') AS amounts -FROM MATCH (a:Account) -[:owner]-> (owner:Person|Company) ON financial_transactions - , MATCH (a) -[out:transaction]-> (:Account) ON financial_transactions -GROUP BY label(owner) -ORDER BY label(owner) -``` +
+ + +
+SELECT owner_lbl, 
+       COUNT(*) AS numTransactions, 
+       SUM(amount) AS totalOutgoing, 
+       LISTAGG(amount, ', ') AS amounts 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[ IS owner]-> (owner IS Person|Company),
+        (a) -[out IS transaction]-> ( IS Account) 
+  COLUMNS(CASE WHEN owner IS LABELED Person THEN 'Person' 
+            ELSE 'Company' END AS owner_lbl, 
+          out.amount))
+GROUP BY owner_lbl 
+ORDER BY owner_lbl
+
+
+SELECT label(owner),
+       COUNT(*) AS numTransactions,
+       SUM(out.amount) AS totalOutgoing,
+       LISTAGG(out.amount, ', ') AS amounts
+FROM MATCH (a:Account) -[:owner]-> (owner:Person|Company) ON financial_transactions
+   , MATCH (a) -[out:transaction]-> (:Account) ON financial_transactions
+GROUP BY label(owner)
+ORDER BY label(owner)
+
``` +---------------------------------------------------------------------------------+ @@ -2999,13 +3620,25 @@ If _no_ `GROUP BY` is specified, aggregations are applied to the entire set of s {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT COUNT(*) AS numTransactions, - SUM(out.amount) AS totalOutgoing, - LISTAGG(out.amount, ', ') AS amounts -FROM MATCH (a:Account) -[:owner]-> (owner:Person|Company) ON financial_transactions - , MATCH (a) -[out:transaction]-> (:Account) ON financial_transactions -``` +
+ + +
+SELECT COUNT(*) AS numTransactions,
+      SUM(amount) AS totalOutgoing, 
+      LISTAGG(amount, ', ') AS amounts 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a IS Account) -[ IS owner]-> (owner IS Person|Company),
+        (a) -[out IS transaction]-> ( IS Account)
+  COLUMNS(out.amount))
+
+
+SELECT COUNT(*) AS numTransactions,
+       SUM(out.amount) AS totalOutgoing,
+       LISTAGG(out.amount, ', ') AS amounts
+FROM MATCH (a:Account) -[:owner]-> (owner:Person|Company) ON financial_transactions
+   , MATCH (a) -[out:transaction]-> (:Account) ON financial_transactions
+
``` +--------------------------------------------------------------------------+ @@ -3023,10 +3656,19 @@ Note that the result will always be a single row, unless nothing was matched in For example: -```sql -SELECT COUNT(*) -FROM MATCH (m:Person) ON my_graph -``` +
+ + +
+SELECT COUNT(*) 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (m IS Person) 
+  COLUMNS(m.*))
+
+
+SELECT COUNT(*)
+FROM MATCH (m:Person) ON my_graph
+
### DISTINCT in aggregation @@ -3034,10 +3676,19 @@ The `DISTINCT` modifier specifies that duplicate values should be removed before For example: -```sql -SELECT AVG(DISTINCT m.age) -FROM MATCH (m:Person) ON my_graph -``` +
+ + +
+SELECT AVG(DISTINCT age) 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (m IS Person) 
+  COLUMNS(m.age))
+
+
+SELECT AVG(DISTINCT m.age)
+FROM MATCH (m:Person) ON my_graph
+
Here, we aggregate only over distinct `m.age` values. @@ -3055,12 +3706,23 @@ The value expression needs to be a boolean expression. For example: -```sql -SELECT n.name -FROM MATCH (n) -[:has_friend]-> (m) ON my_graph -GROUP BY n -HAVING COUNT(m) > 10 -``` +
+ + +
+SELECT id, name 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) -[IS has_friend]-> (m) 
+  COLUMNS(VERTEX_ID(n) AS id, n.name, VERTEX_ID(m) AS m)) 
+GROUP BY id, name 
+HAVING COUNT(m) > 10
+
+
+SELECT id(n) AS id, n.name
+FROM MATCH (n) -[:has_friend]-> (m) ON my_graph
+GROUP BY id, name
+HAVING COUNT(m) > 10
+
This query returns the names of people who have more than 10 friends. @@ -3088,11 +3750,21 @@ The `ORDER BY` clause starts with the keywords `ORDER BY` and is followed by com The following is an example in which the results are ordered by property access `n.age` in ascending order: -```sql -SELECT n.name -FROM MATCH (n:Person) ON my_graph -ORDER BY n.age ASC -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n IS Person)
+  COLUMNS(n.name, n.age)) 
+ORDER BY age ASC
+
+
+SELECT n.name
+FROM MATCH (n:Person) ON my_graph
+ORDER BY n.age ASC
+
### Data types for ORDER BY @@ -3109,11 +3781,21 @@ Vertices, edges and arrays cannot be ordered directly. An `ORDER BY` may contain more than one expression, in which case the expresisons are evaluated from left to right. That is, (n+1)th ordering term is used only for the tie-break rule for n-th ordering term. Note that different expressions can have different ascending or descending decorators. -```sql -SELECT f.name -FROM MATCH (f:Person) ON my_graph -ORDER BY f.age ASC, f.salary DESC -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (f IS Person) 
+  COLUMNS(f.name, f.age, f.salary)) 
+ORDER BY age ASC, salary DESC
+
+
+SELECT f.name
+FROM MATCH (f:Person) ON my_graph
+ORDER BY f.age ASC, f.salary DESC
+
## OFFSET Clause @@ -3136,12 +3818,23 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT n.name -FROM MATCH (n:Person) ON my_graph -ORDER BY n.name -OFFSET 1 -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (n IS Person) 
+  COLUMNS(n.name)) 
+ORDER BY name 
+OFFSET 1
+
+
+SELECT n.name
+FROM MATCH (n:Person) ON financial_transactions
+ORDER BY n.name
+OFFSET 1
+
``` +--------+ @@ -3149,7 +3842,6 @@ OFFSET 1 +--------+ | Liam | | Nikita | -| Oracle | +--------+ ``` @@ -3170,30 +3862,42 @@ FetchFirstQuantity ::= The `FETCH FIRST` clause is applied after the [OFFSET clause](#offset-clause). If there are fewer solutions than the fetch quantity, all solutions are returned. -For example, in the following query the first solution is pruned from the result (`OFFSET 1`) and the next two solutions are fetched (`FETCH FIRST 2 ROWS ONLY`). +For example, in the following query the first solution is pruned from the result (`OFFSET 1`) and the next one solution is fetched (`FETCH FIRST 1 ROWS ONLY`). {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT n.name -FROM MATCH (n:Person) ON financial_transactions -ORDER BY n.name -OFFSET 1 -FETCH FIRST 2 ROWS ONLY -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (n IS Person) 
+  COLUMNS(n.name)) 
+ORDER BY name 
+OFFSET 1 
+FETCH FIRST 1 ROWS ONLY
+
+
+SELECT n.name
+FROM MATCH (n:Person) ON financial_transactions
+ORDER BY n.name
+OFFSET 1
+FETCH FIRST 1 ROWS ONLY
+
``` +--------+ | name | +--------+ | Liam | -| Nikita | +--------+ ``` ## LIMIT Clause The `LIMIT` clause provides a syntactic alternative to the [FETCH FIRST clause](#fetch-first-clause). +Note that `LIMIT` cannot be used with GRAPH_TABLE operator. The syntax is: @@ -3213,7 +3917,7 @@ SELECT n.name FROM MATCH (n:Person) ON financial_transactions ORDER BY n.name OFFSET 1 -LIMIT 2 +LIMIT 1 ``` ``` @@ -3221,7 +3925,6 @@ LIMIT 2 | name | +--------+ | Liam | -| Nikita | +--------+ ``` @@ -3365,30 +4068,63 @@ BindVariable ::= '?' An example query with two bind variables is as follows: -```sql -SELECT n.age -FROM MATCH (n) ON my_graph -WHERE n.name = ? - OR n.age > ? -``` +
+ + +
+SELECT age 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  WHERE n.name = ? 
+     OR n.age > ? 
+  COLUMNS(n.age))
+
+
+SELECT n.age
+FROM MATCH (n) ON my_graph
+WHERE n.name = ?
+   OR n.age > ?
+
In the following query, bind variables are used in `OFFSET` and `FETCH FIRST`: -```sql -SELECT n.name, n.age -FROM MATCH (n) ON my_graph -ORDER BY n.age -OFFSET ? -FETCH FIRST ? ROWS ONLY -``` +
+ + +
+SELECT name, age 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  COLUMNS(n.name, n.age)) 
+ORDER BY age 
+OFFSET ? 
+FETCH FIRST ? ROWS ONLY
+
+
+SELECT n.name, n.age
+FROM MATCH (n) ON my_graph
+ORDER BY n.age
+OFFSET ?
+FETCH FIRST ? ROWS ONLY
+
The following example shows a bind variable in the position of a label: -```sql -SELECT n.name -FROM MATCH (n) ON my_graph -WHERE label(n) = ? -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  WHERE n IS LABELED ? 
+  COLUMNS(n.name))
+
+
+SELECT n.name
+FROM MATCH (n) ON my_graph
+WHERE label(n) = ?
+
## Operators @@ -3533,11 +4269,21 @@ NullPredicate ::= 'IS' ('NOT')? 'NULL' For example: -```sql -SELECT n.name -FROM MATCH (n) ON my_graph -WHERE n.name IS NOT NULL -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  WHERE n.name IS NOT NULL 
+  COLUMNS(n.name))
+
+
+SELECT n.name
+FROM MATCH (n) ON my_graph
+WHERE n.name IS NOT NULL
+
Here, we find all the vertices in the graph that have the property `name` and then return the property. @@ -3570,6 +4316,8 @@ ID( vertex/edge ) The `LABEL` function returns the label of a vertex or an edge. It is an error if the vertex or edge does not have a label, or, has more than one label. The return type of the function is a string. +Note: `LABEL` function is not allowed with `GRAPH_TABLE` operator. + The syntax is: ``` @@ -3598,6 +4346,8 @@ FROM MATCH (n:Person) -[e]-> (m:Person) ON my_graph The `labels` function returns the set of labels of a vertex or an edge. If the vertex or edge does not have a label, an empty set is returned. The return type of the function is a set of strings. +Note: `LABELS` function is not allowed with `GRAPH_TABLE` operator. + The syntax is: ``` @@ -3635,11 +4385,21 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT a.number, - CASE WHEN n IS LABELED Person THEN 'Personal Account' ELSE 'Business Account' END AS accountType -FROM MATCH (n:Person|Company) <-[:owner]- (a:Account) ON financial_transactions -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (n IS Person|Company) <-[ IS owner]- (a IS Account)
+  COLUMNS(a.number, 
+        CASE WHEN n IS LABELED Person THEN 'Personal Account' ELSE 'Business Account' END AS accountType)) 
+
+
+SELECT a.number,
+       CASE WHEN n IS LABELED Person THEN 'Personal Account' ELSE 'Business Account' END AS accountType
+FROM MATCH (n:Person|Company) <-[:owner]- (a:Account) ON financial_transactions
+
``` +---------------------------+ @@ -3670,12 +4430,23 @@ EdgeReference ::= For example: -```sql -SELECT e.amount, CASE WHEN n IS SOURCE OF e THEN 'Outgoing transaction' ELSE 'Incoming transaction' END AS transaction_type -FROM MATCH (n:Account) -[e:transaction]- (m:Account) ON financial_transactions -WHERE n.number = 8021 -ORDER BY transaction_type, e.amount -``` +
+ + +
+SELECT amount, transaction_type 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (n IS Account) -[e IS transaction]- (m IS Account) 
+  WHERE n.number = 8021 
+  COLUMNS(e.amount, CASE WHEN n IS SOURCE OF e THEN 'Outgoing transaction' ELSE 'Incoming transaction' END AS transaction_type)) 
+ORDER BY transaction_type, amount
+
+
+SELECT e.amount, CASE WHEN n IS SOURCE OF e THEN 'Outgoing transaction' ELSE 'Incoming transaction' END AS transaction_type
+FROM MATCH (n:Account) -[e:transaction]- (m:Account) ON financial_transactions
+WHERE n.number = 8021
+ORDER BY transaction_type, e.amount
+
``` +-------------------------------+ @@ -3689,14 +4460,29 @@ ORDER BY transaction_type, e.amount Another example is: -```sql -SELECT n.number, n.name, - SUM(CASE WHEN n IS DESTINATION OF e THEN 1 ELSE 0 END) AS num_incoming_edges, - SUM(CASE WHEN n IS SOURCE OF e THEN 1 ELSE 0 END) AS num_outgoing_edges -FROM MATCH (n) -[e]- (m) ON financial_transactions -GROUP BY number, name -ORDER BY num_incoming_edges + num_outgoing_edges DESC, number, name -``` +
+ + +
+SELECT number, name, 
+      SUM(incoming_edges) AS num_incoming_edges, 
+      SUM(outgoing_edges) AS num_outgoing_edges 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (n) -[e]- (m) 
+  COLUMNS(n.number, n.name, 
+          CASE WHEN n IS DESTINATION OF e THEN 1 ELSE 0 END AS incoming_edges, 
+          CASE WHEN n IS SOURCE OF e THEN 1 ELSE 0 END AS outgoing_edges)) 
+GROUP BY number, name 
+ORDER BY num_incoming_edges + num_outgoing_edges DESC, number, name
+
+
+SELECT n.number, n.name,
+       SUM(CASE WHEN n IS DESTINATION OF e THEN 1 ELSE 0 END) AS num_incoming_edges,
+       SUM(CASE WHEN n IS SOURCE OF e THEN 1 ELSE 0 END) AS num_outgoing_edges
+FROM MATCH (n) -[e]- (m) ON financial_transactions
+GROUP BY number, name
+ORDER BY num_incoming_edges + num_outgoing_edges DESC, number, name
+
``` +------------------------------------------------------------+ @@ -3792,16 +4578,31 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr - , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr - , ELEMENT_NUMBER(v2) AS v2_elem_nr -FROM MATCH ANY (a1:Account) -[:transaction]->+ (a2:Account) - ON financial_transactions - ONE ROW PER STEP ( v1, e, v2 ) -WHERE a1.number = 1001 AND a2.number = 8021 -ORDER BY e_elem_nr -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (a1 IS Account) -[ IS transaction]->+ (a2 IS Account) 
+  KEEP ANY 
+  WHERE a1.number = 1001 AND a2.number = 8021 
+  ONE ROW PER STEP ( v1, e, v2 ) 
+  COLUMNS(v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr , 
+          ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr , 
+          ELEMENT_NUMBER(v2) AS v2_elem_nr)) 
+ORDER BY e_elem_nr
+
+
+SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr
+     , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr
+     , ELEMENT_NUMBER(v2) AS v2_elem_nr
+FROM MATCH ANY (a1:Account) -[:transaction]->+ (a2:Account)
+       ON financial_transactions
+       ONE ROW PER STEP ( v1, e, v2 )
+WHERE a1.number = 1001 AND a2.number = 8021
+ORDER BY e_elem_nr
+
``` +------------------------------------------------------------------------------+ @@ -3818,15 +4619,30 @@ For example: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr - , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr, ELEMENT_NUMBER(v2) AS v2_elem_nr -FROM MATCH ANY (a2:Account) <-[:transaction]-+ (a1:Account) - ON financial_transactions - ONE ROW PER STEP ( v1, e, v2 ) -WHERE a1.number = 1001 AND a2.number = 8021 -ORDER BY e_elem_nr -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(financial_transactions
+  MATCH (a2 IS Account) <-[ IS transaction]-+ (a1 IS Account)
+  KEEP ANY
+  WHERE a1.number = 1001 AND a2.number = 8021
+  ONE ROW PER STEP ( v1, e, v2 )
+  COLUMNS(v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr,
+          ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr,
+          ELEMENT_NUMBER(v2) AS v2_elem_nr))
+ORDER BY e_elem_nr
+
+
+SELECT v1.number AS v1_account_nr, e.amount, v2.number AS v2_account_nr
+     , ELEMENT_NUMBER(v1) AS v1_elem_nr, ELEMENT_NUMBER(e) AS e_elem_nr, ELEMENT_NUMBER(v2) AS v2_elem_nr
+FROM MATCH ANY (a2:Account) <-[:transaction]-+ (a1:Account)
+     ON financial_transactions
+     ONE ROW PER STEP ( v1, e, v2 )
+WHERE a1.number = 1001 AND a2.number = 8021
+ORDER BY e_elem_nr
+
``` +------------------------------------------------------------------------------+ @@ -3853,19 +4669,39 @@ ALL_DIFFERENT( val1, val2, val3, ..., valN ) For example: -```sql -SELECT * -FROM MATCH (n) -> (m) -> (o) ON my_graph -WHERE ALL_DIFFERENT( n, m, o ) -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) -> (m) -> (o) 
+  WHERE ALL_DIFFERENT( n, m, o ) 
+  COLUMNS(n.*, m.*, o.*))
+
+
+SELECT n.*, m.*, o.*
+FROM MATCH (n) -> (m) -> (o) ON my_graph
+WHERE ALL_DIFFERENT( n, m, o )
+
Note that the above query can be rewritten using non-equality constraints as follows: -```sql -SELECT * -FROM MATCH (n) -> (m) <- (o) -> (n) ON my_graph -WHERE n <> m AND n <> o AND m <> o -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) -> (m) <- (o) -> (n) 
+  WHERE NOT VERTEX_EQUAL(n, m) AND NOT VERTEX_EQUAL(n, o) AND NOT VERTEX_EQUAL(m, o) 
+  COLUMNS(n.*, m.*, o.*))
+
+
+SELECT *
+FROM MATCH (n) -> (m) <- (o) -> (n) ON my_graph
+WHERE n <> m AND n <> o AND m <> o
+
Another example is: @@ -4145,11 +4981,21 @@ Result: -30 User-defined functions (UDFs) are invoked similarly to built-in functions. For example, a user may have registered a function `math.tan` that returns the tangent of a given angle. An example invocation of this function is then: -```sql -SELECT math.tan(n.angle) AS tangent -FROM MATCH (n) ON my_graph -ORDER BY tangent -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n) 
+  COLUMNS(math.tan(n.angle) AS tangent))
+ORDER BY tangent
+
+
+SELECT math.tan(n.angle) AS tangent
+FROM MATCH (n) ON my_graph
+ORDER BY tangent
+
The syntax is: @@ -4196,10 +5042,19 @@ DataType ::= 'STRING' For example: -```sql -SELECT CAST(n.age AS STRING), CAST('123' AS INTEGER), CAST('09:15:00+01:00' AS TIME WITH TIME ZONE) -FROM MATCH (n:Person) ON my_graph -``` +
+ + +
+SELECT * 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (n IS Person) 
+  COLUMNS(CAST(n.age AS STRING), CAST('123' AS INTEGER), CAST('09:15:00+01:00' AS TIME WITH TIME ZONE)))
+
+
+SELECT CAST(n.age AS STRING), CAST('123' AS INTEGER), CAST('09:15:00+01:00' AS TIME WITH TIME ZONE)
+FROM MATCH (n:Person) ON my_graph
+
Casting is allowed between the following data types: @@ -4339,12 +5194,28 @@ Subquery ::= '(' ')' ``` An example is to find friend of friends, and, for each friend of friend, return the number of common friends: -```sql -SELECT fof.name, COUNT(friend) AS num_common_friends -FROM MATCH (p:Person) -[:has_friend]-> (friend:Person) -[:has_friend]-> (fof:Person) - ON my_graph -WHERE NOT EXISTS ( SELECT * FROM MATCH (p) -[:has_friend]-> (fof) ON my_graph ) -``` +
+ + +
+SELECT name, COUNT(friend) AS num_common_friends 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (p IS Person) -[ IS has_friend]-> (friend IS Person) -[ IS has_friend]-> (fof IS Person) 
+  COLUMNS(fof.name, VERTEX_ID(friend) AS friend, VERTEX_ID(p) AS p_id, VERTEX_ID(fof) AS fof_id)) 
+WHERE NOT EXISTS (SELECT * 
+                  FROM GRAPH_TABLE(my_graph 
+                    MATCH (p IS Person) -[ IS has_friend]-> (fof IS Person) 
+                    COLUMNS(VERTEX_ID(p) AS id1, VERTEX_id(fof) AS id2)) 
+                  WHERE id1 = p_id AND id2 = fof_id)
+GROUP BY name
+
+
+SELECT fof.name, COUNT(friend) AS num_common_friends
+FROM MATCH (p:Person) -[:has_friend]-> (friend:Person) -[:has_friend]-> (fof:Person)
+      ON my_graph
+WHERE NOT EXISTS ( SELECT * FROM MATCH (p) -[:has_friend]-> (fof) ON my_graph )
+GROUP BY fof.name
+
Here, vertices `p` and `fof` are passed from the outer query to the inner query. The `EXISTS` returns true if there is at least one `has_friend` edge between vertices `p` and `fof`. @@ -4360,36 +5231,80 @@ ScalarSubquery ::= For example: -```sql -SELECT a.name -FROM MATCH (a) ON my_graph -WHERE a.age > ( SELECT AVG(b.age) FROM MATCH (a) -[:friendOf]-> (b) ON my_graph ) -``` +
+ + +
+SELECT name 
+FROM GRAPH_TABLE(my_graph 
+  MATCH (a) 
+  COLUMNS(a.name, a.age, VERTEX_ID(a) AS a_id)) 
+WHERE age > ( SELECT AVG(b_age) 
+              FROM GRAPH_TABLE(my_graph 
+                MATCH (a) -[IS friendOf]-> (b) 
+                COLUMNS(VERTEX_ID(a) AS id, b.age AS b_age)) 
+              WHERE id = a_id)
+
+
+SELECT a.name
+FROM MATCH (a) ON my_graph
+WHERE a.age > ( SELECT AVG(b.age) FROM MATCH (a) -[:friendOf]-> (b) ON my_graph )
+
Another example is: {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT p.name AS name - , ( SELECT SUM(t.amount) - FROM MATCH (a) <-[t:transaction]- (:Account) ON financial_transactions - ) AS sum_incoming - , ( SELECT SUM(t.amount) - FROM MATCH (a) -[t:transaction]-> (:Account) ON financial_transactions - ) AS sum_outgoing - , ( SELECT COUNT(DISTINCT p2) - FROM MATCH (a) -[t:transaction]- (:Account) -[:owner]-> (p2:Person) - ON financial_transactions - WHERE p2 <> p - ) AS num_persons_transacted_with - , ( SELECT COUNT(DISTINCT c) - FROM MATCH (a) -[t:transaction]- (:Account) -[:owner]-> (c:Company) - ON financial_transactions - ) AS num_companies_transacted_with -FROM MATCH (p:Person) <-[:owner]- (a:Account) ON financial_transactions -ORDER BY sum_outgoing + sum_incoming DESC -``` +
+ + +
+SELECT name, 
+      ( SELECT SUM(amount) 
+        FROM GRAPH_TABLE(financial_transactions 
+          MATCH (a IS Account) <-[t IS transaction]- ( IS Account) 
+          COLUMNS(t.amount, VERTEX_ID(a) AS id)) 
+      WHERE id = a_id ) AS sum_incoming , 
+      ( SELECT SUM(amount) 
+        FROM GRAPH_TABLE(financial_transactions 
+          MATCH (a IS Account) -[t IS transaction]-> ( IS Account) 
+          COLUMNS(t.amount, VERTEX_ID(a) AS id)) 
+        WHERE id = a_id ) AS sum_outgoing , 
+      ( SELECT COUNT(DISTINCT p2) 
+        FROM GRAPH_TABLE(financial_transactions 
+          MATCH (a IS Account) -[t IS transaction]- ( IS Account) -[ IS owner]-> (p2 IS Person) 
+          COLUMNS(VERTEX_ID(p2) AS p2, VERTEX_id(a) AS id)) 
+        WHERE p2 <> p_id AND id = a_id ) AS num_persons_transacted_with , 
+      ( SELECT COUNT(DISTINCT c) 
+        FROM GRAPH_TABLE(financial_transactions 
+          MATCH (a IS Account) -[t IS transaction]- ( IS Account) -[ IS owner]-> (c IS Company) 
+          COLUMNS(VERTEX_ID(a) AS id, VERTEX_ID(c) AS c)) 
+        WHERE id = a_id ) AS num_companies_transacted_with 
+FROM GRAPH_TABLE(financial_transactions 
+  MATCH (p IS Person) <-[ IS owner]- (a IS Account) 
+  COLUMNS(p.name, VERTEX_ID(a) AS a_id, VERTEX_ID(p) AS p_id)) 
+ORDER BY sum_outgoing + sum_incoming DESC
+
+
+SELECT p.name AS name
+     , ( SELECT SUM(t.amount)
+         FROM MATCH (a) <-[t:transaction]- (:Account) ON financial_transactions
+       ) AS sum_incoming
+    , ( SELECT SUM(t.amount)
+        FROM MATCH (a) -[t:transaction]-> (:Account) ON financial_transactions
+      ) AS sum_outgoing
+    , ( SELECT COUNT(DISTINCT p2)
+        FROM MATCH (a) -[t:transaction]- (:Account) -[:owner]-> (p2:Person)
+          ON financial_transactions
+        WHERE p2 <> p
+      ) AS num_persons_transacted_with
+    , ( SELECT COUNT(DISTINCT c)
+        FROM MATCH (a) -[t:transaction]- (:Account) -[:owner]-> (c:Company)
+               ON financial_transactions
+      ) AS num_companies_transacted_with
+FROM MATCH (p:Person) <-[:owner]- (a:Account) ON financial_transactions
+ORDER BY sum_outgoing + sum_incoming DESC
+
``` +-----------------------------------------------------------------------------------------------------+ @@ -4401,7 +5316,8 @@ ORDER BY sum_outgoing + sum_incoming DESC +-----------------------------------------------------------------------------------------------------+ ``` -Note that in the query, the graph name `financial_transactions` is repeatedly specified. Such repetition can be avoided by using a [default graph](#default-graphs), which simplifies the query: +Note that in the query, the graph name `financial_transactions` is repeatedly specified. Such repetition can be avoided by using a [default graph](#default-graphs), which simplifies the query (Note: With `GRAPH_TABLE` operator, the graph name has to be specified explicitly inside each operator): + ```sql SELECT p.name AS name @@ -4438,21 +5354,40 @@ For example, the following query finds the top two people that transacted the mo {% include image.html file="example_graphs/financial_transactions.png" %} -```sql -SELECT p.name, total_transacted, top_transaction -FROM LATERAL ( SELECT p, SUM(t.amount) AS total_transacted - FROM MATCH (p:person) <- (a:account) -[t:transaction]- () - ON financial_transactions - GROUP BY p - ORDER BY total_transacted DESC - FETCH FIRST 2 ROW ONLY ), - LATERAL ( SELECT t.amount AS top_transaction - FROM MATCH (p) <- (a:account) -[t:transaction]- () - ON financial_transactions - ORDER BY t.amount DESC - FETCH FIRST 2 ROW ONLY ) -ORDER BY total_transacted DESC, top_transaction DESC -``` +
+ + +
+SELECT name, total_transacted, top_transaction
+FROM LATERAL ( SELECT p1_id, name, SUM(amount) AS total_transacted
+               FROM GRAPH_TABLE(financial_transactions MATCH (p1 IS person) <- (a1 IS account) -[t1 IS transaction]- ()
+               COLUMNS(VERTEX_ID(p1) AS p1_id, p1.name, t1.amount))
+               GROUP BY p1_id, name
+               ORDER BY total_transacted DESC
+               FETCH FIRST 2 ROW ONLY ),
+     LATERAL ( SELECT amount AS top_transaction
+               FROM GRAPH_TABLE(financial_transactions MATCH (p2 IS Person) <- (a2 IS account) -[t2 IS transaction]- ()
+                      COLUMNS(VERTEX_ID(p2) AS p2_id, t2.amount))
+               WHERE p2_id = p1_id
+               ORDER BY amount DESC
+               FETCH FIRST 2 ROW ONLY )
+ORDER BY total_transacted DESC, top_transaction DESC
+
+
+SELECT p.name, total_transacted, top_transaction
+FROM LATERAL ( SELECT p, SUM(t.amount) AS total_transacted
+               FROM MATCH (p:person) <- (a:account) -[t:transaction]- ()
+                      ON financial_transactions
+               GROUP BY p
+               ORDER BY total_transacted DESC
+               FETCH FIRST 2 ROW ONLY ),
+     LATERAL ( SELECT t.amount AS top_transaction
+               FROM MATCH (p) <- (a:account) -[t:transaction]- ()
+                      ON financial_transactions
+               ORDER BY t.amount DESC
+               FETCH FIRST 2 ROW ONLY )
+ORDER BY total_transacted DESC, top_transaction DESC
+
``` +----------------------------------------------+ @@ -4466,6 +5401,7 @@ ORDER BY total_transacted DESC, top_transaction DESC ``` In the following query, the `LATERAL` subquery projects the two vertices `a` and `p`, while the outer accesses properties of those vertices. +Note: Vertex and edge variables cannot be projected with `GRAPH_TABLE` operator ```sql SELECT p.name, a.number diff --git a/pre-pages/pgql-2.0-spec.md b/pre-pages/pgql-2.0-spec.md new file mode 100644 index 00000000..d38ebcf5 --- /dev/null +++ b/pre-pages/pgql-2.0-spec.md @@ -0,0 +1,5866 @@ +--- +title: "PGQL 2.0 Specification" +date: "19 May 2023" +#permalink: /spec/2.0/ +summary: "PGQL is an SQL-based query language for the property graph data model that allows +you to specify high-level graph patterns which are matched against vertices and edges in a graph. +PGQL has support for grouping (GROUP BY), aggregation (e.g. MIN, MAX, AVG, SUM), sorting (ORDER BY) and many other familiar SQL constructs. +Furthermore, PGQL has powerful regular expression constructs for graph reachability (transitive closure), shortest path finding and +cheapest path finding." +sidebar: spec_2_0_sidebar +toc: false +--- + +# Introduction + +PGQL is a graph pattern-matching query language for the [property graph data model](#property-graph-data-model). This document specifies the syntax and semantics of the language. + +## Changelog + +The following are the changes since PGQL 1.5: + +### New features in PGQL 2.0 + +The new (and fully SQL-compatible) features are: + + - [GRAPH_TABLE Operator](#graph_table-operator) + - [LATERAL Subquery](#lateral-subqueries) + - [Path Modes](#path-modes) (`WALK`, `ACYCLIC`, `SIMPLE`, `TRAIL`) + - [KEEP Clause](#KeepClause) + - [LABELED Predicate](#labeled-predicate), [SOURCE/DESTINATION Predicate](#source--destination-predicate), [MATCHNUM Function](#matchnum-function) and [VERTEX_ID/EDGE_ID Function](#vertex_idedge_id-function) + - [FETCH FIRST Clause](#fetch-first-clause) + +## A note on the Grammar + +This document contains a complete grammar definition of PGQL, spread throughout the different sections. There is a single entry point into the grammar: . + +## Document Outline + + - [Introduction](#introduction) contains a changelog, a note on the grammar, this outline and an introduction to the property graph data model. + - [Creating a Property Graph](#creating-a-property-graph) describes how to create a property graph from an existing set of tables in a relational database. + - [Graph Pattern Matching](#graph-pattern-matching) introduces the basic concepts of graph querying. + - [Variable-Length Paths](#variable-length-paths) introduces the constructs for matching variable-length paths such as shortest paths or cheapests paths. + - [Number of Rows Per Match](#number-of-rows-per-match) describes how to specify the number of rows per match, for example to obtain one row per vertex in a shortest path. + - [Grouping and Aggregation](#grouping-and-aggregation) describes the mechanism to group and aggregate results. + - [Sorting and Row Limiting](#sorting-and-row-limiting) describes the ability to sort and paginate results. + - [Functions and Expressions](#functions-and-expressions) describes the supported data types and corresponding functions and operations. + - [Subqueries](#subqueries) describes the syntax and semantics of subqueries for creating more complex queries that nest other queries. + - [Graph Modification](#graph-modification) describes `INSERT`, `UPDATE` and `DELETE` statements for inserting, updating and deleting vertices and edges in a graph. + - [Other Syntactic rules](#other-syntactic-rules) describes additional syntactic rules that are not covered by the other sections, such as syntax for identifiers and comments. + +## Property graph data model + +A property graph has a name, which is a (character) string, and contains: + + - A set of vertices (or nodes). + + - Each vertex has zero or more labels. + - Each vertex has zero or more properties (or attributes), which are arbitrary key-value pairs. + + - A set of edges (or relationships). + + - Each edge is directed. + - Each edge has zero or more labels. + - Each edge has zero or more properties (or attributes), which are arbitrary key-value pairs. + +Labels as well as names of properties are strings. Property values are scalars such as numerics, strings or booleans. + +### Example 1: Student Network + +An example graph is: + +{% include image.html file="example_graphs/student_network.png" %} + +Here, `student_network` is the name of the graph. The graph has three vertices labeled `Person` and one vertex labeled `University`. There are six directed edges that connect the vertices. Three of them go from person to person vertices, and they have the label `knows`. Three others go from person to university vertices and are labeled `studentOf`. The person vertices have two properties, namely `name` for encoding the name of the person and `dob` for encoding the date of birth of the person. The university vertex has only a single property `name` for encoding the name of the university. The edges have no properties. + +### Example 2: Financial Transactions + +An example graph with financial transactions is: + +{% include image.html file="example_graphs/financial_transactions.png" %} + +Here, `financial_transactions` is the name of the graph. The graph has three types of vertices. Vertices labeled `Person` or `Company` have a property `name`, while vertices labeled `Account` have a property `number`. There are edges labeled `owner` from accounts to persons as well as from accounts to companies, and there are edges labeled `transaction` from accounts to accounts. Note that only `transaction` edges have a property (`amount`) while other edges do not have any properties. + + +# Creating a Property Graph + +The [CREATE PROPERTY GRAPH](#create-property-graph) statement allows for creating a property graph from a set of existing database tables, +while the [DROP PROPERTY GRAPH](#drop-property-graph) statements allows for dropping a graph. + +## CREATE PROPERTY GRAPH + +The `CREATE PROPERTY GRAPH` statement starts with a graph name and is followed by a non-empty set of vertex tables and an optional set of edge tables. + +The syntax is: + +```bash +CreatePropertyGraph ::= 'CREATE' 'PROPERTY' 'GRAPH' + + ? + +GraphName ::= + +SchemaQualifiedName ::= ? + +SchemaIdentifierPart ::= '.' + +VertexTables ::= 'VERTEX' 'TABLES' '(' ( ',' )* ')' + +EdgeTables ::= 'EDGE' 'TABLES' '(' ( ',' )* ')' +``` + +It is possible to have no edge tables such that the resulting graph only has vertices that are all disconnected from each other. +However, it is not possible to have a graph with edge tables but no vertex tables. + +The following example shows a schema with a set of tables. Each table has a name and a list of columns, some of which form the primary key for the table (in red) while others form foreign keys that reference rows of other tables. + +{% include image.html file="example_graphs/financial_transactions_schema.png" %} + +The following is a complete example of how a graph can be created from these tables: + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + Persons LABEL Person PROPERTIES ( name ), + Companies LABEL Company PROPERTIES ( name ), + Accounts LABEL Account PROPERTIES ( number ) + ) + EDGE TABLES ( + Transactions + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Persons + LABEL owner NO PROPERTIES, + Accounts AS CompanyOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Companies + LABEL owner NO PROPERTIES, + Persons AS worksFor + SOURCE KEY ( id ) REFERENCES Persons ( id ) + DESTINATION Companies + NO PROPERTIES + ) +``` + +Above, `financial_transactions` is the name of the graph. +The graph has three vertex tables: `Persons`, `Companies` and `Accounts`. +The graph also has four edge tables: `Transactions`, `PersonOwner`, `CompanyOwner` and `worksFor`. + +Underlying foreign keys are used to establish the connections between the two endpoints of the edges and the corresponding vertices. +Note that the "source" of an edge is the vertex where the edge points _from_ while the "destination" of an edge is the vertex where the edge point _to_. + +If foreign keys cannot be used or are not present, the necessary keys can be defined as part of the `CREATE PROPERTY GRAPH` statement. +Labels and properties can also be defined, all of which is explained in more detail in the next sections. + +### Vertex tables + +A vertex table provides a vertex for each row of the underlying table. + +The syntax is: + +```bash +VertexTable ::= ? ? ? + +LabelAndPropertiesClause ::= ? ? + +TableName ::= +``` + +The [table alias](#table-aliases) is required only if the underlying table is used as vertex table more than once, to provide a unique name for each table. +It can be used for specifying a [label](#labels) for the vertices too. + +The key of the vertex table uniquely identifies a row in the table. +If a key is not explicitly specified then it defaults to the primary key of the underlying table. +A key is always required so a primary key needs to exist if no key is specified. +See the section on [keys](#keys) for more details. + +The label clause provides a label for the vertices. +If a label is not defined, the label defaults to the alias. +Since the alias defaults to the name of the underlying table, if no alias is provided, the label defaults to the name of the underlying table. +See the section on [labels](#labels) for details. + +The properties clause defines the mapping from columns of the underlying table into properties of the vertices. +See the section on [properties](#properties) for more details. + +### Edge tables + +An edge table provides an edge for each row of the underlying table. + +```bash +EdgeTable ::= ? ? + + ? + +SourceVertexTable ::= 'SOURCE' ? + +DestinationVertexTable ::= 'DESTINATION' ? + +ReferencedVertexTableKeyClause ::= 'REFERENCES' +``` + +The [table alias](#table-aliases) is required only if the underlying table is used as edge table more than once, to provide a unique name for each table. +It can be used for specifying a [label](#labels) for the edges too. + +The source vertex table and destination vertex table are mandatory for defining the two endpoints of the edge. +A key is optional if there is a single foreign key from the edge table to the source or destination vertex table. +If a key is not provided, it will default to the existing foreign key. + +Take the following example from before: + +{% include image.html file="example_graphs/financial_transactions_schema.png" %} + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + Persons LABEL Person PROPERTIES ( name ), + Companies LABEL Company PROPERTIES ( name ), + Accounts LABEL Account PROPERTIES ( number ) + ) + EDGE TABLES ( + Transactions + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Persons + LABEL owner NO PROPERTIES, + Accounts AS CompanyOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Companies + LABEL owner NO PROPERTIES, + Persons AS worksFor + SOURCE KEY ( id ) REFERENCES Persons ( id ) + DESTINATION Companies + NO PROPERTIES + ) +``` + +The key of the edge table uniquely identifies a row in the table. +If a key is not explicitly specified (in case of all four edge tables above) then it defaults to the primary key of the underlying table. +A key is always required so a primary key needs to exist if no key is specified. +See the section on [keys](#keys) for more details. + +In case of edge tables `PersonOwner`, `CompanyOwner` and `worksFor`, the destination vertex table is the same table as the edge table itself. +This means that rows in the table are mapped into both vertices and edges. It is also possible that the source vertex table is the edge table itself or that both the source and destination tables are the edge table itself. +This is explained in more detail in [Source or destination is self](#source-or-destination-is-self). + +Keys for the destinations of `PersonOwner`, `CompanyOwner` and `worksFor` are omitted because we can default to the existing foreign keys. +Keys for their sources cannot be omitted because there exist no foreign key to default to (e.g. in case of `PersonOwner` there are zero foreign keys from `Accounts` to `Accounts` hence `SOURCE KEY ( number ) REFERENCES Accounts ( number )` needs to be specified). +Furthermore, keys for the source and destination of `Transactions` cannot be omitted because _two_ foreign keys exist between `Transactions` and `Accounts` so it is necessary to specify which one to use. + +If a row in an edge table has a NULL value for any of its source key columns or its destination key columns then no edge is created. +Note that in case of the `Accounts` table from the example, it is assumed that either the `person_id` or the `company_id` is NULL, so that each time the row is mapped into either a "company owner" or a "person owner" edge but never into two types of edges at once. + +The label clause provides a label for the edges. +If a label is not defined, the label defaults to the alias. +Since the alias defaults to the name of the underlying table, if no alias is provided, the label defaults to the name of the underlying table. +See the section on [labels](#labels) for details. + +The properties clause defines the mapping from columns of the underlying table to properties of the edges. +See the section on [properties](#properties) for more details + +### Table aliases + +Vertex and edge tables can have aliases for uniquely naming the tables. +If no alias is defined, then the alias defaults to the name of the underlying database table of the vertex or edge table. + +The syntax is: + +```bash +TableAlias ::= ( 'AS' )? +``` + +For example: + +```sql +... + EDGE TABLES ( Persons AS worksFor ... ) +... +``` + +Above, the underlying table of the edge table is `Persons`, while the alias is `worksFor`. + +All vertex and edge tables are required to have unique names. +Therefore, if multiple vertex tables use the same underlying table, then at least one of them requires an alias. +Similarly, if multiple edge tables use the same underlying table, then at least one of them requires an alias. +The restriction does not apply across vertex and edge tables, so, there may exist a vertex table with the same name as an edge table, +but there may not exist two vertex tables with the same name, or two edge tables with the same name. + +If the alias is not provided then it defaults to the name of the underlying table. +For example: + +```sql +... + VERTEX TABLES ( Person ) +... +``` + +Above is equivalent to: + +```sql +... + VERTEX TABLES ( Person AS Person ) +... +``` + +Finally, in addition to providing unique names for vertex and edge tables, the aliases can also serve as a means to provide [labels](#labels) for vertices and edges: +if no label is defined then the label defaults to the table alias. +Note that although table aliases are required to be unique, labels are not. +In other words, multiple vertex tables and multiple edge tables can have the same label. + +### Keys + +By default, existing primary and foreign keys of underlying tables are used to connect the end points of the edges to the appropriate vertices, but the following scenarios +require manual specification of keys: + + - Multiple foreign keys exists between an edge table and its source vertex table or its destination vertex tables such that it would be ambiguous which foreign key to use. + - Primary and/or foreign keys on underlying tables were not defined or the underlying tables are views which means that primary and foreign keys cannot be defined. + +The syntax for keys is: + +```bash +KeyClause ::= '(' ( ',' )* ')' + +ColumnName ::= +``` + +Take the example from before: + +{% include image.html file="example_graphs/financial_transactions_schema.png" %} + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + ... + ) + EDGE TABLES ( + Transactions + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Persons + LABEL owner NO PROPERTIES, + ... + ) +``` + +Above, a key is defined for the source and destination of `Transactions` because two foreign keys exist between `Transactions` and `Accounts` so it would be ambiguous which one to use without explicit specification. +In case of `PersonOwner`, no foreign key exists between `Accounts` and `Accounts` so a key for the source (`KEY ( number )`) has to be explicitly specified. However, for the destination it is possible to omit the key and default to the existing foreign key between `Accounts` and `Persons`. + +The keys for source and destination vertex tables consist of one or more columns of the underlying edge table that uniquely identify a vertex in the corresponding vertex table. If no key is defined for the vertex table, the key defaults to the underlying primary key, which is required to exist in such a case. + +The following example has a schema that has no primary and foreign keys defined at all: + +{% include image.html file="example_graphs/financial_transactions_schema_no_keys.png" %} + +Note that above, we have the same schema as before, but this time the primary and foreign keys are missing. + +Even though primary and foreign keys are missing, the graph can still be created by specifying the necessary keys in the `CREATE PROPERTY GRAPH` statement itself: + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + Persons + KEY ( id ) + LABEL Person + PROPERTIES ( name ), + Companies + KEY ( id ) + LABEL Company + PROPERTIES ( name ), + Accounts + KEY ( number ) + LABEL Account + PROPERTIES ( number ) + ) + EDGE TABLES ( + Transactions + KEY ( from_account, to_account, date ) + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + KEY ( number ) + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION KEY ( person_id ) REFERENCES Persons ( id ) + LABEL owner NO PROPERTIES, + Accounts AS CompanyOwner + KEY ( number ) + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION KEY ( company_id ) REFERENCES Companies ( id ) + LABEL owner NO PROPERTIES, + Persons AS worksFor + KEY ( id ) + SOURCE KEY ( id ) REFERENCES Persons ( id ) + DESTINATION KEY ( company_id ) REFERENCES Companies ( id ) + NO PROPERTIES + ) +``` + +Above, keys were defined for each vertex table (e.g. `KEY ( id )`), edge table (e.g. `KEY ( from_account, to_account, date )`), source vertex table reference (e.g. `KEY ( from_account )`) and destination table reference (e.g. `KEY ( to_account )`). + +Each vertex and edge table is required to have a key so that if a key is not explicitly specified then the underlying table needs to have a primary key defined. + +### Labels + +In graphs created through `CREATE PROPERTY GRAPH`, each vertex has exactly one label and each edge has exactly one label. +This restriction may be lifted in future PGQL version. + +The syntax for labels is: + +```bash +LabelClause ::= 'LABEL'