Combining tables in mySQL to keep nulls [duplicate] - mysql

This question already has answers here:
How can I do a FULL OUTER JOIN in MySQL?
(15 answers)
Closed 4 years ago.
I have two tables below. I am looking to find all the combinations of store and newid in sales2 which are not present in sales1 and combine them with sales1.
I am looking for the end result to be:
store newid amount total
123 | 123 | 12.3 | 1
456 | 123 | 45.6 | 2
789 | adsf | 78.9 | 3
321 | 123f | NULL | NULL
789 | 1654 | NULL | NULL
Original Tables
CREATE TABLE sales1 (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
store VARCHAR(30) NOT NULL,
newid VARCHAR(30),
amount DOUBLE,
total INT
);
CREATE TABLE sales2 (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
store VARCHAR(30) NOT NULL,
newid VARCHAR(30),
amount DOUBLE
);
INSERT INTO sales1 (store, newid, amount, total) VALUES
('123','123', 12.3, 1),('456','123', 45.6, 2),('789','adsf', 78.9, 3);
INSERT INTO sales2 (store, newid, amount) VALUES
('123','123', Null),('456','123', Null),('321','123f', Null),('789','1654', Null);

We can use two different SELECT queries for this. First query will fetch all the rows from sales1 table.
Second would fetch the remaining rows from sales2 table, which do not exist in the sales1 table. We will use LEFT JOIN from sales2 to sales1 and consider only those rows where sales1.id is NULL after the Join.
We will eventually use UNION ALL to combine the result-set from both the queries.
Query
SELECT
s1.store, s1.newid, s1.amount, s1.total
FROM sales1 s1
UNION ALL
SELECT
s2.store, s2.newid, s2.amount, NULL AS total
FROM sales2 s2
LEFT JOIN sales1 s1
ON s2.store = s1.store AND
s2.newid = s1.newid
WHERE s1.id IS NULL;
Result
| store | newid | amount | total |
| ----- | ----- | ------ | ----- |
| 123 | 123 | 12.3 | 1 |
| 456 | 123 | 45.6 | 2 |
| 789 | adsf | 78.9 | 3 |
| 321 | 123f | | |
| 789 | 1654 | | |
View on DB Fiddle

Related

Update first occurrence of value in a time interval

I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.

Update table based on result of select on another table

I have two tables like this:
person:
id | name | sale | commission
1 | abc | 0 | 0
2 | xyz | 0 | 0
sale:
id | date | person_id | sale | commission
1 | 2016-05-01 | 1 | 10 | 1
2 | 2016-05-02 | 1 | 10 | 1
3 | 2016-05-03 | 1 | 10 | 1
4 | 2016-05-01 | 2 | 20 | 2
5 | 2016-05-02 | 2 | 20 | 2
6 | 2016-05-01 | 2 | 20 | 2
I want to update person table with single update query and change the table something like this:
person:
id | name | sale | commission
1 | abc | 30 | 3
2 | xyz | 60 | 6
I know I can sum sale like following but how to update following query result into person table directly.
SELECT person_id, SUM(sale), SUM(commission)
FROM sale
GROUP BY person_id;
As Strawberry said in the comments under your question, think long and hard before you save this information. It is denormalized, and it becomes stale. Rather, consider using it during report generation. Otherwise, well, as said, you may run into problems.
drop table if exists person;
create table person
( personId int auto_increment primary key,
name varchar(100) not null,
totSales decimal(9,2) not null,
totComm decimal(9,2)
);
insert person(name,totSales,totComm) values
('Joe',0,0),
('Sally',0,0);
-- just added persons 1 and 2 (auto_inc)
drop table if exists sale;
create table sale
( saleId int auto_increment primary key,
saleDate date not null,
personId int not null,
sale decimal(9,2) not null,
commission decimal(9,2) not null,
index(personId), -- facilitate a snappier "group by" later
foreign key (personId) references person(personId) -- Ref Integrity
);
insert sale(saleDate,personId,sale,commission) values
('2016-05-01',2,10,1),
('2016-05-01',1,40,4),
('2016-05-02',1,30,3),
('2016-05-07',2,10,1),
('2016-05-07',2,90,9);
-- the following dies on referential integrity, FK, error 1452 as expected
insert sale(saleDate,personId,sale,commission) values ('2016-05-01',4,10,1);
The update statement
update person p
join
( select personId,sum(sale) totSales, sum(commission) totComm
from sale
group by personId
) xDerived
on xDerived.personId=p.personId
set p.totSales=xDerived.totSales,p.totComm=xDerived.totComm;
The results
select * from person;
+----------+-------+----------+---------+
| personId | name | totSales | totComm |
+----------+-------+----------+---------+
| 1 | Joe | 70.00 | 7.00 |
| 2 | Sally | 110.00 | 11.00 |
+----------+-------+----------+---------+
2 rows in set (0.00 sec)
xDerived is merely an alias name. All derived tables need an alias name, whether or not you use the alias name explicitly.
UPDATE person
SET sale = (
SELECT SUM(s.sale) FROM sale s
WHERE s.person_id = person.id
);
works for me. See it in action at: http://ideone.com/F32oUU
EDIT for new version with additional aggregated column:
UPDATE person SET
sale = (
SELECT SUM(s.sale) FROM sale s
WHERE s.person_id = person.id
),
commission = (
SELECT SUM(s.commission) FROM sale s
WHERE s.person_id = person.id
);
http://ideone.com/yo1A9Y
This being said, I feel sure that a JOIN solution is better, and am hopeful another answerer will be able to post such a solution.

