How do I properly format this MySQL JOIN Statement? - mysql

I've got a table that looks like:
Table 1 ->
+----+--------+--------+
| id | name | author |
+----+--------+--------+
| 1 | First | Me |
| 2 | Second | You |
+----+--------+--------+
Table 2 ->
+-----+------------+-----------+------------+
| mid | table1_id | key | value |
+-----+------------+-----------+------------+
| 1 | 1 | desc | hello |
| 2 | 1 | begin_day | monday |
| 3 | 1 | end_day | tuesday |
| 4 | 2 | desc | goodbye |
| 5 | 2 | begin_day | wednesday |
| 6 | 2 | end_day | friday |
+-----+------------+-----------+------------+
The relationship here is that the id in table 1 corresponds to the table1_id in table 2.
The output that I'm trying to get is...
+----+---------+---------+-------------+-----------+-----------+
| id | name | author | desc | begin_day | end_day |
+----+---------+---------+-------------+-----------+-----------+
| 1 | First | Me | hello | monday | tuesday |
| 1 | Second | You | goodbye | wednesday | friday |
+----+---------+---------+-------------+-----------+-----------+
I've tried several different join statements -- all a variation of the below. I'm not that well versed in MySQL queries, however.
SELECT * FROM table_1 LEFT JOIN table_2 on table_1.id = table_2.table1_id
Which produces...
+----+----------+----------+----------+------------+-----------+
| id | mid | name | author | key | value |
+----+----------+----------+----------+------------+-----------+
| 1 | 1 | First | Me | desc | hello |
| 1 | 2 | First | Me | begin_day | monday |
| 1 | 3 | First | Me | end_day | tuesday |
| 2 | 4 | Second | You | desc | goodbye |
| 2 | 5 | Second | You | begin_day | wednesday|
| 2 | 6 | Second | You | end_day | friday |
Obviously, iterating over this join statement produces 6 results, 1 for each row in table 2 that matches the id in table 1. How can I avoid this with a proper query statement?
Thank you in advance.

You can use a case statement if you know all of the columns you will be getting, as follows:
Select distinct table_1.*,
case when table_2.key='desc' then value end as desc,
case when table_2.key='begin_day' then value end as begin_day,
case when table_2.key='end_day' then value end as end_day
FROM table_1 LEFT JOIN table_2 on table_1.id = table_2.table1_id
Hope this helps!

SELECT
table_1.*,
MAX(IF(key='desc', value, NULL)) AS 'desc',
MAX(IF(key='begin_day', value, NULL)) AS begin_day,
MAX(IF(key='end_day', value, NULL)) AS end_day
FROM table_1
LEFT JOIN table_2 ON (id = table1_id)
GROUP BY id;

Related

How to query MIN value of MAX subquery with two distinct columns?

