我有一个 Postgres SQL 查询,它将在 jsonb 类型列中定义的键的值数组内执行搜索操作。
SELECT o.id
FROM customer c INNER JOIN order o
on c.id = o.c_id where EXISTS (
SELECT 1
FROM jsonb_array_elements_text(c.customer_data->'sid') AS value
WHERE value = '3456' OR value='89110'
);
在这里, sid 是我的密钥并且具有值数组。给定的查询将在值数组内执行深度搜索,如果键与这些值中的任何一个匹配,则返回记录。
我想在 spring data jpa 中生成其等效项,它将使用条件生成器和条件查询返回相同的结果。有关于此的任何建议都会有帮助吗?
我的两个实体都以一对多关系连接,我正在尝试实现上述查询结果:
public List<Long> findCustomerIds(EntityManager entityManager,String key, List<String> sidValues) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Customer> cRoot = cq.from(Customer.class);
// Join between Customer and Order
Join<Customer, Order> oJoin = cRoot.join("orders");
// Subquery to handle JSONB array elements condition
Subquery<String> subquery = cq.subquery(String.class);
Root<Customer> subRoot = subquery.from(Customer.class);
Path<Object> subCustomerDataPath = subRoot.get("customerData");
Path<String> subSidPath = cb.function("jsonb_array_elements_text", String.class, subCustomerDataPath.get(key));
subquery.select(subRoot.get("id"))
.where(cb.or(
sidValues.stream()
.map(sid -> cb.equal(cb.upper(subSidPath), sid.toUpperCase()))
.toArray(Predicate[]::new)
));
cq.select(cRoot.get("id"))
.where(cb.equal(oJoin.get("customer"), cRoot), cb.exists(subquery));
return entityManager.createQuery(cq).getResultList();
}
问题出在代码的给定部分:
// Subquery to handle JSONB array elements condition
Subquery<String> subquery = cq.subquery(String.class);
Root<Customer> subRoot = subquery.from(Customer.class);
Path<Object> subCustomerDataPath = subRoot.get("customerData");
Path<String> subSidPath = cb.function("jsonb_array_elements_text", String.class, subCustomerDataPath.get(key));
subquery.select(subRoot.get("id"))
.where(cb.or(
sidValues.stream()
.map(sid -> cb.equal(cb.upper(subSidPath), sid.toUpperCase()))
.toArray(Predicate[]::new)
));
我在子查询部分收到非法尝试取消引用路径源[null.customerData]错误。我没有明白我的代码有什么问题。
作为参考,我在实体中以以下方式定义了 jsonb 字段,因为所有键和值都以以下方式存储:
@Type(type = "jsonb")
@Column(name = "customer_data",columnDefinition = "jsonb")
private Map<String,List<String>> customerData = new HashMap<>();
如果您有任何替代方法或任何建议都会有所帮助。预先感谢您的帮助。
与 Hibernate Criteria 中的函数一起应用时,jsonb_array_element_text 表达式存在一些问题。
给定的查询可以用作上述查询的替代,以获得相同的结果:
SELECT o.id
FROM customer c INNER JOIN order o
on c.id = o.c_id where LIKE c.customer_data->'sid' LIKE '%"3456"%' OR c.customer_data->'sid' LIKE '%"89110"%'
注意:内部存储的数组值将被转换为字符串并对字符串执行搜索操作。 如果值是字符串数组,请确保传递引号,否则它将无法按预期工作。
要使用休眠标准实现相同的功能,请使用 jsonb_extract_path_text 函数。
public List<Long> findCustomerIds(EntityManager entityManager,String key, List<String> sidValues) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Customer> cRoot = cq.from(Customer.class);
// Join between Customer and Order
Join<Customer, Order> oJoin = cRoot.join("orders");
// Constructing the WHERE clause predicates
Predicate[] predicates = new Predicate[values.size()];
for (int i = 0; i < values.size(); i++) {
predicates[i]= cb.like(cb.function("jsonb_extract_path_text", String.class, subEntityJoin.get("customerData"), cb.literal(key)),"%\"" + values.get(i) + "\"%");
}
// Applying OR condition
requestPredicate= cb.or(predicates);
cq.select(cRoot.get("id")).where(requestPredicate);
return entityManager.createQuery(cq).getResultList();
}
如果值存储在字符串格式列表中,则在外部传递要搜索的值的引号,否则它将按预期工作。