How to get votes with results with percent calculating - mysql

In my Laravel 5.7/mysql 5 app I have a table with votes results:
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`vote_item_id` INT(10) UNSIGNED NOT NULL,
`user_id` INT(10) UNSIGNED NOT NULL,
`is_correct` TINYINT(1) NOT NULL DEFAULT '0',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
where boolean is_correct field is if answer was correct or incorrect.
I need to get data on percents of correct answers.
Creating such request
$voteItemUsersResultsCorrect = VoteItemUsersResult:: // Grouped by vote name
getByIsCorrect(true)->
getByCreatedAt($filter_voted_at_from, ' > ')->
getByCreatedAt($filter_voted_at_till, ' <= ')->
getByUserId($filterSelectedUsers)->
getByVote($filterSelectedVotes)->
getByVoteCategories($filterSelectedVoteCategories)->
getByVoteIsQuiz(true)->
getByVoteStatus('A')->
select( \DB::raw('count(vote_item_users_result.id) as count, votes.id, votes.name as vote_name') )->
orderBy('vote_name', 'asc')->
groupBy( 'votes.id' )->
groupBy( 'vote_name' )->
join(\DB::raw('vote_items'), \DB::raw('vote_items.id'), '=', \DB::raw('vote_item_users_result.vote_item_id'))->
join(\DB::raw('votes '), \DB::raw('votes.id'), '=', \DB::raw('vote_items.vote_id'))->
get();
I can get number of correct votes with sql request.
SELECT count(vote_item_users_result.id) AS count, votes.id, votes.name AS vote_name
FROM `vote_item_users_result`
INNER JOIN vote_items on vote_items.id = vote_item_users_result.vote_item_id
INNER JOIN votes on votes.id = vote_items.vote_id
WHERE `vote_item_users_result`.`is_correct` = '1' AND vote_item_users_result.created_at > '2018-08-01' AND vote_item_users_result.created_at <= '2018-09-22 23:59:59' AND `votes`.`is_quiz` = '1' AND `votes`.`status` = 'A'
GROUP BY `votes`.`id`, `vote_name`
ORDER BY `vote_name` asc
I know a way to get 2nd similar request with is_correct = '0' and on php side to combine results with percent calculating,
but I wonder if that could be done with eloquent in 1 request?
If yes, how ?
Thanks!

One correct raw MySQL would use conditional aggregation:
SELECT
v.id,
100.0 * COUNT(CASE WHEN vir.is_correct = 1 THEN 1 END) / COUNT(*) AS pct_correct,
100.0 * COUNT(CASE WHEN vir.is_correct = 0 THEN 1 END) / COUNT(*) AS pct_incorrect
FROM votes v
INNER JOIN vote_items vi
ON v.id = vi.vote_id
INNER JOIN vote_item_users_result vir
ON vi.id = vir.vote_item_id
WHERE
vir.created_at > '2018-08-01' AND vir.created_at < '2018-09-23' AND
v.is_quiz = '1' AND
v.status = 'A'
GROUP BY
v.id;
Now we can try writing Laravel code for this:
DB::table('vote')
->select('vote.id',
DB::raw('100.0 * COUNT(CASE WHEN vir.is_correct = 1 THEN 1 END) / COUNT(*) AS pct_correct'),
DB::raw('100.0 * COUNT(CASE WHEN vir.is_correct = 0 THEN 1 END) / COUNT(*) AS pct_incorrect'))
->join('vote_items', 'votes.id', '=', 'vote_items.vote_id')
->join('vote_item_users_result', 'vote_items.id', '=', 'vote_item_users_result.vote_item_id ')
->where([
['vote_item_users_result.created_at', '>', '2018-08-01'],
['vote_item_users_result.created_at', '<', '2018-09-23'],
['vote.is_quiz', '=', '1'],
['vote.status', '=', 'A']
])
->groupBy('vote.id')
->get();

Related

SUMs with GROUP WITH ROLLUP - query too slow