I have a table like this:
+---------------+--------------+------+-----+----------+
| Field | Type | Null | Key | Default |
+---------------+--------------+------+-----+----------+
| id | smallint(6) | NO | PRI | NULL |
| Book | tinyint(4) | NO | | NULL |
| Chapter | smallint(6) | NO | | NULL |
| Paragraph | smallint(6) | NO | | NULL |
| Text | text | YES | | NULL |
| RevisionNum | mediumint(9) | NO | PRI | NULL |
+---------------+--------------+------+-----+----------+
mysql> select id,Book,Chapter,Paragraph,RevisionNum FROM MyTable ORDER BY id LIMIT 11;
+-----+------+---------+-----------+-------------+
| id | Book | Chapter | Paragraph | RevisionNum |
+-----+------+---------+-----------+-------------+
| 1 | 1 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 | 2 |
| 2 | 1 | 2 | 2 | 0 |
| 2 | 1 | 2 | 2 | 1 |
| 2 | 1 | 2 | 2 | 2 |
| 2 | 1 | 2 | 2 | 3 |
| 3 | 1 | 2 | 3 | 0 |
| 4 | 1 | 2 | 4 | 0 |
| 4 | 1 | 2 | 4 | 1 |
| 5 | 1 | 3 | 5 | 0 |
+-----+------+---------+-----------+-------------+
To find a book or chapter which has no unrevised paragraph,
I wish to query either the minimum value of the maximums of
all the distinct id's for that chapter or book, or else in
some fashion determine that no id remains unedited (with a
MAX(RevisionNum) of zero).
Most of my attempts to date have ended in errors like this one:
SELECT DISTINCT Book,RecordNum FROM MyTable
-> WHERE 0 < ALL (SELECT DISTINCT RecordNum,MAX(RevisionNum)
FROM MyTable
WHERE MAX(RevisionNum) > 0);
ERROR 1111 (HY000): Invalid use of group function
...And I wasn't using the "GROUP BY" function at all!
The following query produces results, but simply
gives ALL id's, and does not actually show a unique
set of Book records, as requested. How could this happen?
SELECT DISTINCT Book,id,MAX(RevisionNum) FROM MyTable GROUP BY id LIMIT 5;
+------+----+------------------+
| Book | id | MAX(RevisionNum) |
+------+----+------------------+
| 1 | 1 | 30 |
| 1 | 2 | 16 |
| 1 | 3 | 15 |
| 1 | 4 | 10 |
| 1 | 5 | 9 |
+------+----+------------------+
What would the correct query be to give results more like this:
+------+-----+-----------------------+
| Book | id | MIN(MAX(RevisionNum)) |
+------+-----+-----------------------+
| 1 | 5 | 3 |
| 2 | 17 | 1 |
| 3 | 33 | 2 |
| 4 | 147 | 0 |
| 5 | 225 | 2 |
+------+-----+-----------------------+
Are you looking for two levels of aggregation?
select id, book, min(max_revisionnum)
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum
from mytable
group by id, book, chapter, paragraph
) t
group by id, book;
EDIT:
Based on your comment, you can use:
select *
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum,
row_number() over (partition by book order by max(revisionnum) desc) as seqnum
from mytable
group by id, book, chapter, paragraph
) t
where seqnum = 1;
Here is a db<>fiddle.
In older versions of MariaDB, you can use a correlated subquery:
select t.*
from mytable t
where (id, book, chapter, paragraph, revisionnum) = (select t2.id, t2.book, t2.chapter, t2.paragraph, t2.revisionnum
from mytable t2
where t2.book = t.book
order by t2.revisionnum desc
limit 1
);
For this query, try adding an index on (book, revisionnum desc).

Concatenating all matches from a join table into a column

I have two MySQL tables (table_a and table_b) and a join table (table_c).
Table Structures:
table_a:
__________________
| table_a: |
|----------------|
| id |
| result_column |
------------------
table_b:
__________________
| table_b: |
|----------------|
| id |
| name |
------------------
table_c:
__________________
| table_c: |
|----------------|
| id |
| table_a_id |
| table_b_id |
------------------
My Goal:
I want to find a query that will:
Iterate over every table_a record and get the table_a.id value
Find any records in table_c which have a matching table_c.table_a_id value
For each matching record in table_c get the table_c.table_b_id value
Find the record in table_b which has a matching table_b.id value
For that matching record in table_b get the table_b.name value
In table_a, concatenate each matched name value into the corresponding table_a.result_column
Example:
Before the Query:
_______________________ _________________________________ ________________
| table_a: | | table_c: | | table_b: |
|---------------------| |-------------------------------| |--------------|
| id | result_column | | id | table_a_id | table_b_id | | id | name |
|-----|---------------| |-----|------------|------------| |-----|--------|
| 1 | | | 1 | 1 | 3 | | 1 | Kevin |
| 2 | | | 2 | 1 | 4 | | 2 | Jesse |
| 3 | | | 3 | 2 | 2 | | 3 | Karen |
----------------------- | 4 | 3 | 1 | | 4 | Tim |
| 5 | 3 | 5 | | 5 | Lauren |
--------------------------------- ----------------
After the Query:
_______________________ _________________________________ ________________
| table_a: | | table_c: | | table_b: |
|---------------------| |-------------------------------| |--------------|
| id | result_column | | id | table_a_id | table_b_id | | id | name |
|-----|---------------| |-----|------------|------------| |-----|--------|
| 1 | Karen, Tim | | 1 | 1 | 3 | | 1 | Kevin |
| 2 | Jesse | | 2 | 1 | 4 | | 2 | Jesse |
| 3 | Kevin, Lauren | | 3 | 2 | 2 | | 3 | Karen |
----------------------- | 4 | 3 | 1 | | 4 | Tim |
| 5 | 3 | 5 | | 5 | Lauren |
--------------------------------- ----------------
For absolute clarity, I understand that this is incredibly bad practice within a relational data-table. This is as far from normalization as one can get. I would never design a database like this. I was tasked with creating a custom column with a list of values purely for a business case.
The query you seem to want is:
select c.table_a_id, group_concat(b.name separator ', ')
from c join
b
on c.table_b_id = b.id
group by c.table_a_id;
If you actually want to update a, you can put this into an update statement:
update a join
(select c.table_a_id, group_concat(b.name separator ', ') as names
from c join
b
on c.table_b_id = b.id
group by c.table_a_id
) cb
on cb.table_a_id = a.id
set result_column = cb.names
Previous answer is close; but you also required that you only want the records matched in table C that are in A.
The first query does not meet this requirement; but the update statement does, as it will only update records in table A, if the id matches the table_a_id value pulled from table C.
Given what you said you wished for the end result, the update statement above would work.
If you wish to be explicit in your logic, just add a join from table A to table C.
select a.id, group_concat(b.name separator ', ')
from a
join c ON (a.id = c.table_a_id)
join b ON (c.table_b_id = b.id)
group by a.id;

