diff --git a/build.gradle b/build.gradle index f16d16c09c..ec331025c6 100644 --- a/build.gradle +++ b/build.gradle @@ -131,7 +131,7 @@ subprojects { ext { // NB: due to version.json generation by parsing this file, the next line must not have any if/then/else logic - neo4jVersion = "5.19.0" + neo4jVersion = "5.18.0" // instead we apply the override logic here neo4jVersionEffective = project.hasProperty("neo4jVersionOverride") ? project.getProperty("neo4jVersionOverride") : neo4jVersion testContainersVersion = '1.18.3' diff --git a/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/apoc.agg.multiStats.adoc b/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/apoc.agg.multiStats.adoc new file mode 100644 index 0000000000..929484fd20 --- /dev/null +++ b/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/apoc.agg.multiStats.adoc @@ -0,0 +1,128 @@ + += apoc.agg.multiStats +:description: This section contains reference documentation for the apoc.agg.multiStats function. + +label:function[] label:apoc-extended[] + +[.emphasis] +apoc.agg.multiStats(nodeOrRel, keys) - Return a multi-dimensional aggregation + +== Signature + +[source] +---- +apoc.agg.multiStats(value :: NODE | RELATIONSHIP, keys :: LIST OF STRING) :: (MAP?) +---- + +== Input parameters +[.procedures, opts=header] +|=== +| Name | Type | Default +|value|NODE \| RELATIONSHIP|null +|=== + + +[[usage-apoc.data.email]] +== Usage Examples + +Given this dataset: +[source,cypher] +---- +CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "aaa", another: 548}), + (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), + (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), + (:Person { louvain: 597, neo4jImportId: "18349391", wcc: 48, lpa: 598, name: "eee", another: 549}), + (:Person { louvain: 597, neo4jImportId: "18349392", wcc: 47, lpa: 596, name: "iii", another: 549}), + (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 549}), + (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 549}), + (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 10}), + (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 10}) +---- + + +We can create an optimized multiple aggregation based on the property key, +similar to this one: +[source,cypher] +---- +MATCH (p:Person) +WITH p +CALL { + WITH p + MATCH (n:Person {louvain: p.louvain}) + RETURN sum(p.louvain) AS sumLouvain, avg(p.louvain) AS avgLouvain, count(p.louvain) AS countLouvain +} +CALL { + WITH p + MATCH (n:Person {wcc: p.wcc}) + RETURN sum(p.wcc) AS sumWcc, avg(p.wcc) AS avgWcc, count(p.wcc) AS countWcc +} +CALL { + WITH p + MATCH (n:Person {another: p.another}) + RETURN sum(p.another) AS sumAnother, avg(p.another) AS avgAnother, count(p.another) AS countAnother +} +CALL { + WITH p + MATCH (lpa:Person {lpa: p.lpa}) + RETURN sum(p.lpa) AS sumLpa, avg(p.lpa) AS avgLpa, count(p.lpa) AS countLpa +} +RETURN p.name, + sumLouvain, avgLouvain, countLouvain, + sumWcc, avgWcc, countWcc, + sumAnother, avgAnother, countAnother, + sumLpa, avgLpa, countLpa +---- + + +executing the following query: +[source,cypher] +---- +MATCH (p:Person) +RETURN apoc.agg.multiStats(p, ["lpa","wcc","louvain", "another"]) as output +---- + + +.Results +[opts="header"] +|=== +| output +a| +[source,json] +---- +{ + "louvain" :{"596" :{"avg" :596.0, "count" :3, "sum" :1788}, "597" :{"avg" :597.0, "count" :6, "sum" :3582}}, + "wcc" :{"47" :{"avg" :47.0, "count" :5, "sum" :235}, "48" :{"avg" :48.0, "count" :4, "sum" :192}}, + "another" :{"548" :{"avg" :548.0, "count" :1, "sum" :548}, "549" :{"avg" :549.0, "count" :6, "sum" :3294}, "10" :{"avg" :10.0, "count" :2, "sum" :20}}, + "lpa" :{"596" :{"avg" :596.0, "count" :5, "sum" :2980}, "598" :{"avg" :598.0, "count" :4, "sum" :2392}} +} +---- +|=== + +which can be used, for example, to return a result similar to the Cypher one in this way: + +[source,cypher] +---- +MATCH (p:Person) +WITH apoc.agg.multiStats(p, ["lpa","wcc","louvain", "another"]) as data +MATCH (p:Person) +RETURN p.name, + data.wcc[toString(p.wcc)].avg AS avgWcc, + data.louvain[toString(p.louvain)].avg AS avgLouvain, + data.lpa[toString(p.lpa)].avg AS avgLpa +---- + + +.Results +[opts="header"] +|=== +| avgWcc | avgLouvain | avgLpa +| 48.0 | 596.0 | 598.0 +| 48.0 | 596.0 | 598.0 +| 48.0 | 596.0 | 598.0 +| 47.0 | 597.0 | 596.0 +| 47.0 | 597.0 | 596.0 +| 47.0 | 597.0 | 596.0 +| 47.0 | 597.0 | 596.0 +| 47.0 | 597.0 | 596.0 +|=== + diff --git a/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/index.adoc b/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/index.adoc index 9cec8f26cb..26d934a40e 100644 --- a/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/index.adoc +++ b/docs/asciidoc/modules/ROOT/pages/overview/apoc.agg/index.adoc @@ -14,5 +14,11 @@ Returns index of the `element` that match the given `value` Returns index of the `element` that match the given `predicate` |label:procedure[] + + +|xref::overview/apoc.agg/apoc.agg.multiStats.adoc[apoc.agg.multiStats icon:book[]] + +apoc.agg.multiStats(nodeOrRel, keys) - Return a multi-dimensional aggregation +|label:function[] |=== diff --git a/docs/asciidoc/modules/ROOT/partials/generated-documentation/documentation.adoc b/docs/asciidoc/modules/ROOT/partials/generated-documentation/documentation.adoc index dbacdf391e..d25311d2be 100644 --- a/docs/asciidoc/modules/ROOT/partials/generated-documentation/documentation.adoc +++ b/docs/asciidoc/modules/ROOT/partials/generated-documentation/documentation.adoc @@ -19,6 +19,19 @@ Returns index of the `element` that match the given `predicate` |=== +[discrete] +== xref::overview/apoc.agg/index.adoc[] + +[.procedures, opts=header, cols='5a,1a'] +|=== +| Qualified Name | Type +|xref::overview/apoc.agg/apoc.agg.multiStats.adoc[apoc.agg.multiStats icon:book[]] + +apoc.agg.multiStats(nodeOrRel, keys) - Return a multi-dimensional aggregation +|label:procedure[] +|=== + + [discrete] == xref::overview/apoc.bolt/index.adoc[] diff --git a/docs/asciidoc/modules/ROOT/partials/generated-documentation/nav.adoc b/docs/asciidoc/modules/ROOT/partials/generated-documentation/nav.adoc index 02f07fec92..ae165734cb 100644 --- a/docs/asciidoc/modules/ROOT/partials/generated-documentation/nav.adoc +++ b/docs/asciidoc/modules/ROOT/partials/generated-documentation/nav.adoc @@ -5,6 +5,7 @@ This file is generated by DocsTest, so don't change it! ** xref::overview/apoc.agg/index.adoc[] *** xref::overview/apoc.agg/apoc.agg.row.adoc[] *** xref::overview/apoc.agg/apoc.agg.position.adoc[] +*** xref::overview/apoc.agg/apoc.agg.multiStats.adoc[] ** xref::overview/apoc.bolt/index.adoc[] *** xref::overview/apoc.bolt/apoc.bolt.execute.adoc[] *** xref::overview/apoc.bolt/apoc.bolt.load.adoc[] diff --git a/extended/src/main/java/apoc/agg/MultiStats.java b/extended/src/main/java/apoc/agg/MultiStats.java index 3c4a19e85b..097c041fd0 100644 --- a/extended/src/main/java/apoc/agg/MultiStats.java +++ b/extended/src/main/java/apoc/agg/MultiStats.java @@ -19,56 +19,21 @@ @Extended public class MultiStats { - /* - For a property you want to have more than one statistic: - -size, count, avg, median, ... - -which is probably already covered by apoc.agg.stats() - -but then, how about multi-dimensional aggregation. - -e.g. - -apoc.agg.multiStats([key1,key2,key3]) -> Map> - -e.g. - -match (p:Person) -with apoc.agg.multiStats(p, ["wcc","lpa","louvain"]) as data -match (p:Person) -return p.name, data[toString(p.wcc)].count as size - -see -https://community.neo4j.com/t/listing-the-community-size-of-different-community-detection-algorithms-already-calculated/42895/2?u=michael.hunger - */ @UserAggregationFunction("apoc.agg.multiStats") - @Description("TODO...") + @Description("Return a multi-dimensional aggregation") public MultiStatsFunction multiStats() { return new MultiStatsFunction(); } - - // todo --> sum,count,avg public static class MultiStatsFunction { -// private Histogram values = new Histogram(3); -// private DoubleHistogram doubles; -// private List percentiles = asList(0.5D, 0.75D, 0.9D, 0.95D, 0.9D, 0.99D); -// private Number minValue; private final Map>> result = new HashMap<>(); - - // --> TODO - sum must be similar to https://neo4j.com/docs/cypher-manual/current/functions/aggregating/#functions-sum @UserAggregationUpdate public void aggregate( @Name("value") Object value, - @Name(value = "keys") List keys, - // todo... - @Name(value = "statistics", defaultValue = "['sum','count','avg']") List statistics - ) { - // todo - can be also a map, maybe? + @Name(value = "keys") List keys) { Entity entity = (Entity) value; // for each prop @@ -77,29 +42,29 @@ public void aggregate( Object property = entity.getProperty(key); result.compute(key, (ignored, v) -> { - Map> map1 = Objects.requireNonNullElseGet(v, HashMap::new); + Map> map = Objects.requireNonNullElseGet(v, HashMap::new); - // todo - it can be null? - map1.compute(property.toString(), (propKey, propVal) -> { + map.compute(property.toString(), (propKey, propVal) -> { - Map map = Objects.requireNonNullElseGet(propVal, HashMap::new); + Map propMap = Objects.requireNonNullElseGet(propVal, HashMap::new); - NumberValue count = map.compute("count", ((subKey, subVal) -> (NumberValue) ValueUtils.of(subVal == null ? 1 : subVal.longValue() + 1)) ); + NumberValue count = propMap.compute("count", + ((subKey, subVal) -> (NumberValue) ValueUtils.of(subVal == null ? 1 : subVal.longValue() + 1)) ); - AnyValue of1 = ValueUtils.of(property); + AnyValue neo4jValue = ValueUtils.of(property); - if (of1 instanceof NumberValue of) { - NumberValue sum = map.compute("sum", ((subKey, subVal) -> subVal == null ? of : ValueMath.overflowSafeAdd(subVal, of))); - - // NB: avg() return always a double - NumberValue avg = map.compute("avg", ((subKey, subVal) -> subVal == null ? of : sum.dividedBy(count.doubleValue()) )); + if (neo4jValue instanceof NumberValue numberValue) { + NumberValue sum = propMap.compute("sum", + ((subKey, subVal) -> subVal == null ? numberValue : ValueMath.overflowSafeAdd(subVal, numberValue))); + + propMap.compute("avg", + ((subKey, subVal) -> subVal == null ? ValueUtils.asDoubleValue(numberValue.doubleValue()) : sum.dividedBy(count.doubleValue()) )); } - return map; + return propMap; }); - - return map1; + return map; }); } }); diff --git a/extended/src/test/java/apoc/agg/MultiStatsTest.java b/extended/src/test/java/apoc/agg/MultiStatsTest.java index c537cb627f..c9e167a750 100644 --- a/extended/src/test/java/apoc/agg/MultiStatsTest.java +++ b/extended/src/test/java/apoc/agg/MultiStatsTest.java @@ -2,22 +2,17 @@ import apoc.map.Maps; import apoc.util.TestUtil; -import apoc.util.Util; import apoc.util.collection.Iterators; -import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; -import org.neo4j.graphdb.Result; -import org.neo4j.graphdb.Transaction; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; +import java.util.List; import java.util.Map; -import static apoc.util.TestUtil.firstColumn; -import static apoc.util.TestUtil.testCall; import static org.junit.jupiter.api.Assertions.assertEquals; public class MultiStatsTest { @@ -28,46 +23,7 @@ public class MultiStatsTest { @BeforeClass public static void setUp() { TestUtil.registerProcedure(db, Maps.class, MultiStats.class); - } - - @AfterClass - public static void tearDown() { - db.shutdown(); - } - - @After - public void after() { - db.executeTransactionally("MATCH (n) DETACH DELETE n"); - } - - @Test - public void test2() { - db.executeTransactionally("CREATE (:Test {a: 1, b: 2.0, c: 3.0}), " + - "(:Test {a: 2, b: 2.0, c: 3.5}), " + - "(:Test {a: 3, b: 3, c: 4.5})"); - - Map stringObjectMap1 = db.executeTransactionally( - "MATCH (n:Test) WITH n.a as prop return sum(prop) AS sum, avg(prop) AS avg, count(prop) AS count", Map.of(), - Iterators::single); - - Map stringObjectMap2 = db.executeTransactionally( - "MATCH (n:Test) WITH n.b as prop return sum(prop) AS sum, avg(prop) AS avg, count(prop) AS count", Map.of(), - Iterators::single); - - Map stringObjectMap3 = db.executeTransactionally( - "MATCH (n:Test) WITH n.c as prop return sum(prop) AS sum, avg(prop) AS avg, count(prop) AS count", Map.of(), - Iterators::single); - - testCall(db, "MATCH (n:Test) RETURN apoc.agg.multiStats(n, ['a', 'b', 'c']) AS data", r -> { - Map data = (Map) r.get("data"); - assertEquals(stringObjectMap1, data.get("a")); - assertEquals(stringObjectMap2, data.get("b")); - assertEquals(stringObjectMap3, data.get("c")); - }); - } - @Test - public void test123123123() { db.executeTransactionally(""" CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "aaa", another: 548}), (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), @@ -77,114 +33,18 @@ public void test123123123() { (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 549}), (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 549}), (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 10}), - (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 10}) - """); - - - db.executeTransactionally(""" - CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "aaa", another: 548}), - (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), - (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349391", wcc: 48, lpa: 598, name: "eee", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349392", wcc: 47, lpa: 596, name: "iii", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 10}), - (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 10}) - """); -// db.executeTransactionally(""" -// CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549});"""); -// db.executeTransactionally(""" -// CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 47, lpa: 596, name: "iii", another: 549})"""); - - - - String s = db.executeTransactionally(""" - MATCH (p:Person) - WITH p - CALL { - WITH p - MATCH (n:Person {louvain: p.louvain}) - RETURN sum(p.louvain) AS sumLouvain, avg(p.louvain) AS avgLouvain, count(p.louvain) AS countLouvain - } - CALL { - WITH p - MATCH (n:Person {wcc: p.wcc}) - RETURN sum(p.wcc) AS sumWcc, avg(p.wcc) AS avgWcc, count(p.wcc) AS countWcc - } - CALL { - WITH p - MATCH (n:Person {another: p.another}) - RETURN sum(p.another) AS sumAnother, avg(p.another) AS avgAnother, count(p.another) AS countAnother - } - CALL { - WITH p - MATCH (lpa:Person {lpa: p.lpa}) - RETURN sum(p.lpa) AS sumLpa, avg(p.lpa) AS avgLpa, count(p.lpa) AS countLpa - } - RETURN p.name, sumLouvain, avgLouvain, countLouvain, sumWcc, avgWcc, countWcc, sumAnother, avgAnother, countAnother, sumLpa, avgLpa, countLpa - """, Map.of(), - Result::resultAsString); - System.out.println("s = " + s); - - - String s1 = db.executeTransactionally(""" - MATCH (p:Person) - WITH p.lpa as lpa, count(*) as sizeLpa - WITH apoc.map.fromPairs(collect([toString(lpa), sizeLpa])) as map_lpa - MATCH (p:Person) - WITH map_lpa, p.wcc as wcc, count(*) as sizeWcc - WITH map_lpa, apoc.map.fromPairs(collect([toString(wcc), sizeWcc])) as map_wcc - MATCH (p:Person) - WITH map_lpa, map_wcc, p.louvain as louvain, count(*) as sizeLouvain - WITH map_lpa, map_wcc, apoc.map.fromPairs(collect([toString(louvain), sizeLouvain])) as map_louvain - MATCH (p:Person) - RETURN p.name, map_lpa[toString(p.lpa)] as lpaSize, map_wcc[toString(p.wcc)] as wccSize, map_louvain[toString(p.louvain)] as louvainSize""", - Map.of(), Result::resultAsString); - System.out.println("s1 = " + s1); - + (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 10})"""); + } -// String s2 = db.executeTransactionally(""" -// match (p:Person) -// WITH apoc.agg.multiStats(p, ["lpa","wcc","louvain"], ["count"]) as data -// MATCH (p:Person) -// RETURN p.name, data.lpa.count as lpaSize, data.wcc.count as wccSize, data.louvain.count as louvainSize -// """, Map.of(), Result::resultAsString); -// System.out.println("s2 = " + s2); - String s2 = db.executeTransactionally(""" - match (p:Person) - with apoc.agg.multiStats(p, ["lpa","wcc","louvain"], ["count"]) as data - match (p:Person) - return p.name, data.wcc[toString(p.wcc)], data.louvain[toString(p.louvain)], data.lpa[toString(p.lpa)] - //MATCH (p:Person) - //RETURN p.name, data.lpa.count as lpaSize, data.wcc.count as wccSize, data.louvain.count as louvainSize - """, Map.of(), Result::resultAsString); - System.out.println("s2 = " + s2); - + @AfterClass + public static void tearDown() { + db.shutdown(); } // similar to https://community.neo4j.com/t/listing-the-community-size-of-different-community-detection-algorithms-already-calculated/42895 @Test - public void test() { - db.executeTransactionally(""" - CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "aaa", another: 548}), - (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), - (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349391", wcc: 48, lpa: 598, name: "eee", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349392", wcc: 47, lpa: 596, name: "iii", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 549}), - (:Person { louvain: 597, neo4jImportId: "18349393", wcc: 47, lpa: 596, name: "iii", another: 10}), - (:Person { louvain: 597, neo4jImportId: "18349394", wcc: 47, lpa: 596, name: "iii", another: 10}) - """); -// db.executeTransactionally(""" -// CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 48, lpa: 598, name: "eee", another: 549});"""); -// db.executeTransactionally(""" -// CREATE (:Person { louvain: 596, neo4jImportId: "18349390", wcc: 47, lpa: 596, name: "iii", another: 549})"""); - - - - String s = db.executeTransactionally(""" + public void testMultiStatsComparedWithCypherMultiAggregation() { + List multiAggregationResult = db.executeTransactionally(""" MATCH (p:Person) WITH p CALL { @@ -207,132 +67,33 @@ RETURN sum(p.another) AS sumAnother, avg(p.another) AS avgAnother, count(p.anoth MATCH (lpa:Person {lpa: p.lpa}) RETURN sum(p.lpa) AS sumLpa, avg(p.lpa) AS avgLpa, count(p.lpa) AS countLpa } - RETURN p.name, sumLouvain, avgLouvain, countLouvain, sumWcc, avgWcc, countWcc, sumAnother, avgAnother, countAnother, sumLpa, avgLpa, countLpa - """, Map.of(), - Result::resultAsString); - System.out.println("s = " + s); - - - String s1 = db.executeTransactionally(""" - MATCH (p:Person) - WITH p.lpa as lpa, count(*) as sizeLpa - WITH apoc.map.fromPairs(collect([toString(lpa), sizeLpa])) as map_lpa - MATCH (p:Person) - WITH map_lpa, p.wcc as wcc, count(*) as sizeWcc - WITH map_lpa, apoc.map.fromPairs(collect([toString(wcc), sizeWcc])) as map_wcc - MATCH (p:Person) - WITH map_lpa, map_wcc, p.louvain as louvain, count(*) as sizeLouvain - WITH map_lpa, map_wcc, apoc.map.fromPairs(collect([toString(louvain), sizeLouvain])) as map_louvain - MATCH (p:Person) - RETURN p.name, map_lpa[toString(p.lpa)] as lpaSize, map_wcc[toString(p.wcc)] as wccSize, map_louvain[toString(p.louvain)] as louvainSize""", - Map.of(), Result::resultAsString); - System.out.println("s1 = " + s1); - - -// String s2 = db.executeTransactionally(""" -// match (p:Person) -// WITH apoc.agg.multiStats(p, ["lpa","wcc","louvain"], ["count"]) as data -// MATCH (p:Person) -// RETURN p.name, data.lpa.count as lpaSize, data.wcc.count as wccSize, data.louvain.count as louvainSize -// """, Map.of(), Result::resultAsString); -// System.out.println("s2 = " + s2); - String s2 = db.executeTransactionally(""" + RETURN p.name, + sumLouvain, avgLouvain, countLouvain, + sumWcc, avgWcc, countWcc, + sumAnother, avgAnother, countAnother, + sumLpa, avgLpa, countLpa""", Map.of(), + Iterators::asList); + + List multiStatsResult = db.executeTransactionally(""" match (p:Person) - with apoc.agg.multiStats(p, ["lpa","wcc","louvain"], ["count"]) as data + with apoc.agg.multiStats(p, ["lpa","wcc","louvain", "another"]) as data match (p:Person) - return p.name, data.wcc[toString(p.wcc)], data.louvain[toString(p.louvain)], data.lpa[toString(p.lpa)] - //MATCH (p:Person) - //RETURN p.name, data.lpa.count as lpaSize, data.wcc.count as wccSize, data.louvain.count as louvainSize - """, Map.of(), Result::resultAsString); - System.out.println("s2 = " + s2); - - // todo... - } - - - // similar to https://community.neo4j.com/t/how-could-to-replace-groupe-by-roll-up-and-group-by-cube-in-cypher-aggregation/44762 - @Test - public void test23() { - db.executeTransactionally(""" - CREATE (:Sales {ProductID: 10, CustomerID: 48, salesAmount: 100}), - (:Sales {ProductID: 90, CustomerID: 38, salesAmount: 200}), - (:Sales {ProductID: 90, CustomerID: 38, salesAmount: 300}), - (:Sales {ProductID: 90, CustomerID: 108, salesAmount: 300}), - (:Sales {ProductID: 90, CustomerID: 108, salesAmount: 300}), - (:Sales {ProductID: 20, CustomerID: 108, salesAmount: 300}), - (:Sales {ProductID: 20, CustomerID: 108, salesAmount: 300}), - (:Sales {ProductID: 20, CustomerID: 108, salesAmount: 301}), - (:Sales {ProductID: 20, CustomerID: 108, salesAmount: 301}), - (:Sales {ProductID: 30, CustomerID: 108, salesAmount: 300}), - (:Sales {ProductID: 30, CustomerID: 108, salesAmount: 301}), - (:Sales {ProductID: 30, CustomerID: 108, salesAmount: 301}), - (:Sales {ProductID: 30, CustomerID: 118, salesAmount: 300}), - (:Sales {ProductID: 30, CustomerID: 118, salesAmount: 301}), - (:Sales {ProductID: 40, CustomerID: 28, salesAmount: 400})"""); - - -// String s1 = db.executeTransactionally(""" -// MATCH (s:Sales) -// WITH s.ProductID as pid, s.CustomerID as cid, sum(s.salesAmount) as sa -// CALL { -// WITH pid, cid, sa -// WITH pid, cid, sum(sa) as saAmount RETURN saAmount -// } -// CALL { -// WITH pid, cid, sa -// WITH cid, sa, sum(pid) as saProduct RETURN saProduct -// } -// RETURN pid, cid, saProduct, saAmount, sa""", -// Map.of(), Result::resultAsString); - String s1 = db.executeTransactionally(""" - MATCH (p:Sales) - WITH p - CALL { - WITH p - MATCH (l:Sales {ProductID: p.ProductID}) - RETURN sum(l.ProductID) AS louvain - } - CALL { - WITH p - MATCH (another:Sales {salesAmount: p.salesAmount}) - RETURN sum(another.salesAmount) AS another - } - RETURN p.CustomerID, another, louvain""", - Map.of(), Result::resultAsString); - System.out.println("s1 = " + s1); - - -// String s11 = db.executeTransactionally(""" -// MATCH (s:Sales) -// WITH s.CustomerID as cid, sum(s.ProductID) as pid, sum(s.salesAmount) as sum -// RETURN cid, pid, sum -// // UNION -// // MATCH (s:Sales) -// // WITH null as cid, null as pid, sum(s.salesAmount) as sum -// // RETURN cid, pid, sum -// // UNION -// // MATCH (s:Sales) -// // WITH s.CustomerID as cid, null as pid, sum(s.salesAmount) as sum -// // RETURN cid, pid, sum;""", -// Map.of(), Result::resultAsString); -// System.out.println("s11 = " + s11); - - - String s2 = db.executeTransactionally(""" - match (p:Sales) - WITH apoc.agg.multiStats(p, ["ProductID","salesAmount"], ["sum"]) as data - match (p:Sales) - RETURN p.CustomerID, data.ProductID[toString(p.ProductID)].sum, data.salesAmount[toString(p.salesAmount)].sum - """, Map.of(), Result::resultAsString); - System.out.println("s2 = " + s2); - - String s23 = db.executeTransactionally(""" - match (p:Sales) - RETURN apoc.agg.multiStats(p, ["ProductID","salesAmount"], ["sum"]) as data - // match (p:Sales) - // RETURN DISTINCT p.CustomerID, data.ProductID[toString(p.ProductID)].sum, data.salesAmount[toString(p.salesAmount)].sum - """, Map.of(), Result::resultAsString); - System.out.println("s23 = " + s23); + return p.name, + data.wcc[toString(p.wcc)].avg AS avgWcc, + data.louvain[toString(p.louvain)].avg AS avgLouvain, + data.lpa[toString(p.lpa)].avg AS avgLpa, + data.another[toString(p.another)].avg AS avgAnother, + data.another[toString(p.another)].count AS countAnother, + data.wcc[toString(p.wcc)].count AS countWcc, + data.louvain[toString(p.louvain)].count AS countLouvain, + data.lpa[toString(p.lpa)].count AS countLpa, + data.another[toString(p.another)].sum AS sumAnother, + data.wcc[toString(p.wcc)].sum AS sumWcc, + data.louvain[toString(p.louvain)].sum AS sumLouvain, + data.lpa[toString(p.lpa)].sum AS sumLpa + """, Map.of(), Iterators::asList); + + assertEquals(multiAggregationResult, multiStatsResult); }