Sum total points for multiple tables in MySQL - mysql

I'd like to find the sum of a column in a single query given joins between multiple tables.
I have a table of Activities, and a table that maps Users performing an Activity, as well as a table mapping Teams to performed Activities. Both Users and Teams can perform the same activity multiple times.
Each activity is worth a set number of points, and I'd like to know the total number of points for a given user by totalling their activities with their team's activities.
I've tried various combinations of joins between the three tables, but cannot work out the correct query to total the points for a given user.
The following SQL will create a minimal version of this setup:
CREATE TABLE `activity` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT '',
`points` INT(10) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=INNODB;
CREATE TABLE `team_action` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`activity_id` INT(11) UNSIGNED NOT NULL,
`date` DATETIME NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `team_action_ibfk_1` FOREIGN KEY (`activity_id`) REFERENCES `activity` (`id`)
) ENGINE=INNODB;
CREATE TABLE `user_action` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`activity_id` INT(11) UNSIGNED NOT NULL,
`date` DATETIME NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `user_action_ibfk_1` FOREIGN KEY (`activity_id`) REFERENCES `activity` (`id`)
) ENGINE=INNODB;
INSERT INTO `activity` (`id`, `name`, `points`)
VALUES (1,'Running',10), (2,'Swimming',20), (3,'Hiking',30), (4,'Cycling',40);
INSERT INTO `team_action` (`id`, `activity_id`, `date`)
VALUES (1,2,'2012-05-22 14:32:31'), (2,4,'2012-05-22 14:32:36');
INSERT INTO `user_action` (`id`, `activity_id`, `date`)
VALUES (1,1,'2012-05-22 14:32:08'), (2,1,'2012-05-22 14:32:18'), (3,3,'2012-05-22 14:32:23');

It is not clear from the table definitions how users are related to teams (i.e. for a user, how do you know which is "their" team?) But I think the key to summing the points will be to use SUM on the result of UNION ALL in a subquery.
Something along the lines of:
SELECT SUM(points) AS total
FROM
(SELECT points
FROM team_action JOIN activity ON(activity.id = team_action.activity_id)
WHERE team_action.id = my_team
UNION ALL
SELECT points
FROM user_action JOIN activity ON(activity.id = user_action.activity_id)
WHERE user_action.id = my_user) me_and_team

Related

Mysql binary tree select query optimization

