MySql: JOIN and SUM from different tables - mysql

I need to sum-up the amount in 2 tables (c1, c2) linked n:1 to table a. The problem is: It would be greate if I could do it in just 1 query, because the real situation is a bit more complicated ;-) I brought it down to this testcase:
create table a (
`id` int(10) unsigned NOT NULL, KEY(id)
) ENGINE=InnoDB;
create table c1 (
`id` int(10) unsigned NOT NULL, KEY(id),
`a` int(10),
`amount` decimal(15,2) NOT NULL
) ENGINE=InnoDB;
create table c2 (
`id` int(10) unsigned NOT NULL, KEY(id),
`a` int(10),
`amount` decimal(15,2) NOT NULL
) ENGINE=InnoDB;
INSERT INTO a SET id=1;
INSERT INTO c1 SET a=1, amount = 2;
INSERT INTO c1 SET a=1, amount = 3;
INSERT INTO c2 SET a=1, amount = 1;
SELECT SUM(c1.amount), SUM(c2.amount)
FROM a
LEFT JOIN c1 ON c1.a = a.id
LEFT JOIN c2 ON c2.a = a.id
WHERE a.id = 1;
The result of course is:
+----------------+----------------+
| SUM(c1.amount) | SUM(c2.amount) |
+----------------+----------------+
| 5.00 | 2.00 |
+----------------+----------------+
because c1 is joined twice and doubles the amound in c2. But I need to get:
+----------------+----------------+
| SUM(c1.amount) | SUM(c2.amount) |
+----------------+----------------+
| 5.00 | 1.00 |
+----------------+----------------+
Any idea how to get to this?

One possible answer is:
SELECT (select SUM(c1.amount) from c1 where c1.a = a.id) as c1_amount,
(select SUM(c2.amount) from c2 where c2.a = a.id) as c2_amount
FROM a
WHERE a.id = 1;
Link to SQL Fiddle
BTW - Thanks for putting in the data and create scripts. That helped a lot.

SELECT a.*
, SUM(CASE WHEN b.source = 'c1' THEN amount END) c1_ttl
, SUM(CASE WHEN b.source = 'c2' THEN amount END) c2_ttl
FROM a
JOIN
(
SELECT *,'c1' source FROM c1 UNION SELECT *,'c2' FROM c2
) b
ON b.a = a.id;
+----+--------+--------+
| id | c1_ttl | c2_ttl |
+----+--------+--------+
| 1 | 5.00 | 1.00 |
+----+--------+--------+

Another solution.
SELECT *
FROM
(SELECT SUM(c1.amount) FROM c1 WHERE c1.a = 1) C1
INNER JOIN
(SELECT SUM(c2.amount) FROM c2 WHERE c2.a = 1) C2;

Related

how can i select row's index from multi select

