JOIN from tables evaluating results from one of them - mysql

I have three tables to join, one of them with one-to several values.
SQLFIDDLE
CREATE TABLE Table1 (`id` int, `name` varchar(3));
INSERT INTO Table1 (`id`, `name`)
VALUES (1, 'A'), (2, 'B'), (3, 'C');
CREATE TABLE Table2 (`id` int, `status` int, `date` varchar(9));
INSERT INTO Table2 (`id`, `status`, `date`)
VALUES (1, 1, '''.11..'''), (1, 2, '''.12..'''), (1, 3, '''.13..'''),
(2, 3, '''.23..'''), (3, 1, '''.31..'''), (3, 3, '''.33..''')
;
CREATE TABLE Table3 (`id` int, `value` int);
INSERT INTO Table3 (`id`, `value`)
VALUES (1, 34), (2, 22), (3, 17);
Query 1:
select * from table1
| id | name |
|----|------|
| 1 | A |
| 2 | B |
| 3 | C |
Query 2:
select * from table2;
| id | status | date |
|----|--------|---------|
| 1 | 1 | '.11..' |
| 1 | 2 | '.12..' |
| 1 | 3 | '.13..' |
| 2 | 3 | '.23..' |
| 3 | 1 | '.31..' |
| 3 | 3 | '.33..' |
Query 3:
select * from table3
| id | value |
|----|-------|
| 1 | 34 |
| 2 | 22 |
| 3 | 17 |
I need query that returns for each id:
TABLE1.name, TABLE2.status, TABLE2.date, TABLE3.value
with this condition:
If TABLE2.status =1 exists then return ONLY that line of TABLE2
Else if TABLE2.status =1 does not exists then look for status =2 and return ONLY that line of TABLE2
If no one of those values are present in TABLE2 then skip that id from results
EDIT: TABLE2 has an UNIQUE key for id,status so there can be only one id=1 status=1
Thanks for your help!

Something like this maybe:
select table1.id, table1.name,
coalesce(table2_status1.status, table2_status2.status) as status,
coalesce(table2_status1.date, table2_status2.date) as date,
table3.value
from table1
left join table2 table2_status1 on table2_status1.id = table1.id and table2_status1.status = 1
left join table2 table2_status2 on table2_status2.id = table1.id and table2_status2.status = 2
join table3 on table3.id = table1.id
where (table2_status1.id is not null or table2_status2.id is not null);