I have a accounts table. every account creation I am pushing treeRight id and treeLeft id into account_device_tree table.
Now I have more than 10M accounts under first parent account. when I select all the subaccouts it is taking more than a min to execute.
my query is given below
select *
FROM
accounts acc
JOIN
account_device_tree ON acc.tree_id = account_device_tree.tree_id
WHERE
(acc.account_id = 1 OR (account_device_tree.tree_left >= 1 AND account_device_tree.tree_right <= 748534))
I need to optimize as much as possible.
schema of account_device_tree
CREATE TABLE `account_device_tree` (
`tree_id` int(11) NOT NULL AUTO_INCREMENT,
`tree_left` int(11) NOT NULL,
`tree_right` int(11) NOT NULL,
PRIMARY KEY (`tree_id`),
KEY `tree_left` (`tree_left`),
KEY `tree_right` (`tree_right`)
) ENGINE=InnoDB AUTO_INCREMENT=388173 DEFAULT CHARSET=latin1
accounts table Schema
CREATE TABLE `accounts` (
`account_id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL,
`name` varchar(64) NOT NULL,
`tree_id` int(11) NOT NULL,
PRIMARY KEY (`account_id`),
UNIQUE KEY `tree_id` (`tree_id`),
KEY `name` (`name`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=389739 DEFAULT CHARSET=latin1
Hard to suggest something without table structures and query plan..
But rewritting the query into UNION ALL instead of using OR tends to optimize beter assuming the correct indexes are in the table.
select *
FROM
accounts acc
JOIN
account_user_device_tree ON acc.tree_id = account_user_device_tree.tree_id
WHERE
acc.account_id = 1
UNION ALL
select *
FROM
accounts acc
JOIN
account_user_device_tree ON acc.tree_id = account_user_device_tree.tree_id
WHERE
account_user_device_tree.tree_left >= 1
AND
account_user_device_tree.tree_right <= 748534

How to get two sums from two tables in one output?

I have two tables:
Receive_Amount_Details for crediting amount from the construction site owner, and
SitewiseEmployee for debiting the amount to pay the laborer.
For every single Date, which is present in both tables, I want to:
sum all of the Amount_Received from Receive_Amount_Details as Total_Receive_Amount_from_siteowner, and
sum all of the Amount from SitewiseEmployee as Total_Amount_Payed_to_Labour
column on output table.
Both tables have the Date column, but I want a single Date column in the output.
If any single day amount is not received and labour was paid, it should be in the output table, and also if any single day amount was received but not paid for labour, then also it needs to be present in the output table.
CREATE TABLE `Receive_Amount_Details` (
`Id` int(10) NOT NULL AUTO_INCREMENT,
`SiteId` int(5) NOT NULL,
`Amount_Received` int(10) NOT NULL,
`Date` date NOT NULL,
PRIMARY KEY (`Id`),
KEY `SiteId` (`SiteId`),
CONSTRAINT `Receive_Amount_Details_ibfk_1` FOREIGN KEY (`SiteId`)
REFERENCES `SiteList` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
and
CREATE TABLE `SitewiseEmployee` (
`Id` int(10) NOT NULL AUTO_INCREMENT,
`SiteId` int(5) NOT NULL,
`EmployeeId` int(10) NOT NULL,
`Amount` varchar(10) DEFAULT NULL,
`Date` date NOT NULL,
PRIMARY KEY (`Id`),
KEY `SiteId` (`SiteId`),
KEY `EmployeeId` (`EmployeeId`),
CONSTRAINT `SitewiseEmployee_ibfk_1` FOREIGN KEY (`SiteId`)
REFERENCES `SiteList` (`Id`),
CONSTRAINT `SitewiseEmployee_ibfk_2` FOREIGN KEY (`EmployeeId`)
REFERENCES `Employee` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
Assuming that you may have dates in the one table that are not present in the other, and vice versa, you would need to perform a full outer join. MySql does not have such a join type, but you can achieve it with a union:
select `Date`,
sum(Amount_Received) as Sum_Amount_Received,
sum(Amount) as Sum_Amount
from (
select `Date`, Amount_Received, 0 as Amount
from Receive_Amount_Details
union
select `Date`, 0, Amount
from SitewiseEmployee
) as dates
group by `Date`;

how to count same rating from field in sql

I have a problem counting ratings in SQL. This is what my data looks like:
data
CREATE TABLE `restaurant` (
`id_restaurant` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id_restaurant`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
insert into `restaurant`(`id_restaurant`,`name`) values (1,'Mc Donald');
insert into `restaurant`(`id_restaurant`,`name`) values (2,'KFC');
CREATE TABLE `user` (
`id_user` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id_user`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
insert into `user`(`id_user`,`userName`) values (1,'Audey');
CREATE TABLE `factors` (
`factor_id` int(11) NOT NULL AUTO_INCREMENT,
`factor_clean` int(11) NOT NULL DEFAULT '0',
`factor_delicious` int(11) NOT NULL DEFAULT '0',
`id_restaurant` int(11) DEFAULT NULL,
`id_user` int(11) DEFAULT NULL,
PRIMARY KEY (`factor_id`),
KEY `id_restaurant` (`id_restaurant`),
KEY `id_user` (`id_user`),
CONSTRAINT `factors_ibfk_1` FOREIGN KEY (`id_restaurant`) REFERENCES `restaurant` (`id_restaurant`),
CONSTRAINT `factors_ibfk_2` FOREIGN KEY (`id_user`) REFERENCES `user` (`id_user`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
insert into `factors`(`factor_id`,`factor_clean`,`factor_delicious`,`id_restaurant`,`id_user`) values (1,1,5,1,1);
insert into `factors`(`factor_id`,`factor_clean`,`factor_delicious`,`id_restaurant`,`id_user`) values (2,0,5,1,1);
insert into `factors`(`factor_id`,`factor_clean`,`factor_delicious`,`id_restaurant`,`id_user`) values (3,1,5,1,1);
insert into `factors`(`factor_id`,`factor_clean`,`factor_delicious`,`id_restaurant`,`id_user`) values (4,3,3,1,1);
And the result should be like this, Show all ratings (1,2,3,4,5) and their count from the fields rating_clean, rating_delicious, and rating_clean
Thanks for your help.
but the result i get
SELECT COUNT(`factor_clean`+`factor_delicious`),'1' AS rating_1 FROM `factors` WHERE 1 GROUP BY `id_restaurant`
result not should like this
the result should not like that,
my question is, how to select just factor_clean and factor_delicious where factor_clean =1 and factor_delicious = 1
Use union all to unpivot the data and then aggregate:
select id_restaurant, rating, count(*)
from ((select r.id_restaurant, r.rating_clean as rating, r.date
from ratings r
) union all
(select r.id_restaurant, r.rating_delicious, r.date
from ratings r
) union all
(select r.id_restaurant, r.rating_clean2, r.date
from ratings r
)
) r
group by id_restaurant, rating
order by id_restaurant, rating;
For example this is solution for table with colums rating_delicious and rating_clean (only one!):
First of all you should create additional table, I called it factors:
CREATE TABLE `factors` (
`factor_id` int(11) NOT NULL AUTO_INCREMENT,
`factor_clean` int(11) NOT NULL DEFAULT '0',
`factor_delicious` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`factor_id`)
)
Next add two records:
INSERT INTO `factors` (`factor_id`, `factor_clean`, `factor_delicious`) VALUES (NULL, '1', '0'), (NULL, '0', '1');
Now you can join this tables and get results:
SELECT x.id_restaurant
, (x.rating_clean * f.factor_clean) + (x.rating_delicious * f.factor_delicious) AS rating
, count(*)
FROM your_table x
JOIN factors f
WHERE 1
GROUP
BY x.id_restaurant
, rating
In order to use next colum (rating_third), you should and column factor_third to factors, insert new row with 1 in this column and finally add something like your_table.rating_third*factors.factor_third to sum in SELECT

SQL 'merging' columns from result sets into one result set

I've been pulling my hair about how to write a particular view within the constraints of MySQL.
The following tables and columns are of importance:
CREATE TABLE `invoices` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
PRIMARY KEY (`id`)
)
-- Joins payments to invoices. The sum of all `invoice_currency_value`s is the balance paid towards an invoice.
CREATE TABLE `financial_transactions_invoices` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`invoice` int(10) unsigned NOT NULL,
`invoice_currency_value` decimal(8,2) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
-- Lists items (services) available to purchase.
CREATE TABLE `items` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`value` decimal(8,2) unsigned NOT NULL
PRIMARY KEY (`id`)
)
-- Each instance represents that the `item` has been purchased.
CREATE TABLE `item_instances` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`invoice` int(10) unsigned NOT NULL,
`item` int(10) unsigned NOT NULL,
`invoice_currency_rate` decimal(11,5) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
-- Any number of tax instances can exist for an item instance and indicate this tax has been applied to the associated item instance.
CREATE TABLE `tax_instances` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`item_instance` int(10) unsigned NOT NULL,
`value` decimal(8,2) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
Now, I need a view that lists for each row,
the invoice number
the total value of the invoice
the total tax on the invoice
and the total value of payments made towards the invoice
However, I can't figure out how to get these three separate queries into the same result set of one row per invoice, e.g.
inv_no total_value total_tax payments
1 150 5 120
2 120 10 20
3 10 0 10
4 1000 150 1150
I have written the following query which produces the desired result, but due to the 'no subquery' rule in MySQL views, it is not acceptable.
SELECT `invoice_id`, SUM(`total_value`) AS `total_value`, SUM(`total_tax`) AS `total_tax`,
SUM(`paid_balance`) AS `paid_balance`
FROM
(SELECT `invoices`.`id` AS `invoice_id`, SUM(`items`.`value` * `item_instances`.`invoice_currency_rate`) AS `total_value`,
NULL AS `total_tax`, NULL AS `paid_balance`
FROM `items`
JOIN `item_instances` ON `items`.`id` = `item_instances`.`item`
JOIN `invoices` ON `item_instances`.`invoice` = `invoices`.`id`
GROUP BY `invoices`.`id`
UNION
SELECT `invoices`.`id`, NULL, SUM(`tax_instances`.`value`), NULL
FROM `tax_instances`
JOIN `item_instances` ON `tax_instances`.`item_instance` = `item_instances`.`id`
JOIN `invoices` ON `item_instances`.`invoice` = `invoices`.`id`
GROUP BY `invoices`.`id`
UNION
SELECT `invoices`.`id`, NULL, NULL, SUM(`financial_transactions_invoices`.`invoice_currency_value`)
FROM `financial_transactions_invoices`
JOIN `invoices` ON `financial_transactions_invoices`.`invoice` = `invoices`.`id`
GROUP BY `invoices`.`id`) AS `components`
GROUP by `invoice_id`;
Without tackling the problem in the way I have, I can't think of any other way I can do it within MySQL.
Any ideas? Appreciate any help.
You could create two views. One with the UNION Subquery, and one with the outer query.

How can I select the current holder for each championship?

I want to select the current holders for each championship in a championships table, and return NULL for championships that have not had any winners yet.
Here are the create statements for the two tables:
CREATE TABLE `championships` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`friendly_name` varchar(255) NOT NULL,
`rank` int(2) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
UNIQUE KEY `friendly_name` (`friendly_name`)
) ENGINE=InnoDB;
CREATE TABLE `title_history` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`championship` int(10) unsigned NOT NULL,
`winner` varchar(255) NOT NULL,
`date_from` date NOT NULL,
`location` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `championship` (`championship`)
) ENGINE=InnoDB;
ALTER TABLE `title_history` ADD CONSTRAINT `title_history_ibfk_1` FOREIGN KEY (`championship`) REFERENCES `championships` (`id`) ON UPDATE CASCADE;
What MySQL statement would return the data set I wanted?
Assuming you're storing the winner of a championship as the primary key/id of the holder, something like this should work. You might want to add in another join to get the actual name of the team from another table though.
Because LEFT join will only select rows from the 'right' table when there is a match, everything that doesn't have one should come back as NULL.
SELECT name, [holder]
FROM championships AS c
LEFT JOIN title_history AS h ON c.winner = h.id
EDITED VERSION:
With further insight into your tables and from your comment, maybe try this subselect:
SELECT friendly_name,
(SELECT winner FROM title_history WHERE championship = c.id ORDER BY date_from DESC LIMIT 1)
FROM championships AS c
ORDER BY name
If I understand your structure correctly, that ought to get the last winner of each championship?