Hibernate HQL - Use CASE WHEN in COUNT Statement like IF in MySQL - mysql

I am trying to port a MySQL Query that works to Hibernate HQL, this is all very new to me, so I am open to any kind of hint (Wrong Way, Wrong Structure, change all... ;) )
Two tables A and B. (Structure broken down, only the relevant parts)
A contains entrys, each with a unique ID.
B references those IDs and holds a boolean-like marker (TINYINT(1)).
I want to know how many rows there are in B for each row in A with the Id from A's Row and Marker == True (1).
My MySQL query was like this:
SELECT A.id, COUNT( IF( B.marker = 1, 1, NULL ) ) AS markerTrue, COUNT( IF( B.marker =0, 1, NULL ) ) AS markerFalse FROM A LEFT JOIN B ON B.a_id = A.id GROUP BY A.id
It works and I ported it to this (HQL):
SELECT A.id, COUNT(CASE WHEN B.marker = 1 THEN 1 ELSE NULL END) as markerTrue, COUNT(CASE WHEN B.marker = 0 THEN 1 ELSE NULL END) as markerFalse FROM A LEFT JOIN B WITH B.a_id = A.id GROUP BY A.id
This throws an Exception:
org.hibernate.hql.ast.QuerySyntaxException: unexpected token: CASE near ...
In the logs, there is also
org.hibernate.hql.ast.ErrorCounter - line 1:19: unexpected token: CASE
antlr.NoViableAltException: unexpected token: CASE
But thats just the same internal Error.
Is there a way to do this in HQL? Is there another better way, like restructuring the tables, what is an experts opinion on this?

