mysql join different fields - mysql

i basically have two tables:
id pk_id status
1 2162125 open
2 2162125 fixed
3 2162125 released
4 2162125 closed
and
id pk_id type date
1 2162125 date_close 2018-11-09 18:15:17.212
2 2162125 date_fix 2018-11-09 18:14:37.139
3 2162125 date_confirm 2018-11-09 18:14:11.746
the first table has the status,and the second table has the datetime of when the status changed. I need to find a way to join the two tables,so that fixed is related to fix_date value, closed to date_close value etc.
Sadly im pretty limited,i cant create new tables or change the type values,or i would have just named them the same and be done with it.
edit, this is the expected output:
id pk_id status type date
1 2162125 open null null
2 2162125 fixed date_fix 2018-11-09 18:14:37.139
3 2162125 released null null
4 2162125 closed date_close 2018-11-09 18:15:17.212

You can try using a left join
select a.id,a.pk_id,a.status,b.type,b.date
from table1 a left join table2
on a.status like concat(concat('%',replace(b.type,'date_','')),'%')

You can concat 'date_' with the status and concat the type with '%' and use it as a join clause :
Schema (MySQL v5.7)
CREATE TABLE table1 (
`id` INTEGER,
`pk_id` INTEGER,
`status` VARCHAR(8)
);
INSERT INTO table1
(`id`, `pk_id`, `status`)
VALUES
(1, 2162125, 'open'),
(2, 2162125, 'fixed'),
(3, 2162125, 'released'),
(4, 2162125, 'closed');
CREATE TABLE table2 (
`id` INTEGER,
`pk_id` INTEGER,
`type` VARCHAR(12),
`date` VARCHAR(23)
);
INSERT INTO table2
(`id`, `pk_id`, `type`, `date`)
VALUES
(1, 2162125, 'date_close', '2018-11-09 18:15:17.212'),
(2, 2162125, 'date_fix', '2018-11-09 18:14:37.139'),
(3, 2162125, 'date_confirm', '2018-11-09 18:14:11.746');
Query #1
SELECT t1.id,
t1.pk_id,
t1.status,
t2.type,
t2.date
FROM table1 t1
LEFT JOIN table2 t2
ON t1.pk_id = t2.pk_id
AND CONCAT('date_', t1.status) LIKE CONCAT(t2.type, '%')
ORDER BY t1.id;
Output
| id | pk_id | status | type | date |
| --- | ------- | -------- | ---------- | ----------------------- |
| 1 | 2162125 | open | | |
| 2 | 2162125 | fixed | date_fix | 2018-11-09 18:14:37.139 |
| 3 | 2162125 | released | | |
| 4 | 2162125 | closed | date_close | 2018-11-09 18:15:17.212 |
View on DB Fiddle

UNION of SELECTs with specific where clause could do the trick:
FROM status s
JOIN change c ON c.pk_id=s.pk_id
WHERE s.status = "closed" and c.type = "date_close"
UNION
SELECT s.pk_id, s.status, c.type, c.date
FROM status s
JOIN change c ON c.pk_id=s.pk_id
WHERE s.status = "fixed" and c.type = "date_fix"
...

Related

SQL JOIN : Prefix fields with table name