My Query is very slow returning results. It takes more than 30s (depending a lot on the use of the server). The table has some records.
I cannot divide this table for other reasons. I added this to the query `TYPE` IN (1,3)' and the response time has improved significantly. But it remains very slow.
It is worth mentioning that I have already add an INDEX for the PRODUCT, TYPE, STATUS columns.
Question: How can I speed up the query response time without having to divide it by many tabs and have to use the INNER JOIN?
Other Useful Information:
Records: 1,436,004
Record length: 240 Bytes
Records size: 291 Bytes
My Query:
SELECT `PRODUCT`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '11/2020' AND `TYPE` = 1) AS `SUMT1_1`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '12/2020' AND `TYPE` = 1) AS `SUMT1_2`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '01/2021' AND `TYPE` = 1) AS `SUMT1_3`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '02/2021' AND `TYPE` = 1) AS `SUMT1_4`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '03/2021' AND `TYPE` = 1) AS `SUMT1_5`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '04/2021' AND `TYPE` = 1) AS `SUMT1_6`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '11/2020' AND `TYPE` = 3) AS `SUMT3_1`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '12/2020' AND `TYPE` = 3) AS `SUMT3_2`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '01/2021' AND `TYPE` = 3) AS `SUMT3_3`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '02/2021' AND `TYPE` = 3) AS `SUMT3_4`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '03/2021' AND `TYPE` = 3) AS `SUMT3_5`,
SUM(DATE_FORMAT(`REG_DATE`,'%m/%Y') = '04/2021' AND `TYPE` = 3) AS `SUMT3_6`
FROM `MY_TABLE` WHERE
`TYPE` IN (1,3) AND
`STATUS` IN('AVAILABLE','IN PROCESS')
GROUP BY `PRODUCT` ASC
WITH ROLLUP HAVING (`SUMT1_1`+`SUMT1_2`+`SUMT1_3`+`SUMT1_4`+`SUMT1_5`+`SUMT1_6`+`SUMT3_1`+`SUMT3_2`+`SUMT3_3`+`SUMT3_4`+`SUMT3_5`+`SUMT3_6`) >= 1
With an index on some combination of (status,type,reg_date), I would write that query this way:
SELECT product
, DATE_FORMAT(reg_date,'%Y-%m') reg_ym
, type
, COUNT(*) subtotal
FROM my_table
WHERE type IN (1,3)
AND status IN('AVAILABLE','IN PROCESS')
AND reg_date >= '2020-11-01'
AND reg_date < '2020-05-01'
GROUP
BY product
, reg_ym
, type
ORDER
BY product
, reg_ym
, type
...and handle any remaining aspects of the problem in application code
The best solution was given by #Akina (in the comments), and reduced the response time from 30s to 3s (average).
Adding the condition:
`REG_DATE` BETWEEN '20220-11-01' AND '2021-04-30'
But to fit better with my php code i prefer to use:
DATE_FORMAT(`REG_DATE`,'%m/%Y') IN ('11/2020','12/2020','01/2021','02/2021','03/2021','04/2021')

Is there any way to optimize this MySQL query when merging two query?

