注意:为了帮助解决和(尝试)解决这个问题,我创建了一个测试类以及一个简单的User
应用程序域模型类来将此问题置于上下文中。
简而言之...
关于...
“如何在 WHERE 条件中使用 COUNT 作为条件? ”
您不能像count
在WHERE
子句的谓词中那样使用聚合 OQL 查询函数(我怀疑您已经发现),例如:
SELECT x.id, count(*) AS cnt FROM /Users x WHERE count(*) = 1 GROUP BY x.id
这会导致以下异常:
Caused by: org.apache.geode.cache.query.QueryInvalidException: Aggregate functions can not be used as part of the WHERE clause.
at org.apache.geode.cache.query.internal.QCompiler.checkWhereClauseForAggregates(QCompiler.java:204)
at org.apache.geode.cache.query.internal.QCompiler.checkWhereClauseForAggregates(QCompiler.java:214)
at org.apache.geode.cache.query.internal.QCompiler.select(QCompiler.java:260)
...
此外,不幸的是,以下 OQL 查询:
SELECT x.id, count(*) AS cnt FROM /Users x WHERE cnt = 1 GROUP BY x.id
不返回任何结果!
用于查找重复项的相反 OQL 查询也不返回任何结果:
SELECT x.id, count(*) AS cnt FROM /Users x WHERE cnt = 1 GROUP BY x.id
虽然,我不完全确定为什么,但我怀疑这是由于与上面第一个 OQL 查询相同的限制,在子句count
内的 OQL 查询谓词中使用了聚合函数WHERE
,除了后面的形式信息量较少(例如,像我怀疑它可能在某处吃异常,因为根据 GemFire,OQL 查询在语法上是正确的)。
但是,如果您只关心 ID,那么您可以简单地运行类似的 OQL 查询:
SELECT x.id, count(*) AS cnt FROM /Users x GROUP BY x.id
当然,这个 OQL 查询返回一个投影(或 GemFire Struct
( Javadoc )),它返回所有用户 ID(重复和唯一)的计数。显然,如果用户 ID 的计数为 1,则它是唯一的,如果大于 1,则重复(即不唯一)。
详细地...
User
但是,通常情况下,当User
实例具有唯一 ID(在您的情况下)或重复 ID时,用户希望访问实际对象(例如)。用户这样做是为了对 OQL 查询返回的 Region 条目值(例如 )执行一些操作User
,这在用于以并行和分布式方式处理 PARTITION Regions 的函数中特别常见。
但是,我不得不承认,我对无法(完全)解决这个问题有点傻眼。
老实说,我认为这个问题应该可以通过以下 GemFire OQL 查询来解决:
SELECT u
FROM /Users u, (SELECT DISTINCT x.id AS id, count(*) AS cnt FROM /Users x GROUP BY x.id) v
WHERE v.cnt = 1
AND u.id = v.id
ORDER BY u.name ASC
本质上,这个 OQL 查询会选择所有Users
他们的 ID 唯一的地方,因为它们是同类中的 1 个。
奇怪的是,这会导致 GemFire QueryInvalidException
:
org.springframework.data.gemfire.GemfireQueryException: ; nested exception is org.apache.geode.cache.query.QueryInvalidException:
at org.springframework.data.gemfire.GemfireCacheUtils.convertGemfireAccessException(GemfireCacheUtils.java:303)
at org.springframework.data.gemfire.GemfireCacheUtils.convertQueryExceptions(GemfireCacheUtils.java:325)
at org.springframework.data.gemfire.GemfireAccessor.convertGemFireQueryException(GemfireAccessor.java:109)
at org.springframework.data.gemfire.GemfireTemplate.find(GemfireTemplate.java:326)
at org.springframework.data.gemfire.repository.query.StringBasedGemfireRepositoryQuery.execute(StringBasedGemfireRepositoryQuery.java:159)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:135)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:119)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:151)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at io.stackoverflow.questions.apache.geode.query.$Proxy45.findUsersWithDuplicateId(Unknown Source)
at io.stackoverflow.questions.apache.geode.query.QueryCountEqualToOneIntegrationTests.duplicateCountQueryIsCorrect(QueryCountEqualToOneIntegrationTests.java:112)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.apache.geode.cache.query.QueryInvalidException:
at org.apache.geode.cache.query.internal.DefaultQuery.<init>(DefaultQuery.java:172)
at org.apache.geode.cache.query.internal.DefaultQueryService.newQuery(DefaultQueryService.java:150)
at org.springframework.data.gemfire.GemfireTemplate.find(GemfireTemplate.java:313)
... 43 more
Caused by: org.apache.geode.cache.query.TypeMismatchException: Exception in evaluating the Collection Expression in getRuntimeIterator() even though the Collection is independent of any RuntimeIterator
at org.apache.geode.cache.query.internal.CompiledIteratorDef.evaluateCollectionForIndependentIterator(CompiledIteratorDef.java:143)
at org.apache.geode.cache.query.internal.CompiledIteratorDef.getRuntimeIterator(CompiledIteratorDef.java:117)
at org.apache.geode.cache.query.internal.CompiledSelect.computeDependencies(CompiledSelect.java:189)
at org.apache.geode.cache.query.internal.DefaultQuery.<init>(DefaultQuery.java:170)
... 45 more
Caused by: java.lang.NullPointerException
at org.apache.geode.cache.query.internal.CompiledSelect.applyProjectionAndAddToResultSet(CompiledSelect.java:1309)
at org.apache.geode.cache.query.internal.CompiledSelect.doNestedIterations(CompiledSelect.java:800)
at org.apache.geode.cache.query.internal.CompiledSelect.doNestedIterations(CompiledSelect.java:844)
at org.apache.geode.cache.query.internal.CompiledSelect.doIterationEvaluate(CompiledSelect.java:703)
at org.apache.geode.cache.query.internal.CompiledSelect.evaluate(CompiledSelect.java:426)
at org.apache.geode.cache.query.internal.CompiledGroupBySelect.evaluate(CompiledGroupBySelect.java:157)
at org.apache.geode.cache.query.internal.CompiledGroupBySelect.evaluate(CompiledGroupBySelect.java:42)
at org.apache.geode.cache.query.internal.CompiledIteratorDef.evaluateCollection(CompiledIteratorDef.java:184)
at org.apache.geode.cache.query.internal.RuntimeIterator.evaluateCollection(RuntimeIterator.java:104)
at org.apache.geode.cache.query.internal.CompiledIteratorDef.evaluateCollectionForIndependentIterator(CompiledIteratorDef.java:128)
... 48 more
软件中没有什么比 NPE 更让我恼火的了!它们是一个明显且存在的程序员错误;不是用户错误!
看起来,GemFire 对子句中声明的嵌套 OQL 查询不满意FROM
,这实际上会创建一个可查询的集合,或在外部查询中使用的中间结果集(很像 RDBMS 临时表):
TypeMismatchException: Exception in evaluating the Collection Expression in getRuntimeIterator() even though the Collection is independent of any RuntimeIterator
也许,GemFire/Geode 对这个嵌套(临时)集合的“投影”特别不满意,因此这里的 NPE:
Caused by: java.lang.NullPointerException
at org.apache.geode.cache.query.internal.CompiledSelect.applyProjectionAndAddToResultSet(CompiledSelect.java:1309)
at org.apache.geode.cache.query.internal.CompiledSelect.doNestedIterations(CompiledSelect.java:800)
当我查看受影响的 GemFire/Geode代码时,确切的条件对我来说真的没有意义,因为我ClientCache
使用LOCAL
(仅)区域进行了测试。#叹
尽管如此,我什至尝试Cache
使用区域(启用 PDX(实际上是 PR 需要))使用对等实例进行测试,PARTITION
结果相同!#叹
count
鉴于 GemFire 查询引擎似乎在嵌套 OQL 查询(包含and子句)的投影中遇到问题,GROUP BY
我决定尝试向查询引擎提供更多信息,以期更好地告知查询引擎有关投影值的信息。因此,我创建了UserIdCount
投影类类型并在我的 OQL 查询中使用它,如下所示:
IMPORT io.stackoverflow.questions.spring.geode.app.model.UserIdCount;
SELECT DISTINCT u
FROM /Users u, (SELECT DISTINCT x.id AS id, count(*) AS cnt FROM /Users x GROUP BY x.id) v TYPE UserIdCount
WHERE v.cnt = 1
AND u.id = v.id
ORDER BY u.name ASC
当然,不幸的是,这也没有达到预期的效果,只会导致以下异常:
java.lang.IllegalArgumentException: element type must be struct
at org.apache.geode.cache.query.internal.StructSet.setElementType(StructSet.java:365)
at org.apache.geode.cache.query.internal.CompiledIteratorDef.prepareIteratorDef(CompiledIteratorDef.java:275)
at org.apache.geode.cache.query.internal.CompiledIteratorDef.evaluateCollection(CompiledIteratorDef.java:200)
at org.apache.geode.cache.query.internal.RuntimeIterator.evaluateCollection(RuntimeIterator.java:104)
at org.apache.geode.cache.query.internal.CompiledSelect.doNestedIterations(CompiledSelect.java:813)
...
看来我被一个 GemFire 卡住了Struct
,您认为 GemFire 在访问外部查询中的投影值时会知道如何在嵌套查询中处理它。但是无所谓!
我真的觉得 NPE 是 GemFire 的意外结果,并且 GemFire 真的应该能够(并且可能能够)处理这种类型的 OQL 查询。
那么,你还剩下什么。
好吧,正如我上面所说,如果您只关心 ID,那么您可以返回所有 ID 及其计数,并迭代 Struts 列表以找到计数为 1 的 ID。
当然,如果您最终对具有唯一(或可能重复)ID 的对象感兴趣以执行额外的处理,那么您需要将其分解为 2 个单独且单独的 OQL 查询,首先获取感兴趣的 ID,然后使用这些 IDUsers
在另一个查询中获取对象/值(例如)。
在这个测试用例中,我已经为您的用例(即唯一 ID)演示了这种两阶段查询方法。
无论如何,我希望这能给你一些选择或思考的事情。
干杯!