Not performant, using subselects, works ( but rlanvins https://stackoverflow.com/a/48235077/7505395 is nicer) :
A,B,C instead of First, Second, ...
select
TABLE1.name,
case
when exists( select 1 from table2 where id = table1.id and status = 1)
then 1
when exists( select 1 from table2 where id = table1.id and status = 2)
then 2
end as T2status,
case
when exists( select 1 from table2 where id = table1.id and status = 1)
then ( select date from table2 where id = table1.id and status = 1)
when exists( select 1 from table2 where id = table1.id and status = 2)
then ( select date from table2 where id = table1.id and status = 2)
end as T2date,
TABLE3.value
from table1
join table3 on table1.id = table3.id
where
exists( select 1 from table2 where id = table1.id and status = 1)
or exists( select 1 from table2 where id = table1.id and status = 2)
Output
Name T2status T2date value
A 1 '.11..' 34
C 1 '.31..' 17
DDL
CREATE TABLE Table1 (`id` int, `name` varchar(3));
INSERT INTO Table1 (`id`, `name`)
VALUES (1, 'A'), (2, 'B'), (3, 'C');
CREATE TABLE Table2 (`id` int, `status` int, `date` varchar(9));
INSERT INTO Table2 (`id`, `status`, `date`)
VALUES (1, 1, '''.11..'''), (1, 2, '''.12..'''), (1, 3, '''.13..'''),
(2, 3, '''.23..'''), (3, 1, '''.31..'''), (3, 3, '''.33..''')
;
CREATE TABLE Table3 (`id` int, `value` int);
INSERT INTO Table3 (`id`, `value`)
VALUES (1, 34), (2, 22), (3, 17);

Related

Mysql5.6 select statement with duplicate id and set

I have a table A containing id (auto incremented) as a primary key and a table B containing id as a foreign key.While selecting data from Table B if there are duplicate ids then append a number with empid.
TableA
id empid name place
1 ab123 John SL
2 gh345 Lucy AK
3 hj890 Mike KL
TableB
id class
1 A
1 B
1 A
2 A
3 A
Output
ab123,SL,A
ab123,SL,B
ab123,SL,A
gh345,AK,A
hj890,KL,A
Desired output
ab123-1,SL,A
ab123-2,SL,B
ab123-3,SL,A
gh345,AK,A
hj890,KL,A
This is what I have tried
SELECT TableA.empid, ",", TableA.place, ",", TableB.class
FROM TableA
INNER JOIN TableB ON TableA.id = TableB.id
With mysql 8 You can do following code, in mysql 6.x you need to use user-defined variables instead of the window finction
CREATE TABLE TableA (
`id` INTEGER,
`empid` VARCHAR(5),
`name` VARCHAR(4),
`place` VARCHAR(2)
);
INSERT INTO TableA
(`id`, `empid`, `name`, `place`)
VALUES
('1', 'ab123', 'John', 'SL'),
('2', 'gh345', 'Lucy', 'AK'),
('3', 'hj890', 'Mike', 'K');
CREATE TABLE TableB (
`id` INTEGER,
`class` VARCHAR(1)
);
INSERT INTO TableB
(`id`, `class`)
VALUES
('1', 'A'),
('1', 'B'),
('1', 'A'),
('2', 'A'),
('3', 'A');
SELECT CONCAT(`empid`,IF(co > 1,CONCAT('-',`rn`),''),',',`place`,',',`class`) AS 'empid,place,class'
FROM
(SELECT
empid,place,class, ROW_NUMBER() OVER (PARTITION BY empid ) rn
, countB.co
FROM TableA
INNER JOIN TableB ON TableA.id = TableB.id
INNER JOIN (SELECT id,COUNT(*) co FROM TableB GROUP BY id) countB ON TableB.id = countB.id) t1
| empid,place,class |
| :---------------- |
| ab123-1,SL,A |
| ab123-2,SL,B |
| ab123-3,SL,A |
| gh345,AK,A |
| hj890,K,A |
db<>fiddle here
MySQL 5.x Version
SELECT CONCAT(`empid`,IF(co > 1,CONCAT('-',`rn`),''),',',`place`,',',`class`) AS 'empid,place,class'
FROM
(SELECT
place,class, IF(#id = empid,#rn := #rn+1, #rn := 1) rn
,#id := empid as empid
,countB.co
FROM TableA
INNER JOIN TableB ON TableA.id = TableB.id
INNER JOIN (SELECT id,COUNT(*) co FROM TableB GROUP BY id) countB ON TableB.id = countB.id
CROSS JOIN (SELECT #id :=0,#rn:=0) t2
ORDER BY empid) t1
| empid,place,class |
| :---------------- |
| ab123-1,SL,A |
| ab123-2,SL,B |
| ab123-3,SL,A |
| gh345,AK,A |
| hj890,K,A |
db<>fiddle here

MySQL Update Table from Another Table Recursively

I have two tables, table1 and table2, where table1 is updated to fill in missing (null) values based on matching fields in table2 to create a more complete table1.
I have tried numerous queries such as
UPDATE table1 INNER JOIN table2...SET...
and
UPDATE table1 SET ... (SELECT)...
However my results are always incomplete. Note that I have a much larger dataset in both tables in terms of both columns and rows). I just used this as simpler example.
Rules:
1) The `keyword` from table2 looks for a match in `keyword` in table1 and must accept partial matches.
2) No values can be overwritten in table1 (update null values only)
3) The lookup order is per run_order in table2.
Specific example:
Table1:
|-----|-------------------------------|----------|-----|---------|-------|
|t1_id|keyword |category |month|age |skill |
|-----|-------------------------------|----------|-----|---------|-------|
| 1 |childrens-crafts-christmas |kids | | | |
| 2 |kids-costumes | | |tween | |
| 3 |diy-color-printable |printable | | |easy |
| 4 |toddler-halloween-costume-page | | | | |
|-----|-------------------------------|----------|-----|---------|-------|
Table2:
|-----|---------|---------|----------|-----|----------|-------|
|t2_id|run_order|keyword |category |month|age |skill |
|-----|---------|---------|----------|-----|----------|-------|
| 1 | 1 |children | | |4-11 yrs | |
| 2 | 2 |printable| | | |easy |
| 3 | 3 |costume |halloween | 10 |0-12 years| |
| 4 | 4 |toddler | | |1-3 years | |
| 5 | 5 |halloween|holiday | 10 | | |
|-----|---------|---------|----------|-----|----------|-------|
Result:
|-----|-------------------------------|----------|-----|---------|-------|
|t1_id|keyword |category |month|age |skill |
|-----|-------------------------------|----------|-----|---------|-------|
| 1 |childrens-crafts-christmas |kids | |4-11 yrs | |
| 2 |kids-costumes |halloween | 10 |tween | |
| 3 |diy-color-printable printable |printable | | |easy |
| 4 |toddler-halloween-costume-page |holiday | 10 |1-3 years| |
|-----|-------------------------------|----------|-----|---------|-------|
MySQL for schema and table data:
DROP TABLE IF EXISTS table1;
DROP TABLE IF EXISTS table2;
CREATE TABLE `table1` (
`t1_id` INT NOT NULL AUTO_INCREMENT,
`keyword` VARCHAR(200) NULL,
`category` VARCHAR(45) NULL,
`month` VARCHAR(45) NULL,
`age` VARCHAR(45) NULL,
`skill` VARCHAR(45) NULL,
PRIMARY KEY (`t1_id`));
CREATE TABLE `table2` (
`t2_id` INT NOT NULL AUTO_INCREMENT,
`run_order` INT NULL,
`keyword` VARCHAR(200) NULL,
`category` VARCHAR(45) NULL,
`month` INT NULL,
`age` VARCHAR(45) NULL,
`skill` VARCHAR(45) NULL,
PRIMARY KEY (`t2_id`));
INSERT INTO `table1` (`keyword`, `category`) VALUES ('childrens-crafts-christmas', 'kids');
INSERT INTO `table1` (`keyword`, `age`) VALUES ('kids-costumes', 'tween');
INSERT INTO `table1` (`keyword`, `category`, `skill`) VALUES ('diy-color-printable', 'printable', 'easy');
INSERT INTO `table1` (`keyword`) VALUES ('toddler-halloween-costume-page');
INSERT INTO `table2` (`run_order`, `keyword`, `age`) VALUES (1, 'children', '4-11 yrs');
INSERT INTO `table2` (`run_order`, `keyword`, `skill`) VALUES (2, 'printable', 'easy');
INSERT INTO `table2` (`run_order`, `keyword`, `category`, `month`, `age`) VALUES (3, 'costume', 'halloween', 10, '0-12 years');
INSERT INTO `table2` (`run_order`, `keyword`, `age`) VALUES (4, 'toddler', '1-3 years');
INSERT INTO `table2` (`run_order`, `keyword`, `category`, `month`) VALUES (5, 'halloween', 'holiday', 10);
You want to update empty values in table1 with the value in the corresponding column of the first matching record in table2, run_order wise. A typical solution is to use a combination of correlated subqueries to find the matching records in table2 and keep only the one with lowest run_order.
Here is a query that will update null categories with this logic:
update table1 t1
set category = (
select category
from table2 t2
where t2.run_order = (
select min(t22.run_order)
from table2 t22
where
t1.keyword like concat('%', t22.keyword, '%')
and t22.category is not null
)
)
where t1.category is null
This assumes that run_order is unique in table2 (which seems relevant in your use case).
You can extend the logic for more columns with coalesce(). Here is the solution for columns category and month:
update table1 t1
set
category = coalesce(
t1.category,
(
select category
from table2 t2
where t2.run_order = (
select min(t22.run_order)
from table2 t22
where
t1.keyword like concat('%', t22.keyword, '%')
and t22.category is not null
)
)
),
month = coalesce(
t1.month,
(
select month
from table2 t2
where t2.run_order = (
select min(t22.run_order)
from table2 t22
where
t1.keyword like concat('%', t22.keyword, '%')
and t22.month is not null
)
)
)
where t1.category is null or t1.month is null
Demo on DB Fiddle
You can use a join between the 2 tables using a like expression and the if nul() function to ensure you don't overwrite non null values.
UPDATE table1 t1
INNER JOIN table2 t2 ON t1.keyword like concat("%",t2.keyword,"%")
SET
t1.category = ifnull(t1.category,t2.category),
t1.age = ifnull(t1.age,t2.age),
t1.skill = ifnull(t1.skill,t2.skill);

