SQL Query behavior - mysql

I'm bogged in trying to figure out why query a is returning different records than query b. Both queries have seemingly same purpose yet a is returning 500 and b 3500.
this is query a:
SELECT DISTINCT ODE.OrderBillToID
FROM APTIFY.dbo.vwVwOrderDetailsKGExtended ODE
WHERE ProductID IN (2022, 1393)
AND LTRIM(RTRIM(ODE.OrderStatus)) <> 'Cancelled'
AND LTRIM(RTRIM(ODE.OrderType)) <> 'Cancellation'
AND LTRIM(RTRIM(ODE.cancellationStatus)) <> 'FULLY CANCELLED'
UNION
SELECT DISTINCT ID
FROM APTIFY.dbo.vwPersons WHERE City = 'A'
UNION
SELECT DISTINCT RecordID
FROM APTIFY.dbo.vwTopicCodeLinks WHERE TopicCodeID = 16 AND Value = 'Yes, Please'
query b:
SELECT
APTIFY..vwPersons.ID
FROM
APTIFY..vwPersons
WHERE
( APTIFY..vwPersons.ID IN (
SELECT
vwMeetingRegistrants.ID
FROM
APTIFY.dbo.vwMeetings vwMeetings
INNER JOIN APTIFY.dbo.vwMeetingRegistrants vwMeetingRegistrants
ON vwMeetings.ID=vwMeetingRegistrants.ActualMeetingID WHERE
vwMeetings.ProductID = 2022
)
OR
APTIFY..vwPersons.ID IN (
SELECT
vwMeetingRegistrants.ID
FROM
APTIFY.dbo.vwMeetings vwMeetings
INNER JOIN APTIFY.dbo.vwMeetingRegistrants vwMeetingRegistrants
ON vwMeetings.ID=vwMeetingRegistrants.ActualMeetingID WHERE
vwMeetings.ProductID = 1393
)
OR
APTIFY..vwPersons.City = N'Albany' )
OR
((
APTIFY..vwPersons.ID IN (
SELECT
RecordID
FROM
APTIFY.dbo.vwTopicCodeLinks vwTopicCodeLinks
WHERE
vwTopicCodeLinks.TopicCodeID = 16
)
AND
APTIFY..vwPersons.ID IN (
SELECT
RecordID
FROM
APTIFY.dbo.vwTopicCodeLinks vwTopicCodeLinks
WHERE
vwTopicCodeLinks.Value = N'Yes, Please'
) )
)
vwMeetingsRegistrants from the b query are producing the same records as orderkgdetailsextended from query. I cannot see ANY difference in those queries - which perhaps shows my lack of understanding the query behaviour.
BIG Thanks for any points guys! :)

As it came out, incorrectly structured query is a result of badly configured application, Aptify.

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)

SQL - Divide One Query by Another