I have the following tables
CREATE TABLE `constraints` (
`id` int(11),
`name` varchar(64),
`type` varchar(64)
);
CREATE TABLE `groups` (
`id` int(11),
`name` varchar(64)
);
CREATE TABLE `constraints_to_group` (
`groupid` int(11),
`constraintid` int(11)
);
With the following data :
INSERT INTO `groups` (`id`, `name`) VALUES
(1, 'group1'),
(2, 'group2');
INSERT INTO `constraints` (`id`, `name`, `type`) VALUES
(1, 'cons1', 'eq'),
(2, 'cons2', 'inf');
INSERT INTO `constraints_to_group` (`groupid`, `constraintid`) VALUES
(1, 1),
(1, 2),
(2, 2);
I want to get all constraints for all groups, so I do the following :
SELECT groups.*, t.* FROM groups
LEFT JOIN
(SELECT * FROM constraints
LEFT JOIN constraints_to_group
ON constraints.id=constraints_to_group.constraintid) as t
ON t.groupid=groups.id
And get the following result :
id| name | id | name type groupid constraintid
-----------------------------------------------------
1 | group1 | 1 | cons1 | eq | 1 | 1
1 | group1 | 2 | cons2 | inf | 1 | 2
2 | group2 | 2 | cons2 | inf | 2 | 2
What I'd like to get :
group_id | group_name | cons_id | cons_name | cons_type | groupid | constraintid
-------------------------------------------------------------------------------------
1 | group1 | 1 | cons1 | eq | 1 | 1
1 | group1 | 2 | cons2 | inf | 1 | 2
2 | group2 | 2 | cons2 | inf | 2 | 2
This is an example, in my real case my tables have much more columns so using the SELECT groups.name as group_name, ... would lead to queries very hard to maintains.
Try this way
SELECT groups.id as group_id, groups.name as group_name ,
t.id as cons_id, t.name as cons_name, t.type as cons_type,
a.groupid , a.constraintid
FROM constraints_to_group as a
JOIN groups on groups.id=a.groupid
JOIN constraints as t on t.id=a.constraintid
The only difference I see are the names of the columns? Use for that mather an AS-statement.
SELECT
groups.id AS group_id,
groups.name AS group_name,
t.id AS cons_id,
t.name AS cons_name,
t.groupid, t.constraintid
FROM groups
LEFT JOIN
(SELECT * FROM constraints
LEFT JOIN constraints_to_group
ON constraints.id=constraints_to_group.constraintid) as t
ON t.groupid=groups.id
Besides, a better join-construction is:
SELECT G.id AS group_id,
G.name AS group_name,
CG.id AS cons_id,
CG.name AS cons_name,
C.groupid, C.constraintid
FROM constraints_to_group CG
LEFT JOIN constraints C
ON CG.constraintid = C.id
LEFT JOIN groups G
ON CG.groupid = G.id;
Possible duplicate of this issue

can not get correct results with group by in mysql