I was trying to get the result with product_id, stock, new_cost
SELECT inventory_logs.product_id, ROUND(SUM(inventory_logs.qty + 0), 4) AS stock
FROM products
JOIN inventory_logs on products.id = inventory_logs.product_id
WHERE inventory_logs.`type` = 'as'
AND inventory_logs.`created_at` between '1970-01-01' and '2021-02-22 23:59:59'
AND inventory_logs.`branch_id` = 1
AND inventory_logs.`deleted_at` IS NULL AND products.deleted_at IS NULL
GROUP BY inventory_logs.product_id HAVING stock > 0
above that is the query for getting stock,
duration for query is about 0.031 sec
SELECT inventory_logs.product_id, inventory_logs.new_cost
FROM inventory_logs JOIN(
SELECT product_id, MAX(created_at) AS created_at
FROM inventory_logs
WHERE created_at <= '2021-02-22 23:59:59' AND deleted_at IS NULL
AND `type` = 'as'
AND inventory_logs.`branch_id` = 1
GROUP BY product_id
) i2 ON (inventory_logs.product_id = i2.product_id AND inventory_logs.created_at = i2.created_at)
WHERE `type` = 'as'
AND inventory_logs.`branch_id` = 1
above is the query for getting new_cost, duration for query is also about 0.031 sec
But when I try to merge the above two query into one, the duration of query is 26.016 sec
The query is
SELECT inventory_logs.product_id, ROUND(SUM(inventory_logs.qty = 0), 4) AS stock, sub.new_cost
FROM products
JOIN inventory_logs ON products.id = inventory_logs.product_id
JOIN (
SELECT inventory_logs.product_id, inventory_logs.new_cost
FROM inventory_logs JOIN (
SELECT product_id, MAX(created_at) AS created_at
FROM inventory_logs
WHERE created_at <= '2021-02-22 23:59:59' AND deleted_at IS NULL
AND `type` = 'as'
AND inventory_logs.`branch_id` = 1
GROUP BY product_id
) i2 ON (inventory_logs.product_id = i2.product_id AND inventory_logs.vreated_at
= i2.created_at)
WHERE `type` = 'as'
AND inventory_logs.`branch_id` = 1
) sub ON products.id = sub.product_id
WHERE inventory_logs.`type` = 'as'
AND inventory_logs.`created_at` BETWEEN '1970-01-01' AND '2021-02-22 23:59:59'
AND inventory_logs.`branch_id` = 1
AND inventory_logs.`deleted_at` IS NULL
AND products.deleted_at IS NULL
GROUP BY inventory_logs.product_id HAVING stock > 0
Is there any ways to optimize the above query to make it take lesser time?

How to write condition for subquery alias if having null value

Here is my query,
SELECT
`h`.`hotel_id`,
(
SELECT COUNT(room_id)
FROM
`abserve_hotel_rooms` AS `rm`
WHERE
`rm`.`adults_count` >= "1" AND `rm`.`room_count` >= "1" AND "Available" = IF(
check_in_time = '2016-03-15',
'Unavailable',
(
IF(
'2016-03-15' > check_in_time,
(
IF(
'2016-03-15' < check_out_time,
'Unavailable',
'Available'
)
),
(
IF(
'2016-03-22' > check_in_time,
'Unavailable',
'Available'
)
)
)
)
) AND `room_prize` BETWEEN '174' AND '600' AND `rm`.`hotel_id` = `h`.`hotel_id`
) AS `avail_room_count`,
(
SELECT MIN(room_prize)
FROM
`abserve_hotel_rooms` AS `rm`
WHERE
`rm`.`adults_count` >= "1" AND `rm`.`room_count` >= "1" AND "Available" = IF(
check_in_time = '2016-03-15',
'Unavailable',
(
IF(
'2016-03-15' > check_in_time,
(
IF(
'2016-03-15' < check_out_time,
'Unavailable',
'Available'
)
),
(
IF(
'2016-03-22' > check_in_time,
'Unavailable',
'Available'
)
)
)
)
) AND `room_prize` BETWEEN '174' AND '600' AND `rm`.`hotel_id` = `h`.`hotel_id`
) AS `min_val`
FROM
`abserve_hotels` AS `h`
WHERE
1 AND `city` = "madurai" AND `country` = "india"
It totally return one column values from my table abserve_hotels which is hotel_id with extra two alias columns such as avail_room_count and min_val..
And I wrote those in a subquery..
Here I have to check a condition WHERE min_val IS NOT NULL .i.e; if min_val having NULL value I have to restrict it
How can I do this..
And this is my table
hotel_id avail_room_count min_val
1 0 NULL
2 0 NULL
Here I need to restrict these NULL values..
Someone please help me ..
Add a HAVING clause at the end:
HAVING min_val IS NOT NULL
The new query after WHERE looks like:
WHERE
1 AND `city` = "madurai" AND `country` = "india"
HAVING min_val IS NOT NULL
Your query is overly complex and can be much simplified:
The two correlated sub queries are exactly the same, except for the SELECT list (MIN versus COUNT), so they could be combined into one;
The aggregation done by the sub query can be done in the main query;
The condition for checking availability can be written much shorter.
In fact, you can do all of what you need with the following query:
SELECT h.hotel_id,
COUNT(rm.room_id) as avail_room_count,
MIN(rm.room_prize) AS min_val
FROM abserve_hotels AS h
INNER JOIN abserve_hotel_rooms AS rm
ON rm.hotel_id = h.hotel_id
WHERE h.city = "madurai"
AND h.country = "india"
AND rm.adults_count >= 1
AND rm.room_count >= 1
AND rm.room_prize BETWEEN 174 AND 600
AND ( rm.check_in_time >= '2016-03-22'
OR rm.check_out_time <= '2016-03-15'
OR rm.check_in_time IS NULL)
GROUP BY h.hotel_id
Because the INNER JOIN requires at least one match, you can already be sure that min_val will never be NULL.
The check for availability is just as simple as:
( rm.check_in_time >= '2016-03-22'
OR rm.check_out_time <= '2016-03-15'
OR rm.check_in_time IS NULL)
The three parts of that condition mean:
The reservation for this room is future and does not overlap with this week;
The reservation for this room is in the past, the room is free today at the latest;
The room has no known reservation.
In all three cases the room is available for reservation for the concerned week.