MySQL: COUNT with implicit GROUP BY

I've been trying to guess how to solve my problem for some time and I cannot seem to find a solution, so I come to you, experts.
What I've got
A MySQL table with the following structure and values (as an example):
+----+---------+----------------+-----------------+--------------+
| id | item_id | attribute_name | attribute_value | deleted_date |
+----+---------+----------------+-----------------+--------------+
| 1 | 2 | action | call | NULL |
| 2 | 2 | person | Joseph | NULL |
| 3 | 2 | action | fault | NULL |
| 4 | 2 | otherattr | otherval | NULL |
| 5 | 5 | action | call | NULL |
| 6 | 5 | person | Mike | NULL |
| 7 | 5 | action | sprint | NULL |
| 8 | 8 | action | call | NULL |
| 9 | 8 | person | Joseph | NULL |
| 10 | 8 | action | block | NULL |
| 11 | 8 | action | call | NULL |
+----+---------+----------------+-----------------+--------------+
What I need
I'd like a query to return me how many items (item_id) have at least one attribute_name with 'action' and with attribute_value as 'call', grouped by 'person', but only counting one of them.
So, if - like in the example, at ids 8 and 11 - there is an item_id with two "action" = "call", only COUNT one of them.
The query should return something like this:
+--------+--------------+
| person | action_calls |
+--------+--------------+
| Joseph | 2 |
| Mike | 1 |
+--------+--------------+
The problem
The problem is that I don't know how to do that in a simple way that would not make a huge performance increment, as this query will be returning and searching along a lot of rows - and returning a lot of them, too, in some cases.
The only thing that comes to my mind is with nested and nested queries, and I'd like to avoid that.
If I make a COUNT(DISTINCT), it only returns '1' in 'Joseph', because the value is always 'call', and if I GROUP BY b.item_id, it returns me two rows with Joseph (and, in this case too, it counts both 'call' attributes, so it wouldn't be the solution neither).
What I've tried
The query that I've tried is the following:
SELECT a.attribute_value AS person, COUNT(b.attribute_value) AS action_calls
FROM `er_item_attributes` a, `er_item_attributes` b
WHERE a.attribute_name = 'person'
AND b.item_id IN (SELECT DISTINCT item_id FROM er_item_parents WHERE parent_id IN (1234,4567))
AND b.item_id = a.item_id
AND b.attribute_name = 'action'
AND b.attribute_value = 'call'
AND b.deleted_date IS NULL
GROUP BY a.attribute_value, b.attribute_name
Additional information
The item_id, as you can see, will be also chosen from an inner WHERE clause, because the ones that are valid are in another table (just like a parent - son table). The parent_id numbers are for an example and are not relevant.
To sum up
How can I make a COUNT in MySQL to behave like a COUNT GROUP BY without nesting SELECTs that could deteriorate the performance?
If any further information was needed, comment it and I will try to add it.
Also, any recommendations on another way to query the information needed to improve performance will be welcome.
Thank you everyone for your time and help!
Kind regards.
Try this!
SELECT attribute_value AS person, COUNT(*) FROM `stack_1239`
WHERE item_id IN (
SELECT item_id FROM `stack_1239` WHERE attribute_name = 'action' AND attribute_value = 'call'
)
AND attribute_name = 'person'
GROUP BY person;
:)
DROP TABLE IF EXISTS eav_hell;
CREATE TABLE eav_hell
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,entity INT NOT NULL
,attribute VARCHAR(20) NOT NULL
,value VARCHAR(20) NOT NULL
);
INSERT INTO eav_hell
VALUES
( 1 ,2 ,'action','call'),
( 2 ,2 ,'person','Joseph'),
( 3 ,2 ,'action','fault'),
( 4 ,2 ,'otherattr','otherval'),
( 5 ,5 ,'action','call'),
( 6 ,5 ,'person','Mike'),
( 7 ,5 ,'action','sprint'),
( 8 ,8 ,'action','call'),
( 9 ,8 ,'person','Joseph'),
(10 ,8 ,'action','block'),
(11 ,8 ,'action','call');
SELECT e1.entity
, e1.value person
, e2.value action
, COUNT(*)
FROM eav_hell e1
LEFT
JOIN eav_hell e2
ON e2.entity = e1.entity
AND e2.attribute = 'action'
AND e2.value = 'call'
WHERE e1.attribute = 'person'
GROUP
BY entity
, person
, action;
+--------+--------+--------+----------+
| entity | person | action | COUNT(*) |
+--------+--------+--------+----------+
| 2 | Joseph | call | 1 |
| 5 | Mike | call | 1 |
| 8 | Joseph | call | 2 |
+--------+--------+--------+----------+
Edit:
SELECT e1.value person
, e2.value action
, COUNT(DISTINCT e1.entity)
FROM eav_hell e1
LEFT
JOIN eav_hell e2
ON e2.entity = e1.entity
AND e2.attribute = 'action'
AND e2.value = 'call'
WHERE e1.attribute = 'person'
GROUP
BY person
, action;
+--------+--------+---------------------------+
| person | action | COUNT(DISTINCT e1.entity) |
+--------+--------+---------------------------+
| Joseph | call | 2 |
| Mike | call | 1 |
+--------+--------+---------------------------+