mysql pivot using column and row numbers

I am stuck in this situation where I need to use Row Number and Column Number values from table's columns to derive the output mentioned below. I have tried everything - if/else, case when/then but not helping.
Any help/suggestions are really appreciated!
Here is a mocked up sample data present in db table -
+--------+--------+--------+----------+-------------+
| Record | ColNbr | RowNbr | ColTitle | CellContent |
+--------+--------+--------+----------+-------------+
| 1 | 1 | 1 | Unit | sqf |
| 1 | 1 | 2 | Unit | cm |
| 1 | 2 | 1 | Desc | roof |
| 1 | 2 | 2 | Desc | rod |
| 1 | 3 | 1 | Material | concrete |
| 1 | 3 | 2 | Material | steel |
| 1 | 4 | 1 | Quantity | 100 |
| 1 | 4 | 2 | Quantity | 12 |
| 1 | 1 | 1 | Unit | liter |
| 1 | 1 | 2 | Unit | ml |
| 1 | 2 | 1 | Desc | bowl |
| 1 | 2 | 2 | Desc | plate |
| 1 | 3 | 1 | Material | plastic |
| 1 | 3 | 2 | Material | glass |
| 1 | 4 | 1 | Quantity | 2 |
| 1 | 4 | 2 | Quantity | 250 |
+--------+--------+--------+----------+-------------+
Expected Output -
+--------+--------+--------+----------+-------------+
| Record | Unit | Desc | Material | Quantity |
+--------+--------+--------+----------+-------------+
| 1 | sqf | roof | concrete | 100 |
| 1 | cm | rod | steel | 12 |
| 2 | liter | bowl | plastic | 2 |
| 2 | ml | plate | glass | 250 |
+--------+--------+--------+----------+-------------+
If your actual data is like that, I suggest that you consider to separate the data to; for example, 4 different tables (unit,description,material & a table to store all that ids+quantity). The former 3 tables will store the prerequisite info that get minor updates throughout time and the last table will store all the quantity records. Let's say your tables will look something like this:
CREATE TABLE `Unit` (
unit_id INT,
unit_name VARCHAR(50));
+---------+-----------+
| unit_id | unit_name |
+---------+-----------+
| 1 | sqf |
| 2 | cm |
| 3 | liter |
| 4 | ml |
+---------+-----------+
CREATE TABLE `Description` (
desc_id INT,
desc_name VARCHAR(50));
+---------+-----------+
| desc_id | desc_name |
+---------+-----------+
| 1 | roof |
| 2 | rod |
| 3 | bowl |
| 4 | plate |
+---------+-----------+
CREATE TABLE `Material` (
mat_id INT,
mat_name VARCHAR(50));
+--------+----------+
| mat_id | mat_name |
+--------+----------+
| 1 | concrete |
| 2 | steel |
| 3 | plastic |
| 4 | glass |
+--------+----------+
CREATE TABLE `Records` (
unit_id INT,
desc_id INT,
mat_id INT,
quantity DECIMAL(14,4));
+---------+---------+--------+----------+
| unit_id | desc_id | mat_id | Quantity |
+---------+---------+--------+----------+
| 1 | 1 | 1 | 100 |
| 2 | 2 | 2 | 12 |
| 3 | 3 | 3 | 2 |
| 4 | 4 | 4 | 250 |
+---------+---------+--------+----------+
Then you insert the data accordingly.
Anyhow, for your existing data example, it could be done but there are some concern over whether the unit+desc+material+quantity matching are correct. The only way I can maybe at least think that it's correctly matched is by giving all of the query a similar ORDER BY clause. Hence, the following:
SELECT A.record,A.unit,B.Desc,C.Material,D.Quantity FROM
(SELECT #rn:=#rn+1 AS record,CASE WHEN coltitle='unit' THEN cellcontent END AS Unit
FROM yourtable, (SELECT #rn :=0 ) v
HAVING unit IS NOT NULL
ORDER BY colnbr) A LEFT JOIN
(SELECT #rn1:=#rn1+1 AS record,CASE WHEN coltitle='Desc' THEN cellcontent END AS `Desc`
FROM yourtable, (SELECT #rn1 :=0 ) v
HAVING `Desc` IS NOT NULL
ORDER BY colnbr) B ON a.record=b.record LEFT JOIN
(SELECT #rn2:=#rn2+1 AS record,CASE WHEN coltitle='material' THEN cellcontent END AS Material
FROM yourtable, (SELECT #rn2:=0 ) v
HAVING Material IS NOT NULL
ORDER BY colnbr) C ON a.record=c.record LEFT JOIN
(SELECT #rn3:=#rn3+1 AS record,CASE WHEN coltitle='Quantity' THEN cellcontent END AS Quantity
FROM yourtable, (SELECT #rn3:=0 ) v
HAVING Quantity IS NOT NULL
ORDER BY colnbr) D ON a.record=d.record;
The idea here is to make a sub-query based on COLTITLE then assign a numbering/ranking (#rn,#rn1,#rn2,#rn3) variable to each of the sub-query and join them up using LEFT JOIN. Now, this experiment works to exactly return the output that you need but its not a definite answer because there are some part that is questionable especially on matching the combination correctly. Hopefully, this will give you some idea.

mysql - dynamic select row as column

Update: I want to use dynamic sql to select question as column and put answer in row, like cursor or loop, is that possible?
I want the select result like this
+--------+---------------+--------------------------------------------------------------------------+
| userid | Living Status | This is another question get from row and it's longer than 64 characters |
+--------+---------------+--------------------------------------------------------------------------+
| 19 | married | q2_opt3 |
+--------+---------------+--------------------------------------------------------------------------+
And here is my query
select
userid,
min(if(question.ordering=1,o.name,NULL )) as 'Living Status',
min(if(question.ordering=2,o.name,NULL )) as 'This is another question get from row and it's longer than 64 characters'
from answer
inner join question on question.key_value = answer.key_value
inner join q_option o on question.id = o.question_id and o.value = answer.answer
where userid in (19)
GROUP BY id
The question table is like
+----+----------+---------------------------------------------------------------------------+--------------+
| id | ordering | question | key_value |
+----+----------+---------------------------------------------------------------------------+--------------+
| 1 | 1 | Living Status | livingStatus |
| 2 | 2 | This is another question get from row and it's longer than 64 characters | question_2 |
+----+----------+---------------------------------------------------------------------------+--------------+
The answer table is like
+----+--------+--------------+--------+
| id | answer | key_value | userid |
+----+--------+--------------+--------+
| 1 | 2 | livingStatus | 19 |
| 2 | 3 | question_2 | 19 |
+----+--------+--------------+--------+
The q_option table is like
+----+----------+-------------+-------+
| id | name | question_id | value |
+----+----------+-------------+-------+
| 1 | single | 1 | 1 |
| 2 | married | 1 | 2 |
| 3 | divorced | 1 | 3 |
| 4 | q2_opt1 | 2 | 1 |
| 5 | q2_opt2 | 2 | 2 |
| 6 | q2_opt3 | 2 | 3 |
+----+----------+-------------+-------+

Counting the number of times something appears in a column and recording it

So in order to know how many people in a table are called Johnny I would need to excecute the following query.
Query:
Select count(*) from mytable where first = 'Johnny';
It would give me 2 as the result.
What I wish to do however is record this number in the count column so that the end result comes out like this.
+--------+----------+
| First | COUNT |
+--------+----------+
| Johnny | 2 |
| Diane | 1 |
| Johnny | 2 |
| Harold | 1 |
| Amy | 3 |
| Roy | 2 |
| Amy | 3 |
| Amy | 3 |
| Roy | 2 |
+--------+----------+
Is there any query or procedure capable of resulting in this type of output?
To get your exact output, you need to use a subquery:
select
mytable.First,
counts.`COUNT`
from
mytable
join (
select
First,
count(*) `COUNT`
from
mytable
group by
First
) counts on mytable.First = counts.First;
Try this:
SELECT T1.First, T2.COUNT
FROM mytable T1 JOIN
(SELECT First, COUNT(*) as COUNT
FROM mytable
GROUP BY First) as T2 ON T1.First=T2.First
The result will be:
+--------+----------+
| First | COUNT |
+--------+----------+
| Johnny | 2 |
| Diane | 1 |
| Johnny | 2 |
| Harold | 1 |
| Amy | 3 |
| Roy | 2 |
| Amy | 3 |
| Amy | 3 |
| Roy | 2 |
+--------+----------+