MySQL 5.5 - count open items per day

I have the below table that is just a snapshot and all I want to do is to calculate the number of open items per date.
I used to do it in excel with simple formula =COUNTIFS($A$2:$A$30000,"<="&E2,$B$2:$B$30000,">="&E2) where column A was the Open_Date dates and column B the Close_Date dates. I want to use SQL to get the same results.
This is my excel snapshot. Formula above.
In mysql I have replicated it with T1 table:
CREATE TABLE T1
(
ID int (10),
Open_Date date,
Close_Date date);
insert into T1 values (1, '2018-12-17', '2018-12-18');
insert into T1 values (2, '2018-12-18', '2018-12-18');
insert into T1 values (3, '2018-12-18', '2018-12-18');
insert into T1 values (4, '2018-12-19', '2018-12-20');
insert into T1 values (5, '2018-12-19', '2018-12-21');
insert into T1 values (6, '2018-12-20', '2018-12-22');
insert into T1 values (7, '2018-12-20', '2018-12-22');
insert into T1 values (8, '2018-12-21', '2018-12-25');
insert into T1 values (9, '2018-12-22', '2018-12-26');
insert into T1 values (10, '2018-12-23', '2018-12-27');
First step was to create the table with dates in case there any gap in Date_open. So my code at the moment is
SELECT
d.dt, Temp_T1.*
FROM
(
SELECT '2018-12-17' AS dt UNION ALL
SELECT '2018-12-18' UNION ALL
SELECT '2018-12-19' UNION ALL
SELECT '2018-12-20' UNION ALL
SELECT '2018-12-21' UNION ALL
SELECT '2018-12-22' UNION ALL
SELECT '2018-12-23' UNION ALL
SELECT '2018-12-24'
) d
LEFT JOIN
(SELECT * FROM T1) AS Temp_T1
ON Temp_T1.Open_Date = d.dt
I am lost how to calculate the same values as I do in excel?
You want to use GROUP BY to make one row for each date in your d derived table.
Then join d to the t1 table where the d.dt is between the open and close dates.
SELECT
d.dt, COUNT(*) AS open_items
FROM
(
SELECT '2018-12-17' AS dt UNION ALL
SELECT '2018-12-18' UNION ALL
SELECT '2018-12-19' UNION ALL
SELECT '2018-12-20' UNION ALL
SELECT '2018-12-21' UNION ALL
SELECT '2018-12-22' UNION ALL
SELECT '2018-12-23' UNION ALL
SELECT '2018-12-24'
) d
LEFT JOIN T1 ON d.dt BETWEEN t1.Open_Date and t1.Close_Date
GROUP BY d.dt;
Output:
+------------+------------+
| dt | open_items |
+------------+------------+
| 2018-12-17 | 1 |
| 2018-12-18 | 3 |
| 2018-12-19 | 2 |
| 2018-12-20 | 4 |
| 2018-12-21 | 4 |
| 2018-12-22 | 4 |
| 2018-12-23 | 3 |
| 2018-12-24 | 3 |
+------------+------------+