I have 2 SQL tables
CREATE TABLE A(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR(1) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE B(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
A_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO A VALUES (1, 'A'), (2, 'B'), (3, 'C'), (4, 'A');
INSERT INTO B VALUES (1, 1), (2, 2), (3, 4), (4, 4);
The tables look this way:
select * from A;
+----+------+
| id | name |
+----+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
+----+------+
select * from B;
+----+------+
| id | A_id |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 4 |
| 4 | 4 |
+----+------+
Now I want to find out how many each of the elements from table A are there in table B. Using other words I want to see:
A = 3
B = 1
C = 0
I tried to do this with: SELECT name, count(*) FROM A, B WHERE A.id = A_id GROUP BY A.id;, but it returns something completely weird. Can someone help me?
Query
SELECT a.name,COUNT(b.A_id) as `count`
FROM A a
LEFT JOIN B b
ON a.id=b.A_id
GROUP BY a.name;
Fiddle Demo
You just need a left outer join to handle the condition where there are no A's in B:
SELECT A.Name, COUNT(b.id)
FROM A
LEFT OUTER JOIN B on A.id = B.a_id
GROUP BY A.Name;
SqlFiddle here
You should use a LEFT JOIN, and not GROUP BY A.id, but instead by name:
SELECT A.name, COUNT(B.A_id)
FROM A
LEFT JOIN B ON A.id = B.A_id
GROUP BY A.name;

How to rewrite a NOT IN subquery as join

Let's assume that the following tables in MySQL describe documents contained in folders.
mysql> select * from folder;
+----+----------------+
| ID | PATH |
+----+----------------+
| 1 | matches/1 |
| 2 | matches/2 |
| 3 | shared/3 |
| 4 | no/match/4 |
| 5 | unreferenced/5 |
+----+----------------+
mysql> select * from DOC;
+----+------+------------+
| ID | F_ID | DATE |
+----+------+------------+
| 1 | 1 | 2000-01-01 |
| 2 | 2 | 2000-01-02 |
| 3 | 2 | 2000-01-03 |
| 4 | 3 | 2000-01-04 |
| 5 | 3 | 2000-01-05 |
| 6 | 3 | 2000-01-06 |
| 7 | 4 | 2000-01-07 |
| 8 | 4 | 2000-01-08 |
| 9 | 4 | 2000-01-09 |
| 10 | 4 | 2000-01-10 |
+----+------+------------+
The columns ID are the primary keys and the column F_ID of table DOC is a not-null foreign key that references the primary key of table FOLDER. By using the 'DATE' of documents in the where clause, I would like to find which folders contain only the selected documents. For documents earlier than 2000-01-05, this could be written as:
SELECT DISTINCT d1.F_ID
FROM DOC d1
WHERE d1.DATE < '2000-01-05'
AND d1.F_ID NOT IN (
SELECT d2.F_ID
FROM DOC d2 WHERE NOT (d2.DATE < '2000-01-05')
);
and it correctly returns '1' and '2'. By reading
http://dev.mysql.com/doc/refman/5.5/en/rewriting-subqueries.html
the performance for big tables could be improved if the subquery is replaced with a join. I already found questions related to NOT IN and JOINS but not exactly what I was looking for. So, any ideas of how this could be written with joins ?
The general answer is:
select t.*
from t
where t.id not in (select id from s)
Can be rewritten as:
select t.*
from t left outer join
(select distinct id from s) s
on t.id = s.id
where s.id is null
I think you can apply this to your situation.
select distinct d1.F_ID
from DOC d1
left outer join (
select F_ID
from DOC
where date >= '2000-01-05'
) d2 on d1.F_ID = d2.F_ID
where d1.date < '2000-01-05'
and d2.F_ID is null
If I understand your question correctly, that you want to find the F_IDs representing folders which only contains documents from before '2000-01-05', then simply
SELECT F_ID
FROM DOC
GROUP BY F_ID
HAVING MAX(DATE) < '2000-01-05'
Sample Table and Insert Statements
CREATE TABLE `tleft` (
`id` int(2) NOT NULL,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `tright` (
`id` int(2) NOT NULL,
`t_left_id` int(2) DEFAULT NULL,
`description` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `tleft` (`id`, `name`)
VALUES
(1, 'henry'),
(2, 'steve'),
(3, 'jeff'),
(4, 'richards'),
(5, 'elon');
INSERT INTO `tright` (`id`, `t_left_id`, `description`)
VALUES
(1, 1, 'sample'),
(2, 2, 'sample');
Left Join : SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id ;
Returns Id : 1, 2, 3, 4, 5
Right Join : SELECT l.id,l.name FROM tleft l RIGHT JOIN tright r ON l.id = r.t_left_id ;
Returns Id : 1,2
Subquery Not in tright : select id from tleft where id not in ( select t_left_id from tright);
Returns Id : 3,4,5
Equivalent Join For above subquery :
SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id WHERE r.t_left_id IS NULL;
AND clause will be applied during the JOIN and WHERE clause will be applied after the JOIN .
Example : SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id AND r.description ='hello' WHERE r.t_left_id IS NULL ;
Hope this helps

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

MySQL join, empty rows in junction table

I have three tables I'd like to join in a way that produces all records from one table and any matching records or NULL from another table. It is imperative that all records from the first table be returned. I thought I had done this before but I can't remember when or where and MySQL just isn't playing ball.
SELECT VERSION();
5.0.51a-3ubuntu5.7
/* Some table that may or may not be needed, included to give context */
CREATE TABLE `t1` (
`a` int(4) NOT NULL,
`a_name` varchar(10) NOT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `t2` ( /* "One table" */
`b` int(2) NOT NULL,
`b_name` varchar(10) NOT NULL,
PRIMARY KEY (`b`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `t3` ( /* "Another table" */
`a` int(4) NOT NULL,
`b` int(2) NOT NULL,
PRIMARY KEY (`a`,`b`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO t1 VALUES (1, '1-one'),(2, '1-two'),(3, '1-three');
INSERT INTO t2 VALUES (1, '2-one'),(2, '2-two'),(3, '2-three'),
(4, '2-four'),(5, '2-five');
INSERT INTO t3 VALUES (1,1),(1,2),(1,3),(1,4),(2,2),(2,5);
t3 is a junction table for t1 and t2. The result set I'm looking for should look like this for any a=n:
n=1
b | b_name | a
-------------------
1 | 2-one | 1
2 | 2-two | 1
3 | 2-three | 1
4 | 2-four | 1
5 | 2-five | NULL
n=2
b | b_name | a
-------------------
1 | 2-one | NULL
2 | 2-two | 2
3 | 2-three | NULL
4 | 2-four | NULL
5 | 2-five | 2
n=7
b | b_name | a
-------------------
1 | 2-one | NULL
2 | 2-two | NULL
3 | 2-three | NULL
4 | 2-four | NULL
5 | 2-five | NULL
The value of a in the result set actually isn't important as long as it unambiguously reflects the presence or absence of records in t3 (does that make sense?).
The query
SELECT t2.b, t2.b_name, a
FROM t2
LEFT /* OUTER */ JOIN t3 ON t3.b = t2.b
WHERE (
a = 2
OR
a IS NULL
);
returns
b | b_name | a
-------------------
2 | 2-two | 2
5 | 2-five | 2
Can this be done?
SELECT t2.b, t2.b_name, MAX(IF(a=2, a, NULL)) AS a
FROM t2
LEFT OUTER JOIN t3
ON t3.b = t2.b
GROUP by t2.b
ORDER BY b ASC;
Something like this? If not, can you give an example of the output you'd like to get.
select * from t1
left join t3 using(a)
left join t2 using(b)