HQL-запрос возвращает разные результаты по сравнению с одним и тем же логическим c критерием запроса - PullRequest
1 голос
/ 04 марта 2020

Мне нужен HQL-гуру, чтобы помочь со сложным запросом. Мои сопоставления: AccountingDocument:

AccountingDocument extends Document

@ManyToOne
@JoinColumn(name = "gestiune_id", nullable = false)
private Gestiune gestiune;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "accDoc", cascade = CascadeType.ALL)
private Set<Operatiune> operatiuni = new HashSet<>();

Документ:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Document

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne
@JoinColumn(name = "partner_id", nullable = true)
private Partner partner;

@Column(columnDefinition = "text")
private String name;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TipDoc tipDoc;

@Column(precision = 16, scale = 2, nullable = true)
private BigDecimal total;

Операция:

@Entity
public class Operatiune

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne
@JoinColumn(name = "accounting_doc_id", nullable = false)
private AccountingDocument accDoc;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TipOp tipOp;

@Column(nullable = false)
private String barcode; // taken from product
private String name; // taken from product
private String uom; // taken from product

@Column(precision = 12, scale = 2, nullable = false)
private BigDecimal valoareVanzareFaraTVA;

@Column(precision = 10, scale = 2, nullable = false)
private BigDecimal valoareVanzareTVA;

Партнер:

@Entity
public class Partner
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Column(nullable = false, unique = true)
private String name;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "partner")
private List<Document> documents = new ArrayList<>();

В основном * Класс 1013 * является операцией продажи, а класс AccountingDocument может представлять собой как документ продажи (пример: Счет-фактура), так и документ ca sh (пример: Квитанция).

Для счетов-фактур AccountingDocument.total равен нулю , В этом случае мы добавляем сумму от всех операций. Итого за операцию = Operatiune.valoareVanzareFaraTVA + Operatiune.valoareVanzareTVA

Для квитанций набор operatiuni пуст. В этом случае AccountingDocument.total заполняется.

Требование : вернуть всех партнеров (клиентов), которые не полностью заплатили нам. totalCashed не полностью оплачен

Я создал фильтр CriteriaQuery + Java для этого, но он слишком медленный, поэтому я пытаюсь преобразовать его в HQL.

Ожидание: CriteriaQuery + Java фильтрация возвращает тот же результат, что и HQL.

Результат: CriteriaQuery + Java фильтрация возвращает 29 партнеров, в то время как HQL возвращает 6 партнеров.

CriteriaQuery + Java фильтрация (примечание: «incasat» означает «обналиченный»):

final ImmutableList<Partner> unpaidPartners = allUnpaidPartners(); // 29


public ImmutableList<Partner> allUnpaidPartners() {
    final CriteriaBuilder cb = em.getCriteriaBuilder();
    final CriteriaQuery<Partner> cq = cb.createQuery(Partner.class);
    final Root<Partner> rootEntry = cq.from(Partner.class);
    final CriteriaQuery<Partner> all = cq.select(rootEntry);
    final TypedQuery<Partner> allQuery = em.createQuery(all);
    final List<Partner> allPartners = allQuery.getResultList();

    return allPartners.stream()
            .filter(partner -> globalIsMatch(partner.getName(), Partner.L1_L2, TextFilterMethod.NOT_EQUALS))
            .filter(partner -> globalIsMatch(partner.getName(), Partner.OP_INTERNA, TextFilterMethod.NOT_EQUALS))
            .filter(partner -> globalIsMatch(partner.getName(), Partner.STAT_PLATA, TextFilterMethod.NOT_EQUALS))
            .filter(partner -> globalIsMatch(partner.getName(), Partner.MARFA, TextFilterMethod.NOT_EQUALS))
            .filter(partner -> globalIsMatch(partner.getName(), Partner.CARD_NAME, TextFilterMethod.NOT_EQUALS))
            .filter(partner -> globalIsMatch(partner.getName(), Partner.STANDARD_PARTNER_NAME, TextFilterMethod.NOT_EQUALS))
            .filter(VanzariBean::isNotPaid)
            .sorted(Comparator.comparing(Partner::getName))
            .collect(toImmutableList());
}