I am by no means an expert — when HQL stymies me, I rarely have qualms about bypassing the problem by switching to straight SQL — so I can't tell you if there is a better, more HQL-ish way to do this. But in your specific instance, where B.marker is always either 0 or 1, I suppose you could change
COUNT(CASE WHEN B.marker = 1 THEN 1 ELSE NULL END)
to
SUM(B.marker)
and
COUNT(CASE WHEN B.marker = 0 THEN 1 ELSE NULL END)
to
COUNT(*) - SUM(B.marker)
(though you may also need to wrap your SUMs in COALESCE(..., 0) — I'm not sure).

A rewrite in SQL. I hope it's more easily converted to HQL:
SELECT A.id
, COALESCE(markerTrue, 0) AS markerTrue
, COALESCE(markerFalse, 0) AS markerFalse
FROM A
LEFT JOIN
( SELECT a_id
, COUNT(*) AS markerTrue
FROM B
WHERE marker = 1
GROUP BY a_id
) AS BT
ON BT.a_id = A.id
LEFT JOIN
( SELECT a_id
, COUNT(*) AS markerFalse
FROM B
WHERE marker = 0
GROUP BY a_id
) AS BF
ON BF.a_id = A.id

Oops, maybe too late?
Try :
SUM(CASE WHEN B.marker = 0 THEN 1 ELSE NULL END) AS your_result
as there is no "conditionnal" count...

Related

Problems with query speed when using a nested query for item count

When I add the nested query for invCount, my query time goes from .03 sec to 14 sec. The query works and I get correct values, but it is very, very slow in comparison. Is that just because I have to many conditions in that query? When I take it out and still have the second nested query, the time is still .03 secs. There is clearly something about the first nested query the database doesn't like, but I am not seeing what it is. I have a foreign key set for all the inner join lines too. Any help or ideas would be appreciated.
SELECT a.*,
f.name,
f.partNumber,
f.showInAdminStore,
f.showInPublicStore,
f.productImage,
r.mastCatID,
(SELECT COUNT(b.inventoryID)
FROM storeInventory b
INNER JOIN events c ON c.eventID = b.eventID
WHERE b.pluID = a.pluID
AND b.listPrice = a.listPrice
AND b.unlimitedQty = a.unlimitedQty
AND (b.packageID = a.packageID OR (b.packageID IS NULL AND a.packageID IS NULL))
AND b.orderID IS NULL
AND c.isOpen = '1'
AND b.paymentTypeID <= '2'
AND (b.inCart < '$cartTime' OR b.inCart IS NULL) ) AS invCount,
(SELECT COUNT(x.inventoryID)
FROM storeInventory x
WHERE x.packageID = a.inventoryID) AS packageCount
FROM storeInventory a
INNER JOIN storePLUs f ON f.pluID = a.pluID
INNER JOIN storeCategories r ON r.catID = f.catID
INNER JOIN events d ON d.eventID = a.eventID
WHERE a.storeFrontID = '1'
AND a.orderID IS NULL
AND a.paymentTypeID <= '2'
AND d.isOpen = '1'
GROUP BY a.packageID, a.unlimitedQty, a.listPrice, a.pluID
Table from query output
UPDATE: 12/12/2022
I changed the line checking the packageID to "AND (b.packageID <=> a.packageID)" as suggested and that cut my query time down to 7.8 seconds from 14 seconds. Thanks for the pointer. I will definitely use that in the future for NULL comparisons.
using "count(*)" took about half a second off. When I take the first nested query out, it drops down to .05 seconds even with the other nested queries in there, so I feel like there is still something causing issues. I tried running it without the other "AND (b.inCart < '$cartTime' OR b.inCart IS NULL)" line and that did take about a second off, but no where what I was hoping for. Is there an operand that includes NULL on a less than comparison? I also tried running it without the inner join in the nested query and that didn't change much at all. Of course removing any of that, throughs the values off and they become incorrect, so I can't run it that way.
Here is my current query setup that still pulls correct values.
SELECT a.*,
f.name,
f.partNumber,
f.showInAdminStore,
f.showInPublicStore,
f.productImage,
r.mastCatID,
(SELECT COUNT(*)
FROM storeInventory b
INNER JOIN events c ON c.eventID = b.eventID
WHERE b.pluID = a.pluID
AND b.listPrice = a.listPrice
AND b.unlimitedQty = a.unlimitedQty
AND (b.packageID <=> a.packageID)
AND b.orderID IS NULL
AND c.isOpen = '1'
AND b.paymentTypeID <= '2'
AND (b.inCart < '$cartTime' OR b.inCart IS NULL) ) AS invCount,
(SELECT COUNT(x.inventoryID)
FROM storeInventory x
WHERE x.packageID = a.inventoryID) AS packageCount
FROM storeInventory a
INNER JOIN storePLUs f ON f.pluID = a.pluID
INNER JOIN storeCategories r ON r.catID = f.catID
INNER JOIN events d ON d.eventID = a.eventID
WHERE a.storeFrontID = '1'
AND a.orderID IS NULL
AND a.paymentTypeID <= '2'
AND d.isOpen = '1'
GROUP BY a.packageID, a.unlimitedQty, a.listPrice, a.pluID
I am not familiar with the term 'Composite indexes' Is that something different than these?
Screenshot of ForeignKeys on Table a
I think
AND (b.packageID = a.packageID
OR (b.packageID IS NULL
AND a.packageID IS NULL)
)
can be simplified to ( https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to ):
AND ( b.packageID <=> a.packageID )
Use COUNT(*) instead of COUNT(x.inventoryID) unless you check for not-NULL.
The subquery to compute packageCount seems strange; you seem to count inventories but join on packages.
The need to reach into another table to check isOpen is part of the performance problem. If eventID is not the PRIMARY KEYforevents, then add INDEX(eventID, isOpen)`.
Some other indexes that may help:
a: INDEX(storeFrontID, orderID, paymentTypeID)
a: INDEX(packageID, unlimitedQty, listPrice, pluID)
b: INDEX(pluID, listPrice, unlimitedQty, orderID)
f: INDEX(pluID, catID)
r: INDEX(catID, mastCatID)
x: INDEX(packageID, inventoryID)
After OP's Update
There is no way to do (x<y OR x IS NULL) except by switching to a UNION. In your case, it is pretty easy to do the conversion. Replace
( SELECT COUNT(*) ... AND ( b.inCart < '$cartTime'
OR b.inCart IS NULL ) ) AS invCount,
with
( SELECT COUNT(*) ... AND b.inCart < '$cartTime' ) +
( SELECT COUNT(*) ... AND b.inCart IS NULL ) AS invCount,
Revised indexes:
storePLUs:
INDEX(pluID, catID)
storeCategories:
INDEX(catID, mastCatID)
events:
INDEX(isOpen, eventID)
storeInventory:
INDEX(pluID, listPrice, unlimitedQty, orderID, packageID)
INDEX(pluID, listPrice, unlimitedQty, orderID, inCart)
INDEX(packageID, inventoryID)
INDEX(storeFrontID, orderID, paymentTypeID)

Turn simple query into a count case statement

I have a query that is quite helpful, that I would like help simplifying and turning into a count that I can add to a larger query that I'm working on. I have a scenario that occurs in my data where there are many to one relationships where there shouldn't be. Just to keep it simple, I'd describe it as if ford were describing all of the sedans they sold of a particular model, and in their data base they had more than one occurrence of a 'Red Sedan'. This causes problems for us, and I'd like to product a simple count of that occurrence in a larger query.
I have a query that successfully uncovers all cases of duplicates of 'red sedan' occurring (below)
select p.model, p.mFinish, count(p.productVendorStockNumber) as prodno, b.BrandName
from products p
join brands b on p.brandID = b.brandID
where p.brandid = 299 and p.`status` = 1 and p.model != ''
GROUP BY p.model, p.mFinish
HAVING prodno > 1;
which produces an output something like this (did my best to reproduce it there - sorry)
|Model|Finish| Prod No| BrandName|
|12334|Red | 2 | Santec
What I'd like to do though is have just the count, which in this case is 'prod no' come out in a case statement. I'm sort of a journeymen SQL guy so reducing an entire query into a count is something that is beyond me. I'm not even quite sure what to search for, I tried enclosing the entire query in "Count" and "Sum" on a lark and obviously that didn't work. When someone has a moment can they please provide a little insight into how something like that would be done?
I don't know if this is helpful or not, but the query I'm adding it to is below
select b.brandName, b.BrandCode, SUM(Case When p.IMAP > p.MSRP THEN 1 ELSE NULL END) as 'MAP Greater Than MSRP', COUNT(DISTINCT(p.ProductVendorStockNumber)) as 'Total Products', MAX(dl.createDate) 'Most Recent Data Load', b.BrandStatus, b.datasource as 'Data Source', b.state as 'State', CASE WHEN dl.datanextConf = 1 THEN 'Confirmed' ELSE 'Not Confirmed' END AS 'Data Incoming?'
from products p
left join brands b on p.brandID = b.brandID
left join dataloads dl on b.brandID = dl.brandID
where p.status = 1 and b.brandID = 653
Not sure why, but someone posted an answer and then the answer was deleted. I'm reposting what they provided.
SELECT
b.brandName,
b.BrandCode,
SUM( CASE WHEN p.IMAP > p.MSRP THEN 1 ELSE NULL END ) AS 'MAP Greater Than MSRP',
COUNT( DISTINCT ( p.ProductVendorStockNumber ) ) AS 'Total Products',
ROUND(COUNT( DISTINCT (p.ImageName) )/COUNT( DISTINCT ( p.ProductVendorStockNumber ) ),2) '% with images',
MAX( dl.createDate ) 'Most Recent Data Load',
b.BrandStatus,
b.datasource AS 'Data Source',
b.state AS 'State',
CASE
WHEN dl.datanextConf = 1 THEN
'Confirmed' ELSE 'Not Confirmed'
END AS 'Data Incoming?',
(
SELECT
count( * )
FROM
products p2
WHERE
p2.brandid = p.brandid
AND p2.model = p.model
AND p2.mFinish = p.mFinish
AND p2.`status` = p.`status`
) AS 'Total Duplicate Finishes'
FROM
products p
LEFT JOIN brands b ON p.brandID = b.brandID
LEFT JOIN dataloads dl ON b.brandID = dl.brandID
WHERE
p.STATUS = 1
AND b.brandID = 286

Should I find correct MySQL query or optimize database structure?

I have database of following structure:
TABLE ingredients (ingredient_name, color)
TABLE recipes (recipe_name)
TABLE recipes_ingredients_parts (recipe_name, ingredient_name, parts)
What I want is to get a recipe that corresponds with selected ingredients and their number. So what I`ve tried first was query:
SELECT rr.* FROM
(SELECT r.* FROM receipes r
INNER JOIN receipes_ingredients_parts ri
ON r.receipe_name = ri.receipe_name
AND ri.ingredient_name = 'espresso'
AND ri.parts_number = '1') rr;
And what I get are {"Americano", "Espresso"}. But that should be "Espresso" only because for "Americano" there should be the query:
SELECT rr.* FROM
(SELECT r.* FROM receipes r
INNER JOIN receipes_ingredients_parts ri
ON r.receipe_name = ri.receipe_name
AND ri.ingredient_name = 'espresso'
AND ri.parts_number = '1') rr
INNER JOIN receipes_ingredients_parts ri
ON rr.receipe_name = ri.receipe_name
AND ri.ingredient_name = 'water'
AND ri.parts_number = '4';
Next my idea was to alter recipe table and add columns for each ingredient to store it's quantity for the recipe. But it would be near 20 columns of that kind. So I'm confused with thought that I'm doing job in a bad style. Maybe I should use some good query for the purpose? Do you guys have any ideas about all the stuff?
I think this is what you are looking for, it should find receipe_names that have all the ingredients in your list, and no other ingredients.
SELECT receipe_name
, SUM(CASE
WHEN (ingredient_name, parts_number) IN (('espresso','1'))
THEN 1 ELSE 0
END
) AS matchedIngredients
, SUM(CASE
WHEN (ingredient_name, parts_number) NOT IN (('espresso','1'))
THEN 1 ELSE 0
END
) AS otherIngredients
FROM receipes_ingredients_parts
GROUP BY receipe_name
HAVING matchedIngredients = 1 AND otherIngredients = 0
A more generalized version/template:
SELECT aField
, SUM(CASE
WHEN someField IN ([matchList])
THEN 1
ELSE 0
END
) AS matches
, SUM(CASE
WHEN someField NOT IN ([matchList])
THEN 1
ELSE 0
END
) AS others
FROM aTable
GROUP BY aField
HAVING matches = [# of values in matchlist]
AND others = 0
Alternatively, if items in the matchlist might be repeated in the table for an "aField" value:
SELECT aField
, COUNT(DISTINCT CASE
WHEN someField IN ([matchList])
THEN someField
ELSE NULL
END
) AS matches
, COUNT(DISTINCT CASE
WHEN someField NOT IN ([matchList])
THEN someField
ELSE NULL
END
) AS others
FROM aTable
GROUP BY aField
HAVING matches = [# of values in matchlist]
AND others = 0

SQL Query Help - Joining Multiple Columns Based On Condition [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
SQL query help - have two where conditons in join condition
I have the following tables with the columns as below. I have mentioned what I need from this. I already posted a link in here SQL query help - have two where conditons in join condition with what I have been trying but cannot get this through. Once again positing it plainly with what I need:
Book
BookId, BookName
Desk
DeskId, BookId ,DeskName
CounterParty
CPId, CpName
Trade
TradeId, Buyer, Seller
This is how the Buyer and Seller data would be :
Buyer Seller
B3232 B323
C32 B222
B323 C323
Based on the starting character B or C in these two columns, I need to join Book or CP table to check the ids.
I need **t.TradingDeskName, b.BookName, c.CpName, t.Buyer, t.Seller.**
Any help is very much appreciated.
Thanks,
mani
p.s : I am trying to get this done through SQL or Linq to Sql.
The recent query but have more to fix :
SELECT DISTINCT desk.Name as TradingDeskName, b.Name as Book, t.Seller, t.Buyer, c.PartyName, FROM TradingDesk AS desk
RIGHT JOIN Book as b
ON b.TradingDeskId = d.Id
RIGHT JOIN Trade as t
ON LEFT(t.Buyer, 1) = 'B' AND SUBSTRING(t.Buyer, 2, len(t.Buyer)) = b.Id
LEFT JOIN Book as b1
ON LEFT(t.Seller, 1) = 'B' AND SUBSTRING(t.Seller, 2, len(t.Seller)) = b1.Id
LEFT JOIN CounterParty as c
ON LEFT(t.Buyer, 1) = 'C' AND SUBSTRING(t.Buyer, 2, len(t.Buyer)) = c.PartyId
LEFT JOIN CounterParty as c1
ON LEFT(t.Seller, 1) = 'C' AND SUBSTRING(t.Seller, 2, len(t.Seller)) = c1.PartyId
As I mentioned I need :
Desk.Name - B.Name - T.Seller - T.Buyer- C.PartyName
The C.PartyName will have the value if T.Seller or T.Buyer value is starting with 'C' (from CounterParty Table) else will be null.
With the above query, I have null values coming in Desk.Name, B.Name and the logic of gettig C.PartyName is also not working.
There are a couple ways I could think of for achieving the desired results but because first things should come first, I'd suggest to modify the DB design if it is at all possible.
So, here are the 2 queries that I could work out:
Query 1
SELECT `t`.*,
(CASE
WHEN LEFT(`t`.`Buyer`, 1) = 'B' THEN
(SELECT `b`.`BookName`
FROM `Book` `b`
WHERE `b`.`BookId` = SUBSTRING(`t`.`Buyer`, 2))
ELSE (SELECT `c`.`CPName`
FROM `CounterParty` `c`
WHERE `c`.`CPId` = SUBSTRING(`t`.`Buyer`, 2))
END) AS `buyer_name`,
(CASE
WHEN LEFT(`t`.`Seller`, 1) = 'B' THEN
(SELECT `b`.`BookName`
FROM `Book` `b`
WHERE `b`.`BookId` = SUBSTRING(`t`.`Seller`, 2))
ELSE (SELECT `c`.`CPName`
FROM `CounterParty` `c`
WHERE `c`.`CPId` = SUBSTRING(`t`.`Seller`, 2))
END) AS `seller_name`
FROM `Trade` `t`
Query 2
SELECT *
FROM `Trade` `t`
LEFT JOIN `Book` `b` ON LEFT(`t`.`Buyer`, 1) = 'B' AND SUBSTRING(`t`.`Buyer`, 2) = `b`.`BookId`
LEFT JOIN `Book` `b1` ON LEFT(`t`.`Seller`, 1) = 'B' AND SUBSTRING(`t`.`Seller`, 2) = `b1`.`BookId`
LEFT JOIN `CounterParty` `c` ON LEFT(`t`.`Buyer`, 1) = 'C' AND SUBSTRING(`t`.`Buyer`, 2) = `c`.`CPId`
LEFT JOIN `CounterParty` `c1` ON LEFT(`t`.`Seller`, 1) = 'C' AND SUBSTRING(`t`.`Seller`, 2) = `c1`.`CPId`;
Both the above queries return same results but in different formats. Please try and see which one works best for you.
Also, it isn't very clear from your question where does the table Desk fit in and what relations does it hold with other tables. Please feel free to add respective columns you'll need from Desk.
Please note that the suggested queries are in MySQL. It is not very clear what system are you running - you've mentioned in your post that you are trying using SQL or Linq SQL and in the tags you've mentioned everything + MySQL.
You could do it something like this (untested):
select
t.Buyer,
t.Seller,
case when t.Buyer like 'B%' THEN (select BookName from Book where BookId = t.Buyer)
ELSE (select CpName from Counterparty where CPId = t.Buyer)
end BuyerName,
case when t.Buyer like 'B%' THEN (select DeskName from Desk where BookId = t.Buyer)
ELSE NULL
end BuyerDeskName,
case when t.Seller like 'B%' THEN (select BookName from Book where BookId = t.Seller)
ELSE (select CpName from Counterparty where CPId = t.Seller)
end SellerName,
case when t.Seller like 'B%' THEN (select DeskName from Desk where BookId = t.Seller)
ELSE NULL
end SellerDeskName,
from
Trade t
The problem you have is that, since the table you want to join to is data driven, you can't specify it in the FROM clause..

SQL Custom Order By

I can't understand why this doesn't work:
select distinct a.QuestionID,a.QuestionName,b.AnswerID,b.AnswerName
from #TempExportList a
join tblAnswers b
on a.QuestionID = b.QuestionID
where a.PaperID=#PaperID
order by (case when a.QuestionName='A' then 0
when a.QuestionName='B' then 1
else a.QuestionID
end)
I get the following error -
ORDER BY items must appear in the
select list if SELECT DISTINCT is
specified.
But this works fine:
select distinct a.QuestionID,a.QuestionName,b.AnswerID,b.AnswerName
from #TempExportList a
join tblAnswers b
on a.QuestionID = b.QuestionID
where a.PaperID=#PaperID
order by a.QuestionID
The error message explains the problem perfectly.
In the first example the ORDER BY item -- CASE WHEN ... END -- doesn't appear in the SELECT list.
In the second example the ORDER BY item -- a.QuestionID -- does appear in the SELECT list.
To fix the first example you'll need to do something like this:
SELECT DISTINCT a.QuestionID, a.QuestionName, b.AnswerID, b.AnswerName,
CASE WHEN a.QuestionName = 'A' THEN 0
WHEN a.QuestionName = 'B' THEN 1
ELSE a.QuestionID
END
FROM #TempExportList AS a
JOIN tblAnswers AS b
ON a.QuestionID = b.QuestionID
WHERE a.PaperID = #PaperID
ORDER BY CASE WHEN a.QuestionName = 'A' THEN 0
WHEN a.QuestionName = 'B' THEN 1
ELSE a.QuestionID
END
You can get around this with a CTE
;WITH T AS
(
SELECT DISTINCT a.QuestionID,a.QuestionName,b.AnswerID,b.AnswerName
FROM #TempExportList a
JOIN tblAnswers b
ON a.QuestionID = b.QuestionID
WHERE a.PaperID=#PaperID
)
SELECT *
FROM T
ORDER BY
CASE
WHEN QuestionName='A'
THEN 0
WHEN QuestionName='B'
THEN 1
ELSE QuestionID
END
I would have thought the message self explanitory.
You have selected a distinct on a.QuestionID,a.QuestionName,b.AnswerID and b.AnswerName. Therefore, there could be rows of data with the same respective values for each of these fields, but a different one for your case statement.
Consider this
a.QuestionID a.QuestionName b.AnswerID b.AnswerName [case statement]
1 'One' 2 'Two' 0
1 'One' 2 'Two' 1
How does the query know which value in the last column to use in the order? Is it 0? It is 1? Quite simply, it can't determine, so it can't use it, hence the error.
The second example is fine, because a.QuestionID does appear in the SELECT list, and the query can happily apply the ordering.