count the sum of a column where data is contained in multiple columns

I have a debating competition where I want to find out which team has won and lost the most debates. The problem I am having is the id of the two teams competing in the debate is in two different columns (hostid, visitid).
I have the below so far which gives me what I want however it only shows the visitid data.
CREATE TABLE teams (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE debates (
debateid INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
debatedate DATE NOT NULL,
hostid INT,
visitid INT,
winnerid INT
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
INSERT INTO teams (id, name) VALUES
(1,'team one'),
(2,'team two'),
(3,'team three'),
(4,'team four'),
(5,'team five'),
(6,'team six');
INSERT INTO debates (debateid, debatedate,hostid, visitid, winnerid ) VALUES
(1,'2012-01-11', 1,2,1),
(2,'2012-01-11', 3,4,4),
(3,'2012-02-11', 5,6,5),
(4,'2012-02-11', 1,4,1),
(5,'2012-02-11', 2,5,5),
(6,'2012-02-11', 3,6,3),
(7,'2012-03-11', 6,1,1),
(8,'2012-03-11', 5,2,5),
(9,'2012-03-11', 3,4,4);
SELECT
visitid AS id,
t.name AS name,
sum(visitid= deb.winnerid) as w,
sum(visitid != deb.winnerid) as l
FROM debates AS deb
JOIN teams t ON t.id = deb.visitid
WHERE visitid != -1
AND debatedate < CURDATE( )
GROUP BY id
ORDER BY w DESC
RESULT
-----------------------------------------
| ID | NAME | W | L |
| 4 | team four | 2 | 1 |
| 5 | team five | 1 | 0 |
| 1 | team one | 1 | 0 |
| 6 | team six | 0 | 2 |
| 2 | team two | 0 | 2 |
-----------------------------------------
How can I combine these two columns, I am aware of union but I can't think of a way to implement this in this situation or what method I should use?
If I had it working as intended the result would be the below eg where hostid or visitid = winnerid
-----------------------------------------
| ID | NAME | W | L |
| 1 | team one | 3 | 0 |
| 5 | team five | 3 | 0 |
| 4 | team four | 2 | 1 |
| 3 | team three | 1 | 2 |
| 2 | team two | 0 | 3 |
| 6 | team six | 0 | 3 |
-----------------------------------------
See fiidle for example
SELECT t.id,
t.name,
SUM(t.id = d.winnerid) AS w,
SUM(t.id != d.winnerid) AS l
FROM debates AS d
JOIN teams AS t ON t.id IN (d.hostid, d.visitid)
WHERE d.visitid != -1 -- not sure what purpose this serves
AND d.debatedate < CURDATE()
GROUP BY t.id
ORDER BY w DESC
See it on sqlfiddle.

how to merge 2 tables with one request

so i got 2 tables with following structure:
CREATE TABLE courses(
id bigint not null auto_increment,
title varchar(255) default '',
primary key(id)
);
CREATE TABLE course_dates(
id bigint not null auto_increment,
course_id bigint,
`date` date,
key idx(course_id,date),
primary key(id)
);
so courses are stored in first table and course dates in second (each course can have unlimited number of dates)
i need to get all course rows (with all its dates) at one time using one query
for example, if i have tables with such data:
courses:
id | title
1 | course#1
2 | course#2
course_dates:
id | course_id | date
1 | 1 | 2012-12-25
2 | 1 | 2012-12-27
3 | 1 | 2012-12-31
4 | 2 | 2012-12-23
5 | 2 | 2012-12-30
then i need result rows like this:
id | course_id | date | title
1 | 1 | 2012-12-25 | course#1
2 | 1 | 2012-12-27 | course#1
3 | 1 | 2012-12-31 | course#1
4 | 2 | 2012-12-23 | course#2
5 | 2 | 2012-12-30 | course#2
A simple INNER JOIN will do.
SELECT b.*, a.title
FROM Courses a
INNER JOIN Courses_Dates b
ON a.id = b.Course_ID
SQLFiddle Demo
SQLFiddle Demo (with ORDER BY clause)
To learn more about joins, see the link below
Visual Representation of SQL Joins