mysql running difference with group by

Dataset I am experimenting has the structure as given in this SQLFiddle.
create table readings_tab (id int, site varchar(15), logged_at datetime, reading smallint);
insert into readings_tab values (1, 'A', '2017-08-21 13:22:00', 2500);
insert into readings_tab values (2, 'B', '2017-08-21 13:22:00', 1210);
insert into readings_tab values (3, 'C', '2017-08-21 13:22:00', 3500);
insert into readings_tab values (4, 'A', '2017-08-22 13:22:00', 2630);
insert into readings_tab values (5, 'B', '2017-08-22 13:22:00', 1400);
insert into readings_tab values (6, 'C', '2017-08-22 13:22:00', 3800);
insert into readings_tab values (7, 'A', '2017-08-23 13:22:00', 2700);
insert into readings_tab values (8, 'B', '2017-08-23 13:22:00', 1630);
insert into readings_tab values (9, 'C', '2017-08-23 13:22:00', 3950);
insert into readings_tab values (10, 'A', '2017-08-24 13:22:00', 2850);
insert into readings_tab values (11, 'B', '2017-08-24 13:22:00', 1700);
insert into readings_tab values (12, 'C', '2017-08-24 13:22:00', 4200);
insert into readings_tab values (13, 'A', '2017-08-25 13:22:00', 3500);
insert into readings_tab values (14, 'B', '2017-08-25 13:22:00', 2300);
insert into readings_tab values (15, 'C', '2017-08-25 13:22:00', 4700);
Current Query:
select t.rownum, t.logged_on, t.tot_reading, coalesce(t.tot_reading - t3.tot_reading, 0) AS daily_generation
from
(
select #rn:=#rn+1 AS rownum, date(t.logged_at) AS logged_on, sum(t.reading) AS tot_reading
from readings_tab t, (SELECT #rn:=0) t2
group by date(t.logged_at)
order by date(t.logged_at) desc
) t
left join
(
select #rn:=#rn+1 AS rownum, date(t.logged_at) AS logged_on, sum(t.reading) AS tot_reading
from readings_tab t, (SELECT #rn:=0) t2
group by date(t.logged_at)
order by date(t.logged_at) desc
) t3 on t.rownum = t3.rownum + 1
order by t.logged_on desc;
I am expecting below output. I don't need the formula (3500+2300+4700, etc...) in the result set. Just included it to make it understandable.
-----------------------------------------------------------------
| logged_on | tot_reading | daily_generation |
-----------------------------------------------------------------
| 2017-08-25 | (3500+2300+4700) = 10500 | (10500 - 8750) = 1750 |
| 2017-08-24 | (2850+1700+4200) = 8750 | (8750-8280) = 470 |
| 2017-08-23 | (2700+1630+3950) = 8280 | (8280-7830) = 450 |
| 2017-08-22 | (2630+1400+3800) = 7830 | (7830-7210) = 620 |
| 2017-08-21 | (2500+1210+3500) = 7210 | 0 |
-----------------------------------------------------------------
I cannot figure out why it doesn't produce expected output. Can someone please help?
If using variables make sure they are unique to each subquery else you can get incorrect results. I suggest the following adjusted query (which has some added columns to help follow what is happening):
select
t.rownum, t.logged_on, t.tot_reading
, coalesce(t.tot_reading - t3.tot_reading, 0) AS daily_generation
, t3.rownum t3_rownum
, t3.tot_reading t3_to_read
, t.tot_reading t_tot_read
from
(
select #rn:=#rn+1 AS rownum, date(t.logged_at) AS logged_on, sum(t.reading) AS tot_reading
from readings_tab t
cross join (SELECT #rn:=0) t2
group by date(t.logged_at)
order by date(t.logged_at) desc
) t
left join
(
select #rn2:=#rn2+1 AS rownum, date(t.logged_at) AS logged_on, sum(t.reading) AS tot_reading
from readings_tab t
cross join (SELECT #rn2:=0) t2
group by date(t.logged_at)
order by date(t.logged_at) desc
) t3 on t.rownum = t3.rownum + 1
order by t.logged_on desc
;
Note I also recommend using explicit CROSS JOIN syntax as it leads to easier comprehension for anyone who needs to maintain this query.
Here is the result (& also see http://sqlfiddle.com/#!9/dcb5e2/1 )
| rownum | logged_on | tot_reading | daily_generation | t3_rownum | t3_to_read | t_tot_read |
|--------|------------|-------------|------------------|-----------|------------|------------|
| 5 | 2017-08-25 | 10500 | 1750 | 4 | 8750 | 10500 |
| 4 | 2017-08-24 | 8750 | 470 | 3 | 8280 | 8750 |
| 3 | 2017-08-23 | 8280 | 450 | 2 | 7830 | 8280 |
| 2 | 2017-08-22 | 7830 | 620 | 1 | 7210 | 7830 |
| 1 | 2017-08-21 | 7210 | 0 | (null) | (null) | 7210 |

Updating a table joining another table

Update a table joining 1 more table.
UPDATE t1 SET t1.col1 =1 FROM table1 t1 JOIN table2 t2
ON t1.ID=t2.ID
WHERE t1.Name='Test' AND t2.Age=25;
i get this error,You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FROM table1 t1 JOIN table2 t2 ...
Any thoughts?
Thanks.
There shouldn't be a FROM clause in the UPDATE statement, and the SET clause should follow the full set of table references:
UPDATE table1 t1
JOIN table2 t2 ON t1.ID = t2.ID
SET t1.col1 = 1
WHERE t1.Name = 'Test' AND t2.Age = 25;
Test case:
CREATE TABLE table1 (id int, col1 int, name varchar(20));
CREATE TABLE table2 (id int, age int);
INSERT INTO table1 VALUES (1, 0, 'Test');
INSERT INTO table1 VALUES (2, 0, 'Test');
INSERT INTO table1 VALUES (3, 0, 'No Test');
INSERT INTO table2 VALUES (1, 20);
INSERT INTO table2 VALUES (2, 25);
INSERT INTO table2 VALUES (3, 25);
Result:
SELECT * FROM table1;
+------+------+---------+
| id | col1 | name |
+------+------+---------+
| 1 | 0 | Test |
| 2 | 1 | Test |
| 3 | 0 | No Test |
+------+------+---------+
3 rows in set (0.00 sec)
UPDATE table1 SET col1 = 1
from table1 t1
JOIN table2 t2 ON t1.ID = t2.ID
WHERE t1.Name = 'Test' AND t2.Age = 25;
I have a problem in update join. in table1 i saved username instead of userid.
And for username datatype was varchar(10). When name exceeds 10 then name is saved with 10 character only. Now when i try to update using join query it doesn't works on those fields that have not exact unername in users table,
Notice name bill gat in table1 but the name in users field was bill gates
-es missing.
SELECT * FROM table1;
+------+------+---------+
| id | col1 | created_by|
+------+------+---------+
| 1 | 0 | steve jobs|
| 2 | 1 | bill gat |
| 3 | 0 | Jones |
+------+------+---------+
3 rows in set (0.00 sec)
===I solved this way
UPDATE table1 AS tr
JOIN users AS u ON u.name LIKE CONCAT('%', tr.created_by, '%')
SET tr.created_by=u.id
WHERE tr.created_by IS NOT NULL