Mysql return null if subquery returns null

Hy guys, sometimes my subquery return null which is ok, it should return null, but in those cases i would like my "parent select" to return null.
Is that possible?
And if yes, then how?
Heres the code:
SELECT
`company`.`companyID`,
`company`.`companyName`,
`company`.`companyName`,
`company`.`companyEmail`,
`company`.`contactEmail`,
`company`.`companyTel`,
(
SELECT
`package_map`.`szekhely_endDate`
FROM
`package_map`
WHERE
`package_map`.`companyID` = `company`.`companyID`
AND
`package_map`.`active` = 1
AND
`package_map`.`szekhely_endDate` > NOW()
ORDER BY
`package_map`.`szekhely_endDate` DESC
LIMIT 1
) as endDate,
CASE
WHEN endDate = NULL
FROM
`company`
WHERE
`company`.`companyBase` = 'some address'
AND
`company`.`szekhely_check_out` = 0
Use an ordinary INNER JOIN between the two tables. If there's no matching rows in the package_map table, there won't be a row in the result. To get the latest endDate, use the MAX() function.
SELECT
`company`.`companyID`,
`company`.`companyName`,
`company`.`companyName`,
`company`.`companyEmail`,
`company`.`contactEmail`,
`company`.`companyTel`,
MAX(package_map.szekhely_endDate) AS endDate
FROM company
INNER JOIN package_map ON `package_map`.`companyID` = `company`.`companyID`
WHERE
`company`.`companyBase` = 'some address'
AND
`company`.`szekhely_check_out` = 0
AND
`package_map`.`active` = 1
AND
`package_map`.`szekhely_endDate` > NOW()
GROUP BY `company`.`companyID`

same table count union

SELECT COUNT(*) as totalHappenings FROM `happenings` WHERE `userId` = ?
UNION
SELECT COUNT(*) as xHappenings FROM `happenings` WHERE `userId` = ? AND `destinationObjectType` = \'2\'
UNION
SELECT COUNT(*) as yHappenings FROM `happenings` WHERE `userId` = ? AND `destinationObjectType` = \'1\'
Since it's the same table, and I don't wanna pass through 3 times the userId parameter how can I solve this the best way?
SELECT
COUNT(*) AS totalHappenings,
SUM(CASE WHEN `destinationObjectType` = \'2\' THEN 1 ELSE 0 END) AS xHappenings,
SUM(CASE WHEN `destinationObjectType` = \'1\' THEN 1 ELSE 0 END) AS yHappenings
FROM `happendings`
WHERE `userId` = ?
Result:
totalHappenings xHappenings yHappenings
24 10 14
You can do this with if statements inside select clause:
SELECT
COUNT(userId) as totalHappenings,
SUM(IF(`destinationObjectType`='2',1,0) as xHappenings,
SUM(IF(`destinationObjectType`='1',1,0) as yHappenings
FROM `happenings`
WHERE `userId` = ?
This will surely return your results in 3 columns. Your original query was returning in 3 rows but I think that is not a problem.
try the shortest way:
SELECT COUNT(*) as totalHappenings, SUM(`destinationObjectType` = \'2\') AS xHappenings, SUM(`destinationObjectType` = \'1\') AS yHappenings FROM `happenings` WHERE `userId` = ?
comparision inside SUM returns true or false (1 or 0) so there is no need for IF or CASE statements