I am trying to create a query which returns the workout percentage completion...
Workout Percentage Completion =
((Sum of LogEntries WHERE date = IN list of dates and WorkoutID =1 ) /
(Sum of all set numbers WHERE WorkoutID = 1))
x 100
Below is what I currently have, at the moment it is only returning the result of the first query.
What should I change for the query to run correctly?
SELECT
(
(
SELECT COUNT(LogEntriesID)
FROM LogEntriesTable
LEFT JOIN ExerciseWorkoutJunctionTable
ON ExerciseWorkoutJunctionTable.ExerciseWorkoutJunctionID =
LogEntriesTable.JunctionID
WHERE LogEntriesTable.date IN (
"14-05-2020", "15-05-2020", "16-05-2020", "17-05-2020",
"18-05-2020", "19-05-2020", "20-05-2020"
)
AND ExerciseWorkoutJunctionTable.WorkoutID = 1
) / (
SELECT sum(SetNumber)
FROM ExerciseWorkoutGoalsTable
LEFT JOIN ExerciseWorkoutJunctionTable
ON ExerciseWorkoutJunctionTable.ExerciseWorkoutJunctionID =
ExerciseWorkoutGoalsTable.JunctionID
WHERE ExerciseWorkoutJunctionTable.WorkoutID = 1
)
)
Your first SELECT statement is doing an OUTER JOIN but then you have a WHERE clause that is selecting non-NULL values from the ExerciseWorkoutJunctionTable table, so I suspect you might as well be doing an INNER JOIN.
When you have two queries, try:
SET #sum = (SELECT SUM(SetNumber) etc ....);
SELECT (COUNT(LogEntriesID) * 100 / #sum) AS percentage
FROM etc.
If you are using MySQL >= 8.0 you should be able to use window functions like this which breakdown your query into more readable sections.
with entries as (
SELECT COUNT(LogEntriesID) as log_entry_count
FROM LogEntriesTable as l
LEFT JOIN ExerciseWorkoutJunctionTable as e ON
e.ExerciseWorkoutJunctionID = l.JunctionID
WHERE l.date IN ("14-05-2020","15-05-2020","16-05-2020","17-05-2020","18-05-2020","19-05-2020","20-05-2020")
AND e.WorkoutID = 1
),
sets as (
SELECT sum(SetNumber) as set_sum
FROM ExerciseWorkoutGoalsTable as eg
LEFT JOIN ExerciseWorkoutJunctionTable ej
ON ej.ExerciseWorkoutJunctionID = eg.JunctionID
WHERE ej.WorkoutID = 1
)
select ((select log_entry_count from entries) / (select set_sum from sets)) * 100 as workout_completion_pct

SQL Statement for gouping messages

I have the following code and I'm trying to group the messages
Here is a picture of database table and how the groups should be
and here is the SQL statement
SELECT a.* FROM `user_messages` `a`
JOIN (
SELECT `sender`, MAX(`id`) `last_id` FROM `user_messages` WHERE `receiver` = '1' GROUP BY `sender`
) `b`
ON `a`.`sender` = `b`.`sender` AND `a`.`id` = `b`.`last_id`
WHERE `a`.`receiver` = '1'
ORDER BY `id` DESC
OUTPUT:
I want to get somehow the last record where "receiver" is not my id, but "sender" is and name receiver column as "id" or something.
...so what i want is following result:
id | msg
13852 123
48 Hello!
17 321
Here is a fiddle: http://sqlfiddle.com/#!9/e06d57/3/0
To map my generic answer to your particular use case (using example 1):
SELECT receiver AS id, msg
FROM user_messages outerTable
WHERE NOT EXISTS
( SELECT *
FROM user_messages innerTable
WHERE innerTable.sender = outerTable.sender
AND innerTable.receiver = outerTable.receiver
AND innerTable.added > outerTable.added
)
AND sender = 1
This is a very common use case. There are several ways to write this code. Depending on the SQL engine used, they will be of different speeds.
I will use fairly generic column names. Tweak as needed.
SELECT common_id, msg
FROM myTable outerTable
WHERE NOT EXISTS
( SELECT *
FROM myTable innerTable
WHERE innerTable.common_id = outerTable.common_id
AND innerTable.time > outerTable.time
)
Please note that if there are two rows with identical common_id and time columns, then both will show up in the output. You can replace the > with >= to hide both of those rows.
The other common approach is kind of difficult to make sense of, but here goes. Notice the similarities to the NOT EXISTS approach.
SELECT outerTable.common_id, outerTable.msg
FROM myTable outerTable
LEFT JOIN myTable innerTable
ON innerTable.common_id = outerTable.common_id
AND innerTable.time > outerTable.time
WHERE innerTable.common_id IS NULL
According to your description, you seem to want something like this:
select um.receiver as id, um.msg
from user_messages um
where um.sender = 1 and
um.id = (select max(um2.id)
from user_messages um2
where um2.msg = um.msg and um2.receiver <> 1 and um.sender = 1
);
It doesn't produce the desired output, but that is because the output is inconsistent with the text description.

Pivot result returning duplicate rows

With this query, I am getting result with null values and duplicate Ids...
SELECT QuesId,QuesName,[Ketan Mevada],[Parvej],[Parvez Vahora]
FROM (
SELECT tbl_EvolutionAnswer.QuesId,tbl_QuestionMaster.Name as QuesName,
dbo.Evaluation_Calculation_CourseWise(tbl_EvolutionAnswer.QuesId,34,'Course-Green Course-2045',1065, tbl_EvolutionAnswer.TrainerId ) as Average,
tbl_EvolutionAnswer.TrainerId,
tbl_TrainerMaster.Name as TrName
from tbl_EvolutionAnswer
inner join tbl_TrainerMaster
on tbl_EvolutionAnswer.TrainerId = tbl_TrainerMaster.Id
inner join tbl_QuestionMaster
on tbl_EvolutionAnswer.QuesId = tbl_QuestionMaster.QuestionId
where tbl_EvolutionAnswer.EvolId =34
and tbl_EvolutionAnswer.TrainerId <> 0
and tbl_EvolutionAnswer.CourseId = 'Course-Green Course-2045'
and tbl_EvolutionAnswer.SchID = 1065
) as Books
PIVOT (
MAX(Average) FOR TrName IN ([Ketan Mevada],[Parvej],[Parvez Vahora])
) as Result
I need Following Output
QuesId QuesName Ketan Mevada Parvej Parvez Vohra
122 Did your trainer answer... 2 3 2
123 was your trainer activ.. 1 4 3
It appears that you have a column inside your subquery that is unique and it is causing the grouping of the aggregate function to be skewed. When you are using the PIVOT function, you should only include the columns needed for the PIVOT and the final select list, otherwise you run the risk of the end result being spilt over multiple rows.
It looks like the column you need to remove is tbl_EvolutionAnswer.TrainerId. Making your actual query:
SELECT QuesId,QuesName,[Ketan Mevada],[Parvej],[Parvez Vahora]
FROM
(
SELECT tbl_EvolutionAnswer.QuesId,
tbl_QuestionMaster.Name as QuesName,
dbo.Evaluation_Calculation_CourseWise(tbl_EvolutionAnswer.QuesId,34,'Course-Green Course-2045',1065, tbl_EvolutionAnswer.TrainerId ) as Average,
tbl_TrainerMaster.Name as TrName
from tbl_EvolutionAnswer
inner join tbl_TrainerMaster
on tbl_EvolutionAnswer.TrainerId = tbl_TrainerMaster.Id
inner join tbl_QuestionMaster
on tbl_EvolutionAnswer.QuesId = tbl_QuestionMaster.QuestionId
where tbl_EvolutionAnswer.EvolId =34
and tbl_EvolutionAnswer.TrainerId <> 0
and tbl_EvolutionAnswer.CourseId = 'Course-Green Course-2045'
and tbl_EvolutionAnswer.SchID = 1065
) as Books
PIVOT (
MAX(Average) FOR TrName IN ([Ketan Mevada],[Parvej],[Parvez Vahora])
) as Result

SQL JOIN to count and SUM issue

I know that I am missing something, I have tried several different ways but I am overlooking (or trying too hard). Can someone please tell me where I am wrong on this SQL?
SELECT id,
COUNT(id) AS dupBlocks
FROM tbl_duplicates8 INNER JOIN (
tbl_accounts8,
tbl_delaccounts,
tbl_bad_bots,
tbl_log,
tbl_ipban,
tbl_ipban8
) ON (
tbl_accounts8.SUM(num_attacks) AND
tbl_delaccounts.SUM(noattacks) AND
tbl_bad_bots.COUNT(id) AND
tbl_log.COUNT(id) AND
tbl_ipban.COUNT(txt_ip) AND
tbl_ipban8.COUNT(ip)
);
I did notice this MySQL Join two tables count and sum from second table but it gives me a null on return.
Any help would be appreciated.
To further the question for a better answer, this is what I am currently doing:
$statsresults['newIPBan'] = $db->query("SELECT COUNT(ip) AS newIPBan FROM tbl_ipban8;");
$statsresults['oldIPBan'] = $db->query("SELECT COUNT(txt_ip) AS oldIPBan FROM tbl_ipban;");
$statsresults['log_blocks'] = $db->query("SELECT COUNT(id) AS logBlocks FROM tbl_log;");
$statsresults['badbots'] = $db->query("SELECT COUNT(id) AS badBots FROM tbl_bad_bots;");
$statsresults['del_num_attacks'] = $db->query("SELECT SUM(noattacks) AS deltotalattacks FROM tbl_delaccounts;");
$statsresults['num_attacks'] = $db->query("SELECT SUM(num_attacks) AS totalattacks FROM tbl_accounts8;");
$statsresults['dup_blocks'] = $db->query("SELECT COUNT(id) AS dupBlocks FROM tbl_duplicates8;");
Which will return this:
| ['newIPBan0newIPBan'] = String(6) "289033"
| ['oldIPBan0oldIPBan'] = String(6) "125723"
| ['log_blocks0logBlocks'] = String(4) "6481"
| ['badbots0badBots'] = String(5) "15310"
| ['del_num_attacks0deltotalattacks'] = String(9) "119494860"
| ['num_attacks0totalattacks'] = String(8) "25286478"
| ['dup_blocks0dupBlocks'] = String(6) "179916"
So right now it is calling the database 7 times to get each sum or count. I was hoping to change that to 1 database call and returning the sum of them all.
This is one way that you can combine them:
select (newIPBan + oldIPBan + logBlocks + badBots + deltotalattacks + totalattacks + dupBlocks
) as NumIPs
from (SELECT COUNT(ip) AS oldIPBan FROM tbl_ipban8) ipb8 cross join
(SELECT COUNT(txt_ip) AS newIPBan FROM tbl_ipban) ipb cross join
(SELECT COUNT(id) AS logBlocks FROM tbl_log) l cross join
(SELECT COUNT(id) AS badBots FROM tbl_bad_bots) bb cross join;
(SELECT coalesce(SUM(noattacks), 0) AS deltotalattacks FROM tbl_delaccounts) da cross join
(SELECT coalesce(SUM(num_attacks), 0) AS totalattacks FROM tbl_accounts8) ta cross join
(SELECT COUNT(id) AS dupBlocks FROM tbl_duplicates8) d;
Edit
I would personally create a view for the various metrics, along the following lines:
CREATE VIEW vwMetrics AS
SELECT 'newIPBan' AS Metric, COUNT(ip) AS Value FROM tbl_ipban8
UNION
SELECT 'oldIPBan', COUNT(txt_ip) FROM tbl_ipban
UNION
SELECT 'logBlocks', COUNT(id) FROM tbl_log
UNION
SELECT 'badBots', COUNT(id) AS badBots FROM tbl_bad_bots
UNION
SELECT 'deltotalattacks', SUM(noattacks) FROM tbl_delaccounts
UNION
SELECT 'totalattacks', SUM(num_attacks) FROM tbl_accounts8
UNION
SELECT 'dupBlocks', COUNT(id) FROM tbl_duplicates8;
And then you can aggregate the components:
SELECT SUM(Value) AS TotalOfEverything
FROM vwMetrics;
The benefit of the view is that you can drill down into the components for detail / debugging purposes, rather than arrive at a magical total - there might be some reuse for the view to be had elsewhere in your system.
Prototype Fiddle here
(+Thanks for clarifying the question)