Turn simple query into a count case statement - mysql

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

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)

How can I correct this query that involves a CASE statement for a summary?

I'm currently trying to solve an issue revolving around summarizing a list of publishers, their total revenue, total payouts, and their net profit. What makes this slightly complicated is that the total payout is contingent on a case statement (due to having to choose between the higher value of royalties). This case statement was perfectly fine and executed in a previous query that you can see on the SQLFiddle link down below. My issue is that I have a near finished query that addresses what I need but I don't know what correction to make for it to complete. Help would be super appreciated! And if you get it to work, you would be a legit lifesaver!!
Select name,
SUM(book.msrp) AS 'Total Revenue',
SUM(EarningByBook) AS 'Total Payouts',
SUM(book.msrp)-SUM(EarningByBook) AS 'Net Profit'
FROM
(SELECT publisher.name, book.msrp,
(SELECT
CASE WHEN preferred_royalties > standard_royalties
THEN preferred_royalties*copies_sold
ELSE standard_royalties*copies_sold END
AS 'EarningByBook',
copies_sold ,
YEAR(CURDATE())-YEAR(date_published) Years
INNER JOIN book ON publisher.id = book.publisher_id)
FROM author A
JOIN book B ON A.id=B.author_id
JOIN publisher P ON B.publisher_id=P.id)
From publisher
INNER JOIN book ON publisher.id = book.publisher_id) Z
GROUP BY
name;
The SQL fiddle is as follows :
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=b0015a0a4286f9b2c064bbd65525faa5&hide=13312
The output expected should look
Publisher
Total Revenue
Total Payouts
Net Profit
name
20000
1500
18500
name
15000
1000
14000
Consider flattening all your inner selects to a single SELECT subquery.
SELECT sub.publisher
, SUM(sub.msrp) AS 'Total Revenue'
, SUM(sub.EarningByBook) AS 'Total Payouts'
, SUM(sub.msrp) - SUM(sub.EarningByBook) AS 'Net Profit'
FROM
(SELECT
P.`name` AS publisher
, CASE
WHEN A.preferred_royalties > P.standard_royalties
THEN A.preferred_royalties * B.copies_sold
ELSE P.standard_royalties * B.copies_sold
END AS EarningByBook
, YEAR(CURDATE()) - YEAR(B.date_published) AS Years
, B.msrp
, B.copies_sold
FROM author A
INNER JOIN book B
ON A.id = B.author_id
INNER JOIN publisher P
ON B.publisher_id = P.id
) AS sub
GROUP BY
sub.publisher;

MYSQL database IFNULL query

I'm trying to display a 'NULL' value as 0 using 'IFNULL' but it returns all rows as null or 0. Everything works fine until I add the 'lounge2' table and the 'lounge2order2' table. The order2.lounge2orderid is 'NULL'. I know it has something to do with 'JOINS' but not sure where or which to implement.
Thanks all...
SELECT orders2.orderid, orders2.orderdate, branch2.branchname, COUNT(orders2.garment2orderid) AS 'no gar orders', SUM(garment2.hireprice) as 'total gar sold',
COUNT(orders2.lounge2orderid+IFNULL(orders2.lounge2orderid,0)) as 'No of lounge sales', SUM(lounge2.hirerate)
from orders2, branch2, garment2, garment2order2, lounge2, lounge2order2
WHERE orders2.orderid IN
(SELECT orders2.orderid FROM orders2
WHERE orders2.branchid = 2
AND YEAR(orders2.orderdate)= 2011)
AND branch2.branchid IN
(SELECT branch2.branchid from branch2
WHERE branch2.branchid = orders2.branchid)
AND garment2order2.garment2orderid IN
(SELECT garment2order2.garment2orderid FROM garment2order2
WHERE garment2order2.garment2orderid = orders2.garment2orderid)
AND garment2.garmentid IN
(SELECT garment2.garmentid FROM garment2
WHERE garment2.garmentid = garment2order2.garmentid)
AND lounge2order2.lounge2orderid IN
(SELECT lounge2order2.lounge2orderid FROM lounge2order2
WHERE lounge2order2.lounge2orderid = orders2.lounge2orderid)
AND lounge2.loungeid IN
(SELECT lounge2.loungeid FROM lounge2
WHERE lounge2.loungeid = lounge2order2.loungeid)
First of all, try to avoid FROM orders2, branch2, garment2, garment2order2, lounge2, lounge2order2 it must be JOIN ... ON statement. That would help you to debug and us to understand what your logic and table relations are.
And to have any agregate functions like SUM or COUNT working you should set GROUP BY statement.
Since I have no idea what is your database structure like.
Here is my try:
SELECT
orders2.orderid,
orders2.orderdate,
COUNT(orders2.garment2orderid) AS 'no gar orders',
branch2.branchname,
SUM(garment2.hireprice) as 'total gar sold',
SUM(IF(orders2.lounge2orderid IS NULL,1,0)) as 'No of lounge sales',
SUM(lounge2.hirerate)
FROM orders2
LEFT JOIN branch2
ON branch2.branchid = orders2.branchid
LEFT JOIN garment2order2
ON garment2order2.garment2orderid = orders2.garment2orderid
LEFT JOIN garment2
ON garment2.garmentid = garment2order2.garmentid
LEFT JOIN lounge2order2
ON lounge2order2.lounge2orderid = orders2.lounge2orderid
LEFT JOIN lounge2
ON lounge2.loungeid = lounge2order2.loungeid)
WHERE orders2.branchid = 2
AND YEAR(orders2.orderdate)= 2011
GROUP BY orders2.orderid

Hibernate HQL - Use CASE WHEN in COUNT Statement like IF in 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...

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..