Need help creating SQL query - mysql

I have thus two tables:
CREATE TABLE `workers` (
`id` int(7) NOT NULL AUTO_INCREMENT,
`number` int(7) NOT NULL,
`percent` int(3) NOT NULL,
`order` int(7) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE `data` (
`id` bigint(15) NOT NULL AUTO_INCREMENT,
`workerId` int(7) NOT NULL,
PRIMARY KEY (`id`)
);
I want to return the first worker (order by order ASC) that his number of rows in the table data times percent(from table workers) /100 is smaller than number(from table workers.
I have tried this query:
SELECT workers.id, COUNT(data.id) AS `countOfData`
FROM `workers` as workers, `data` as data
WHERE data.workerId = workers.id
AND workers.percent * `countOfData` < workers.number
LIMIT 1
But I get the error:
#1054 - Unknown column 'countOfData' in 'where clause'

This should work:
SELECT A.id
FROM workers A
LEFT JOIN (SELECT workerId, COUNT(*) AS Quant
FROM data
GROUP BY workerId) B
ON A.id = B.workerId
WHERE (COALESCE(Quant,0) * `percent`)/100 < `number`
ORDER BY `order`
LIMIT 1

You could calculate the number of rows per worker in a subquery. The subquery can be joined to the worker table. If you use a left join, a worker with no data rows will be considered:
select *
from workers w
left join
(
select workerId
, count(*) as cnt
from data
group by
workerId
) d
on w.id = d.workerId
where coalesce(d.cnt, 0) * w.percent / 100 < w.number
order by
w.order
limit 1

Related

Mysql Inner join - how to use value of first table column in join clause

In my "bookings" table, each booking has a number of persons and an "event_time" , which is one of three time slots which is bookable.
In my query I am trying to return how many free seats there are left for each restaurant and time slot (event_time number)
I select restaurants and do an INNER JOIN to include the bookings table, but I would need access to the "number_of_seats_max" column from the restaurants table inside the inner join, which does not seem possible.
Here is fiddle.
Tables:
CREATE TABLE `restaurants` (
`id` int(10) UNSIGNED NOT NULL,
`title` text COLLATE utf8mb4_unicode_ci,
`number_of_seats_max` int(11) DEFAULT NULL
);
CREATE TABLE `bookings` (
`id` int(10) UNSIGNED NOT NULL,
`event_date` timestamp NULL DEFAULT NULL,
`event_time` int(11) NOT NULL,
`number_of_persons` int(11) NOT NULL,
`restaurant_id` int(11) NOT NULL
);
The below query works, but in this case I have hard coded "80" instead of the max seats column ( r.number_of_seats_max ). Thats the column I need to use. If you put r.number_of_seats_max instead, you get the error "unknown column".
SELECT r.title, r.number_of_seats_max, innerquery.free_seats_left,
innerquery.num_persons_booked
FROM restaurants r
INNER JOIN(
select
restaurant_id,
SUM(number_of_persons) as num_persons_booked,
(80 - SUM(number_of_persons)) AS free_seats_left // <-- 80 is hard coded
from bookings
WHERE event_date = '2019-07-18'
group by event_time,restaurant_id
ORDER BY free_seats_left DESC
) as innerquery
ON innerquery.restaurant_id = r.id;
How can I solve it?
Do the subtraction in the main query, not the subquery.
SELECT r.title, innerquery.event_time, r.number_of_seats_max,
r.number_of_seats_max - innerquery.num_persons_booked AS free_seats_left,
innerquery.num_persons_booked
FROM restaurants r
INNER JOIN(
select
restaurant_id,
event_time,
SUM(number_of_persons) as num_persons_booked
from bookings
WHERE event_date = '2019-07-18'
group by event_time,restaurant_id
) as innerquery
ON innerquery.restaurant_id = r.id
ORDER BY free_seats_left DESC
I added event_time to the SELECT list of both the subquery and the main query, so you can show the available seats for each time slot.

How to get NULL at the top of the sort?

Two tables are defined:
CREATE TABLE `users` (
`user_id` mediumint(6) unsigned NOT NULL AUTO_INCREMENT,
`score` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`user_id`)
);
CREATE TABLE `online` (
`user_id` mediumint(6) unsigned NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
PRIMARY KEY (`user_id`)
);
How to combine the tables so that the result would be sorted by the score field from the largest to the smallest but at the top there were records with the value NULL?
This query does not sort the second sample:
(SELECT * FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NULL)
UNION
(SELECT * FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NOT NULL ORDER BY `score` DESC)
Use two keys in the sort:
SELECT *
FROM `online` o JOIN
`users`
USING (user_id)
ORDER BY (`score` IS NULL) DESC, Score DESC;
MySQL treats booleans as numbers in a numeric context, with "1" for true and "0" for false. So, DESC puts the true values first.
Incidentally, your version would look like it works if you used UNION ALL rather than UNION. However, it is not guaranteed that the results are in any particular order unless you explicitly have an ORDER BY.
The UNION incurs overhead for removing duplicates and in doing so rearranges the data.
Try:
select * from online join users using (user_id) order by ifnull(score, 10) desc;
You can use order by Nulls Last in the end of your sql to show nulls on the first.
You can try below -
select * from
(
SELECT *,1 as ord FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NULL
UNION
SELECT *,2 FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NOT NULL
)A ORDER BY ord asc,`score` DESC

How to count and order by max by other table in MySQL?

I'm having two table:
CREATE TABLE `apps` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(191) NOT NULL COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
;
CREATE TABLE `downloads_stats` (
`app_id` INT(10) UNSIGNED NOT NULL,
`date` DATE NOT NULL,
`downloads` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`app_id`, `date`),
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
First is the list of my apps, the other stores the download number of each app. What is the best way to get 10 most downloaded apps ? I can do simple JOIN and then SUM and ORDER BY but is it the most efficient way?
Yes, I'd use LEFT JOIN with GROUP BY, e.g.:
SELECT a.id, a.name, SUM(ds.downloads) AS 'downloads'
FROM apps a LEFT JOIN downloads_stats ds ON a.id = ds.app_id
GROUP BY a.id, a.name
ORDER BY SUM(ds.downloads) DESC
LIMIT 10;
If you just want the app_id then JOIN is not needed, e.g.:
SELECT app_id, SUM(downloads) as 'downloaded'
FROM downloads_stats
GROUP BY app_id
ORDER BY SUM(downloads) DESC
Yes , you can use LEFT JOIN with GROUP BY
SELECT a.id , a.name , SUM(ds.downloads) AS 'downloads'
FROM apps a LEFT JOIN downloads_stats ds ON a.id = ds.app_id
GROUP BY a.id , a.name
ORDER BY downloads DESC
LIMIT 10;

Writing a JOIN query with two counts from joined table

I've spent a few hours fighting with this, but I can't get the counts to work. Hopefully someone can help?!
I have a project table and task table, linked on the project_id. I can get the project_id, project_name, and the status_id with the query below:
SELECT
a.project_id,
a.project_name,
b.status_id
FROM project_list as a
INNER JOIN task_list as b
ON a.project_id=b.project_id
I'd like to select a single record for each project and add two count fields based on the status_id. In pseudo code:
SELECT
a.project_id,
a.project_name,
(SELECT COUNT(*) FROM task_list WHERE status_id < 3) as not_completed,
(SELECT COUNT(*) FROM task_list WHERE status_id = 3) as completed
FROM project_list as a
INNER JOIN task_list as b
ON a.project_id=b.project_id
GROUP BY project_id
My create table scripts are below:
CREATE TABLE `project_list` (
`project_id` int(11) NOT NULL AUTO_INCREMENT,
`topic_id` int(11) DEFAULT NULL,
`project_name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`project_id`)
)
CREATE TABLE `task_list` (
`task_id` int(11) NOT NULL AUTO_INCREMENT,
`project_id` int(11) DEFAULT NULL,
`task_name` varchar(45) DEFAULT NULL,
`status_id` int(11) DEFAULT '0',
PRIMARY KEY (`task_id`)
)
Any help is much appreciated. Thanks!
EDIT: ANSWER:
SELECT
a.project_id,
project_name,
SUM(status_id != 3) AS not_completed,
SUM(status_id = 3) AS completed,
SUM(status_id IS NOT NULL) as total
FROM tasks.project_list as a
INNER JOIN tasks.task_list as b
ON a.project_id=b.project_id
GROUP BY a.project_id
The problem is that in your subqueries you are counting all the rows in the whole table rather than just the rows that have the correct project_id. You could fix this by modifying the WHERE clause in each of your subqueries.
(SELECT COUNT(*)
FROM task_list AS c
WHERE c.status_id < 3
AND a.project_id = c.project_id)
However a simpler approach is to use SUM with a boolean condition instead of COUNT to count the rows that match the condition:
SELECT
a.project_id,
a.project_name,
SUM(b.status_id < 3) AS not_completed,
SUM(b.status_id = 3) AS completed,
FROM project_list as a
INNER JOIN task_list as b
ON a.project_id = b.project_id
GROUP BY project_id
This works because TRUE evaluates to 1 and FALSE evaluates to 0.

MySQL joins involving aggregate data

What I require is a SQL query which can report on data from aggregate and singular tables. The current database I have is as follows.
CREATE TABLE IF NOT EXISTS `faults_days` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`employee_id` int(11) NOT NULL,
`day_date` date NOT NULL,
`actioned_calls_out` int(11) NOT NULL,
`actioned_calls_in` int(11) NOT NULL,
`actioned_tickets` int(11) NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_departments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`team_id` int(11) NOT NULL,
`name` varchar(127) NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_qos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`qos_date` datetime NOT NULL,
`employee_id` int(11) NOT NULL,
`score` double NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_teams` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`department_id` int(11) NOT NULL,
`name` varchar(40) NOT NULL,
)
A row in Day tracks a single employee's performance for a single day (number of calls taken, number of tickets actioned). A Qos is a measure of an employee's quality on a day (there can be more than one Qos per day - what I need to obtain is the average score). Also, a Qos can be performed on a day where the employee has no performance entry in the database, and this will still need to be shown on the report.
The required end result is 4 reports, which show the employee performance grouped by different columns. A breakdown of a single employee's performance per day, an employee's total performance over a period of time, a team's performance over a period of time, and a whole department's performance over a period of time.
My problem, is that my current queries are a little convoluted, and require two separate queries for the Day data, and the Qos data. My PHP application then combines the data before outputting the report. What I would like, is a single query which returns both total performance, and average quality scores.
The current queries I have to show employee performances are:
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name` ,
SUM( `Day`.`actioned_calls_in` ) + SUM( `Day`.`actioned_calls_out` ) ,
SUM( `Day`.`actioned_tickets` )
FROM
`faults_days` AS `Day`
JOIN
`faults_employees` AS `Employee` ON `Day`.`employee_id` = `Employee`.`id`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
WHERE
`Day`.`day_date` >= '2011-06-01'
AND `Day`.`day_date` <= '2011-06-07'
GROUP BY `Employee`.`id`
WITH ROLLUP
and
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name` ,
COUNT( `Qos`.`score` ) ,
AVG( `Qos`.`score` )
FROM
`faults_qos` AS `Qos`
JOIN
`faults_employees` AS `Employee` ON `Qos`.`employee_id` = `Employee`.`id`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
WHERE
`Qos`.`qos_date` >= '2011-06-01'
AND `Qos`.`qos_date` <= '2011-06-07'
GROUP BY `Employee`.`id`
WITH ROLLUP
I have also tried simply joining the Qos table, but because it returns multiple rows it messes up the SUM() totals, and also has problems due to the missing FULL OUTER JOIN functionality.
EDIT:
I've made some small progress with this. It looks like using subqueries is the way to go, but everything I'm doing is pure guesswork. Here's what I've got so far, its only showing a row if there's an entry in both the Day and Qos tables, which is not what I want, and I've no idea how to expand it to include the various groupings described above.
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name`,
`Day`.`Calls`,
`Day`.`Tickets`,
`Qos`.`NumQos`,
`Qos`.`Score`
FROM `faults_employees` AS `Employee`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
JOIN
(SELECT
`Day`.`employee_id` AS `eid`,
SUM(`Day`.`actioned_calls_in`) + SUM(`Day`.`actioned_calls_out`) AS `Calls`,
SUM(`Day`.`actioned_tickets`) AS `Tickets`
FROM `faults_days` AS `Day`
WHERE
`Day`.`day_date` = '2011-03-02'
GROUP BY `Day`.`employee_id`
) AS `Day`
ON `Day`.`eid` = `Employee`.`id`
JOIN
(SELECT
`Qos`.`employee_id` AS qid,
COUNT(`Qos`.`id`) AS `NumQos`,
AVG(`Qos`.`score`) AS `Score`
FROM `faults_qos` AS `Qos`
WHERE
`Qos`.`qos_date` = '2011-03-02'
GROUP BY `Qos`.`employee_id`
) AS `Qos`
ON `Qos`.`qid` = `Employee`.`id`
GROUP BY `Employee`.`id`
You do want the left joins on the fault_qos and fault_days subqueries. That's what will give you a result even if there isn't a corresponding row in one or both. A left join says that the value is necessary in the table(s) to the left of the join that are involved in the join but not the one on the right. I haven't tested this, and it's late, so I might not be thinking clearly, but if you change your query to this it should work:
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name`,
`Day`.`Calls`,
`Day`.`Tickets`,
`Qos`.`NumQos`,
`Qos`.`Score`
FROM `faults_employees` AS `Employee`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
LEFT JOIN
(SELECT
`Day`.`employee_id` AS `eid`,
SUM(`Day`.`actioned_calls_in`) + SUM(`Day`.`actioned_calls_out`) AS `Calls`,
SUM(`Day`.`actioned_tickets`) AS `Tickets`
FROM `faults_days` AS `Day`
WHERE
`Day`.`day_date` = '2011-03-02'
GROUP BY `Day`.`employee_id`
) AS `Day`
ON `Day`.`eid` = `Employee`.`id`
LEFT JOIN
(SELECT
`Qos`.`employee_id` AS qid,
COUNT(`Qos`.`id`) AS `NumQos`,
AVG(`Qos`.`score`) AS `Score`
FROM `faults_qos` AS `Qos`
WHERE
`Qos`.`qos_date` = '2011-03-02'
GROUP BY `Qos`.`employee_id`
) AS `Qos`
ON `Qos`.`qid` = `Employee`.`id`
GROUP BY `Employee`.`id`