GemFire OQL 查询 - 如何在 WHERE 子句中使用 SELECT 语句的计数?

问题描述

我正在尝试从 /ExampleRegion 查询记录的所有 ID。如果ID的计数只有1,我想检索记录,因此该ID的区域中只有1条记录。

SELECT COUNT(*),id from /ExampleRegion group by id --> 仅当该 id 的计数为 1 时。

如何在 WHERE 条件中使用 COUNT 作为条件?

我尝试了以下方法,但没有用:

SELECT * from /ExampleRegion a where (SELECT count(*) as c,b.id from /ExampleRegion b where b.id = a.id and c = 1)

SELECT * from /ExampleRegion a where (SELECT count(*) as c from /ExampleRegion b where b.id = a.id ) = 1

我认为 GROUP BY 会起作用,尽管我似乎仍然找不到正确的 OQL。

非常感谢。

解决方法

注意:为了帮助解决和(尝试)解决这个问题,我创建了一个 test class 以及一个简单的 User application domain model class 来将这个问题放到上下文中。

简而言之...

关于...

"如何在 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 聚合函数用于 WHERE 内的 OQL 查询谓词子句,除了后面的形式信息较少(例如,我怀疑它可能在某处吃到异常,因为根据 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 实例具有唯一 ID(在您的情况下)或重复 ID 时,用户希望访问实际对象(例如 User)。用户这样做是为了对 OQL 查询返回的 Region 条目值(例如 User)执行一些操作,这在用于以并行和分布式方式处理 PARTITION Regions 的 Functions 中尤为常见。

但是,我不得不承认,无法(完全)解决这个问题让我有点傻眼。

老实说,我认为这个问题应该可以通过以下 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 对 FROM 子句中声明的嵌套 OQL 查询不满意,它本质上会创建一个可查询的集合,或在外部查询中使用的中间结果集(很像 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 code 时,确切的条件对我来说真的没有意义,因为我使用 ClientCache(仅)区域对 LOCAL 进行了测试。 #叹息

尽管如此,我什至尝试使用 Cache 区域(启用 PDX(实际上 PR 需要))对对等 PARTITION 实例进行测试,结果相同! #叹息

鉴于 GemFire 查询引擎在嵌套 OQL 查询(包含 countGROUP BY 子句)的投影中似乎有问题,我决定尝试向查询引擎提供更多信息,希望更好地告知查询引擎有关预计值的信息。因此,我创建了 UserIdCount projection class type 并在我的 OQL 查询中使用它,如下所示:

IMPORT io.stackoverflow.questions.spring.geode.app.model.UserIdCount;
SELECT DISTINCT u 
FROM /Users u,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 ,然后使用这些 ID 在另一个查询中获取对象/值(例如 Users)。

我已在此 test case 中为您的用例(即唯一 ID)演示了这种两阶段查询方法。

无论如何,我希望这能给你一些选择或考虑的事情。

干杯!

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...