how can I Derive the query..?
Create Table Query:
CREATE TABLE `account_list` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`nick` char(12) NOT NULL DEFAULT '',
`sponsor` char(12) DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `nick` (`nick`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Example table data:
ID NICK SPONSOR
1 A NULL
2 B C
3 C NULL
4 D C
SELECT a.id, a.nick,b.sponsor
FROM account_list a (select b.sponsor from account_list b)
then, I wanted some like this:
SELECT id, nick, sponsor FROM `account_list` ....
ID NICK SPONSOR
1 A
2 B 3
3 C
4 D 3
look the sponsor row.
printed C is 3.
how can I select look like this?
Your query was almost right(I think) the sub query where clause should reference nick
drop table if exists t;
create table t
(ID int, NICK varchar(1), SPONSOR varchar(1));
insert into t values
(1 , 'A' , NULL),
(2 , 'B' , 'C'),
(3 , 'C' , NULL),
(4 , 'D' , 'C');
SELECT id, nick,sponsor,
(select b.id
from t as b
WHERE a.sponsor= b.nick and a.sponsor is not null
)
FROM t as a
;
+------+------+---------+--------------------------------------------------------------------------------------------------+
| 1 | A | NULL | NULL |
| 2 | B | C | 3 |
| 3 | C | NULL | NULL |
| 4 | D | C | 3 |
+------+------+---------+--------------------------------------------------------------------------------------------------+
4 rows in set (0.00 sec)
You could try using account_list for left join
SELECT a.id, a.nick, b.id sponsor
from account_list a
left join account_list b ON b.nick = a.sponsor

Creating summary VIEW from fields from multiple tables

I am trying to write a select query for creating a view in MySQL. Each row in the view should display weekly summary (sum, avg) for user values collected from multiple tables. The tables are similar to each-other but not identical. The view should include rows also in case other table doesn't have a values for that week. Something like this:
| week_year | sum1 | avg1 | sum2 | user_id |
| --------- | ---- | ---- | ---- | ------- |
| 201840 | | | 3 | 1 |
| 201844 | 45 | 55 | | 1 |
| 201845 | 55 | 65 | | 1 |
| 201849 | 65 | 75 | | 1 |
| 201849 | 75 | 85 | 3 | 2 |
The tables (simplified) are as follows:
CREATE TABLE IF NOT EXISTS `t1` (
`user_id` INT NOT NULL AUTO_INCREMENT,
`date` DATE NOT NULL,
`value1` int(3) NOT NULL,
`value2` int(3) NOT NULL,
PRIMARY KEY (`user_id`,`date`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `t2` (
`id` INT NOT NULL AUTO_INCREMENT,
`date` DATE NOT NULL,
`value3` int(3) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `t3` (
`t3_id` INT NOT NULL,
`user_id` INT NOT NULL
) DEFAULT CHARSET=utf8;
My current solution doesn't seem reasonable and I am not sure how it would perform in case of thousands of rows:
select ifnull(yearweek(q1.date1), yearweek(q1.date2)) as week_year,
sum(value1) as sum1,
avg(value2) as avg1,
sum(value3) as sum2,
q1.user_id
from (select t2.date as date2,
t1.date as date1,
ifnull(t3.user_id, t1.user_id) as user_id,
t1.value1,
t1.value2,
t2.value3
from t2
join t3 on t3.t3_id=t2.id
left join t1 on yearweek(t1.date) = yearweek(t2.date) and t1.user_id = t3.user_id
union
select t2.date as date2,
t1.date as date1,
ifnull(t3.user_id, t1.user_id) as user_id,
t1.value1,
t1.value2,
t2.value3
from t2
join t3 on t3.t3_id=t2.id
right join t1 on yearweek(t1.date) = yearweek(t2.date) and t1.user_id = t3.user_id) as q1
group by week_year, user_id;
DB Fiddle
Is the current solution okay performance wise or are there better options? In case of in the future third (or fourth) table is added, how would I manage the query? Should I consider creating a separate table, that is updated with triggers?
Thanks in advance.
Another way you can do it is to union all the data and then group it. You'll have to perf test to see which is better:
SELECT
yearweek(date),
SUM(value1) as sum1,
AVG(value2) as avg1,
SUM(value3) as sum2
FROM
(
SELECT user_id, date, value1, value2, CAST(null as INT) as value3 FROM t1
UNION ALL
SELECT user_id, date, null, null, value3 FROM t2 INNER JOIN t3 ON t2.id = t3.t3_id
)
GROUP BY
user_id,
yearweek(date)
Hopefully mysql won't take issue with casting null to an int..

Log affected rows into another table in MySQL

Given the table:
CREATE TABLE `records` (
`id_type` varchar(50) NOT NULL,
`old_id` INT,
`new_id` INT,
) ENGINE=InnoDB;
And the data:
id_type | old_id | new_id
USER | 11 | NULL
USER | 15 | NULL
USER | 56 | NULL
USER | NULL | 500
USER | NULL | 523
USER | NULL | 800
I want to perform a query that will return:
id_type | old_id | new_id
USER | 11 | 500
USER | 15 | 523
USER | 56 | 800
Create table records_old
(
id_type varchar(20) primary key,
old_id int not null
);
Create table records_new
(
id_type varchar(20),
new_id int not null
);
insert into records_old(id_type,old_id) values ('USER1',11);
insert into records_old(id_type,old_id) values ('USER2',12);
insert into records_old(id_type,old_id) values ('USER3',13);
insert into records_new(id_type,new_id) values ('USER1',500);
insert into records_new(id_type,new_id) values ('USER2',600);
insert into records_new(id_type,new_id) values ('USER3',700);
select * from records_old;
select * from records_new;
select a.id_type,a.old_id,b.new_id from records_old a
inner join records_new b
where a.id_type=b.id_type;
SET #old_row_number = 0;
SET #new_row_number = 0;
SELECT OldData.id_type, OldData.old_id, NewData.new_id
FROM (SELECT id_type, old_id, (#old_row_number:=#old_row_number + 1) AS OldRowNumber
FROM `records`
WHERE old_id IS NOT NULL) OldData
JOIN (SELECT id_type, new_id, (#new_row_number:=#new_row_number + 1) AS NewRowNumber
FROM `records`
WHERE new_id IS NOT NULL) NewData ON NewData.NewRowNumber = OldData.OldRowNumber
Filter with id is not null and separate as two sub-queries and add a row number for each row then join will help in your case.
Working Demo

Join two tables where table A has a date value and needs to find the next date in B below the date in A

I got this table "A":
| id | date |
===================
| 1 | 2010-01-13 |
| 2 | 2011-04-19 |
| 3 | 2011-05-07 |
| .. | ... |
and this table "B":
| date | value |
======================
| 2009-03-29 | 0.5 |
| 2010-01-30 | 0.55 |
| 2011-08-12 | 0.67 |
Now I am looking for a way to JOIN those two tables having the "value" column in "B" mapped to the dates in "A". The tricky part for me here is that table "B" only stores the change date and the new value. Now when I need this value in table "A" the SQL needs to look back what date is the next below the date it is asking the value for.
So in the end the JOIN of those tables should look like this:
| id | date | value |
===========================
| 1 | 2010-01-13 | 0.5 |
| 2 | 2011-04-19 | 0.55 |
| 3 | 2011-05-07 | 0.55 |
| .. | ... | ... |
How can I do this?
-- Create and fill first table
CREATE TABLE `id_date` (
`id` int(11) NOT NULL auto_increment,
`iddate` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `id_date` VALUES(1, '2010-01-13');
INSERT INTO `id_date` VALUES(2, '2011-04-19');
INSERT INTO `id_date` VALUES(3, '2011-05-07');
-- Create and fill second table
CREATE TABLE `date_val` (
`mydate` date NOT NULL,
`myval` varchar(4) collate utf8_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `date_val` VALUES('2009-03-29', '0.5');
INSERT INTO `date_val` VALUES('2010-01-30', '0.55');
INSERT INTO `date_val` VALUES('2011-08-12', '0.67');
-- Get the result table as asked in question
SELECT iddate, t2.mydate, t2.myval
FROM `id_date` t1
JOIN date_val t2 ON t2.mydate <= t1.iddate
AND t2.mydate = (
SELECT MAX( t3.mydate )
FROM `date_val` t3
WHERE t3.mydate <= t1.iddate )
What we're doing:
for each date in the id_date table (your table A),
we find the date in the date_val table (your table B)
which is the highest date in the date_val table (but still smaller than the id_date.date)
You could use a subquery with limit 1 to look up the latest value in table B:
select id
, date
, (
select value
from B
where B.date < A.date
order by
B.date desc
limit 1
) as value
from A
I have been inspired by the other answers but ended with my own solution using common table expressions:
WITH datecombination (id, adate, bdate) AS
(
SELECT id, A.date, MAX(B.Date) as Bdate
FROM tableA A
LEFT JOIN tableB B
ON B.date <= A.date
GROUP BY A.id, A.date
)
SELECT DC.id, DC.adate, B.value FROM datecombination DC
LEFT JOIN tableB B
ON DC.bdate = B.bdate
The INNER JOIN return rows when there is at least one match in both tables. Try this.
Select A.id,A.date,b.value
from A inner join B
on A.date=b.date

A mysql query: couple of conditions matching in the same column

I have this table named "events" in my mysql database:
+-----+-----------+------------------------------+------------+
| ID | CATEGORY | NAME | TYPE |
+-----+-----------+------------------------------+------------+
| 1 | 1 | Concert | music |
| 2 | 2 | Basketball match | indoors |
| 3 | 1 | Theather play | outdoors |
| 4 | 1 | Concert | outdoors |
+-----+-----------+------------------------------+------------+
I need a query to count the events with category 1 and which type is music and also outdoors
Meaning that from the table above the count should be only 1: there are three events with category 1
but only "Concert" has type outdoor and music (ID 1 and ID 4).
What would be that query? Can that be done?
Try this:
SELECT count(DISTINCT e1.name)
FROM `events` AS e1
JOIN `events` AS e2 ON e1.name = e2.name
WHERE e1.category = 1
AND e2.category = 1
AND e1.type = 'music'
AND e2.type = 'outdoor'
Or a harder to understand way, but way faster than the previous one:
SELECT count(*) FROM (
SELECT `name`
FROM `events`
WHERE `category` = 1
GROUP BY `name`
HAVING SUM( `type` = 'music') * SUM( `type` = 'outdoor' ) >= 1
) AS notNeeded
For 2 criteria I would use Alin's answer. An approach you can use for greater numbers is below.
SELECT COUNT(*)
FROM (SELECT `name`
FROM `events`
WHERE `category` = 1
AND `type` IN ( 'outdoors', 'music' )
GROUP BY `name`
HAVING COUNT(DISTINCT `type`) = 2) t
Try this query
Select count(*), group_concat(TYPE SEPARATOR ',') as types
from events where category = 1
HAVING LOCATE('music', types) and LOCATE('outdoors', types)
try:
SELECT * FROM `events` AS e1
LEFT JOIN `events` AS e2 USING (`name`)
WHERE e1.`category` = 1 AND e2.`category` = 1 AND e1.`type` = 'music' AND e2.`type` = 'outdoors'
SELECT COUNT(*)
FROM table
WHERE category=1
AND type='music' AND type IN (SELECT type
FROM table
WHERE type = 'outdoor')
one line keeps resetting my connection. wth? i'll try posting as a comment
Select count(distinct ID) as 'eventcount' from events where Category = '1' and Type in('music','outdoor')