protected static boolean isNotPaid(final Partner partner)
{
    final Optional<BigDecimal> totalIncasat = partner.getDocuments().stream()
            .filter(AccountingDocument.class::isInstance)
            .filter(doc -> doc.getTipDoc().equals(TipDoc.INCASARE))
            .map(Document::getTotal)
            .collect(Collectors.reducing(BigDecimal::add));

    final Optional<BigDecimal> totalSold = partner.getDocuments().stream()
            .filter(AccountingDocument.class::isInstance)
            .filter(doc -> doc.getTipDoc().equals(TipDoc.VANZARE))
            .map(Document::getTotal)
            .collect(Collectors.reducing(BigDecimal::add));

    return totalIncasat.orElse(BigDecimal.ZERO).compareTo(totalSold.orElse(BigDecimal.ZERO)) < 0;
}

public BigDecimal Document.getTotal()
{
    return total;
}

@Override
public BigDecimal AccountingDocument.getTotal()
{
    final BigDecimal total = super.getTotal();

    if (total == null)
        return add(getVanzareTotalFaraTva(), getVanzareTotalTva());

    return total;
}

public BigDecimal AccountingDocument.getVanzareTotalFaraTva()
{
    return getOperatiuni().stream()
            .map(Operatiune::getValoareVanzareFaraTVA)
            .reduce(BigDecimal::add)
            .orElse(BigDecimal.ZERO);
}

public BigDecimal AccountingDocument.getVanzareTotalTva()
{
    return getOperatiuni().stream()
            .map(Operatiune::getValoareVanzareTVA)
            .reduce(BigDecimal::add)
            .orElse(BigDecimal.ZERO);
}

HQL («incasat» = «обналиченный», «vandut» = «продано»):

final StringBuilder sb = new StringBuilder();
    sb.append("SELECT p FROM Partner p");
    sb.append(" LEFT JOIN AccountingDocument incasat WITH incasat.partner = p AND incasat.tipDoc = :qIncasatDoc");
    sb.append(" LEFT JOIN AccountingDocument vandut WITH vandut.partner = p AND vandut.tipDoc = :qVandutDoc");
    sb.append(" LEFT JOIN vandut.operatiuni opVandut");
    sb.append(" WHERE p.name != :qL1L2 AND p.name != :qOpInterna AND p.name != :qStatPlata AND p.name != :qMarfa AND p.name != :qCard AND p.name != :qStandard");
    // totalIncasat < totalSold => is not fully paid
    sb.append(" AND (select COALESCE(SUM(doc.total), 0) from AccountingDocument doc WHERE doc=incasat) < ")
        .append("(select COALESCE(SUM(op.valoareVanzareFaraTVA)+SUM(op.valoareVanzareTVA), 0) from Operatiune op WHERE op=opVandut)");
    sb.append(" GROUP BY p");

    final Query query = em.createQuery(sb.toString());
    query.setParameter("qL1L2", Partner.L1_L2);
    query.setParameter("qOpInterna", Partner.OP_INTERNA);
    query.setParameter("qStatPlata", Partner.STAT_PLATA);
    query.setParameter("qMarfa", Partner.MARFA);
    query.setParameter("qCard", Partner.CARD_NAME);
    query.setParameter("qStandard", Partner.STANDARD_PARTNER_NAME);

    query.setParameter("qIncasatDoc", TipDoc.INCASARE);
    query.setParameter("qVandutDoc", TipDoc.VANZARE);

    final List result = query.getResultList(); // 6 partners

1 Ответ

0 голосов
/ 07 марта 2020

Хорошо, похоже, логика c HQL по своей сути неверна. Фактически он проверял, меньше ли сумма любого накопленного счета любого Аккодо c этого Партнера, чем любая проданная сумма Аккодокса этого Партнера, вместо фактического сравнения суммы обналиченного и проданного итога всех Аккодок каждого Партнера.

Итак, поскольку подзапросы в предложении JOIN не поддерживаются в HQL, мне пришлось перейти на собственный запрос. Таким образом, правильный запрос:

SELECT * FROM Partner p 
    LEFT JOIN 
    (
        SELECT doc.partner_id, SUM(doc.total) incasat
        FROM Document doc
        INNER JOIN AccountingDocument accDoc ON accDoc.id = doc.id 
        WHERE doc.tipDoc = 'INCASARE'
        GROUP BY doc.partner_id
    ) i ON p.id = i.partner_id
    INNER JOIN 
    (
        SELECT doc.partner_id, SUM(op.valoareVanzareFaraTVA)+SUM(op.valoareVanzareTVA) vandut
        FROM Document doc
        INNER JOIN AccountingDocument accDoc ON accDoc.id = doc.id 
        INNER JOIN Operatiune op ON op.accounting_doc_id = accDoc.id
        WHERE doc.tipDoc = 'VANZARE'
        GROUP BY doc.partner_id
    ) v ON p.id = v.partner_id
    WHERE p.name != 'L1<->L2' AND p.name != 'OP INTERNA' AND p.name != 'STAT DE PLATA' AND p.name != 'MARFA' AND p.name != 'CARD INCASARE' AND p.name != 'STANDARD' 
    AND COALESCE(i.incasat, 0) < COALESCE(v.vandut, 0)

А в Java это будет:

final StringBuilder sb = new StringBuilder();
    sb.append("SELECT * FROM Partner p").append(NEWLINE);
    sb.append(" LEFT JOIN ").append(NEWLINE)
        .append("(").append(NEWLINE)
        .append("SELECT doc.partner_id, SUM(doc.total) incasat ").append(NEWLINE)
        .append("FROM Document doc ").append(NEWLINE)
        .append("INNER JOIN AccountingDocument accDoc ON accDoc.id = doc.id ").append(NEWLINE)
        .append("WHERE doc.tipDoc = :qIncasatDoc ").append(NEWLINE)
        .append("GROUP BY doc.partner_id ").append(NEWLINE)
        .append(") i ON p.id = i.partner_id").append(NEWLINE);
    sb.append(" INNER JOIN ").append(NEWLINE)
        .append("(").append(NEWLINE)
        .append("SELECT doc.partner_id, SUM(op.valoareVanzareFaraTVA)+SUM(op.valoareVanzareTVA) vandut ").append(NEWLINE)
        .append("FROM Document doc ").append(NEWLINE)
        .append("INNER JOIN AccountingDocument accDoc ON accDoc.id = doc.id ").append(NEWLINE)
        .append("INNER JOIN Operatiune op ON op.accounting_doc_id = accDoc.id ").append(NEWLINE)
        .append("WHERE doc.tipDoc = :qVandutDoc ").append(NEWLINE)
        .append("GROUP BY doc.partner_id ").append(NEWLINE)
        .append(") v ON p.id = v.partner_id").append(NEWLINE);
    sb.append(" WHERE p.name != :qL1L2 AND p.name != :qOpInterna AND p.name != :qStatPlata AND p.name != :qMarfa AND p.name != :qCard AND p.name != :qStandard").append(NEWLINE);
    // totalIncasat < totalSold => is not fully paid
    sb.append(" AND COALESCE(i.incasat, 0) < COALESCE(v.vandut, 0)");

    final Query query = em.createNativeQuery(sb.toString(), Partner.class);
    query.setParameter("qL1L2", Partner.L1_L2);
    query.setParameter("qOpInterna", Partner.OP_INTERNA);
    query.setParameter("qStatPlata", Partner.STAT_PLATA);
    query.setParameter("qMarfa", Partner.MARFA);
    query.setParameter("qCard", Partner.CARD_NAME);
    query.setParameter("qStandard", Partner.STANDARD_PARTNER_NAME);

    query.setParameter("qIncasatDoc", TipDoc.INCASARE.toString());
    query.setParameter("qVandutDoc", TipDoc.VANZARE.toString());

Небольшой совет здесь! em.createNativeQuery возвращает нестандартный c запрос, поэтому, если вы хотите получить результат в виде потока и хотите обработать этот поток и привести элементы, вам нужно выполнить следующий трюк:

final Stream<?> resultStream = query.getResultStream();

и тогда вы можете обработать его:

resultStream
            .map(Partner.class::cast)
            .flatMap(Partner::getDocumentsStream)
...