Extending Values in Joined Tables with MYSQL - mysql

I currently have a table of concentrations, which are linked to a table of elements. There is a concentration table
|conc_id|element_id|conc|
and and element table
|element_id|symbol|
The concentration table only has element_ids for each conc_id that have non-zero concentrations.
What I am trying to do is create a query which will, for each concentration_id, list all the elements in order, with their concentration, regardless of whether they're non-zero or not. I have tried this in a number of increasingly complicated ways (starting with a RIGHT JOIN) but this always outputs a NULL conc_id when there is none of that element in it. The output I am looking for is something like this:
|conc_id|element_id|symbol|conc|
|1 |1 |H |1.2 |
|1 |2 |He |NULL|
|1 |3 |Li |2.3 |
...
|3 |1 |H |4.5 |
|3 |2 |He |NULL|
|3 |3 |Li |NULL|
...
And so on. Is there a way I can do this without having NULL conc_id?
Thanks for any help in advance...

First, you have to build a table that contains all combinations between conc_id and element_id:
SELECT DISTINCT conc_id, elements.element_id FROM concentrations, elements;
Which gives you the following table:
| conc_id | element_id |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
...
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
...
Once you have such a table, joining it with concentrations and elements should be fairly simple. For example:
SELECT combinations.conc_id, combinations.element_id, symbol, conc
FROM
(SELECT DISTINCT conc_id, elements.element_id FROM concentrations, elements) AS combinations
LEFT JOIN concentrations ON (combinations.conc_id = concentrations.conc_id AND combinations.element_id = concentrations.element_id)
LEFT JOIN elements ON (combinations.element_id = elements.element_id);
Result:
| conc_id | element_id | symbol | conc |
| 1 | 1 | H | 1.2 |
| 1 | 2 | He | NULL |
| 1 | 3 | Li | 2.3 |
...
| 3 | 1 | H | 4.5 |
| 3 | 2 | He | NULL |
| 3 | 3 | Li | NULL |
...

To be honest, I think the best way would be to actually fill in the missing data with zero values in the conc column. Otherwise, you're trying to hack it to display data that you don't actually have and it would be much more tangible than keeping null values anyway...
Assuming there are no performance consideration that this would impede, of course.

Assuming you do not have a simple list of all the conc_id somewhere, you could try something like:
SELECT c.conc_id, e.element_id, e.symbol, c2.conc
FROM
(SELECT DISTINCT conc_id FROM concentration c) c
INNER JOIN element e
LEFT JOIN concentration c2 ON c2.conc_id = c.conc_id AND c2.element_id = e.element_id
ORDER BY c.conc_id, e.element_id
The logic is:
Get a list of all unique conc_id derived table c
Do a cross join against element (for each row of c, list all row of element)
Left join against the full concentration table.
If you have another table with all the conc_id, you could avoid the derived table entirely.

select conc_id, conc.element_id, symbol, concentration from concentration_table as conc
join element_table as elem on conc.element_id = elem.element_id
order by conc.conc_id asc
This gives me this table:
conc_id element_id concentration element_id symbol
1 1 1.2 1 H
1 2 0 2 He
1 3 2.3 3 Li
3 1 4.5 1 H
3 2 0 2 He
3 3 0 3 Li
And these are the starting tables:
element_table:
element_id symbol
1 H
2 He
3 Li
concentration table:
conc_id element_id concentration
1 1 1.2
1 2 0
1 3 2.3
3 1 4.5
3 2 0
3 3 0
EDIT: edited the query to obtain correct table values requested

Related

How can I merge two strings of comma-separated numbers in MySQL?

For example, there are three rooms.
1|gold_room|1,2,3
2|silver_room|1,2,3
3|brown_room|2,4,6
4|brown_room|3
5|gold_room|4,5,6
Then, I'd like to get
gold_room|1,2,3,4,5,6
brown_room|2,3,4,6
silver_room|1,2,3
How can I achieve this?
I've tried: select * from room group by name; And it only prints the first row. And I know CONCAT() can combine two string values.
Please use below query,
select col2, GROUP_CONCAT(col3) from data group by col2;
Below is the Test case,
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ab35e8d66ffe3ac6436c17faf97ee9af
I'm not making an assumption that the lists don't have elements in common on separate rows.
First create a table of integers.
mysql> create table n (n int primary key);
mysql> insert into n values (1),(2),(3),(4),(5),(6);
You can join this to your rooms table using the FIND_IN_SET() function. Note that this cannot be optimized. It will execute N full table scans. But it does create an interim set of rows.
mysql> select * from n inner join rooms on find_in_set(n.n, rooms.csv) order by rooms.room, n.n;
+---+----+-------------+-------+
| n | id | room | csv |
+---+----+-------------+-------+
| 2 | 3 | brown_room | 2,4,6 |
| 3 | 4 | brown_room | 3 |
| 4 | 3 | brown_room | 2,4,6 |
| 6 | 3 | brown_room | 2,4,6 |
| 1 | 1 | gold_room | 1,2,3 |
| 2 | 1 | gold_room | 1,2,3 |
| 3 | 1 | gold_room | 1,2,3 |
| 4 | 5 | gold_room | 4,5,6 |
| 5 | 5 | gold_room | 4,5,6 |
| 6 | 5 | gold_room | 4,5,6 |
| 1 | 2 | silver_room | 1,2,3 |
| 2 | 2 | silver_room | 1,2,3 |
| 3 | 2 | silver_room | 1,2,3 |
+---+----+-------------+-------+
Use GROUP BY to reduce these rows to one row per room. Use GROUP_CONCAT() to put the integers together into a comma-separated list.
mysql> select room, group_concat(distinct n.n order by n.n) as csv
from n inner join rooms on find_in_set(n.n, rooms.csv) group by rooms.room
+-------------+-------------+
| room | csv |
+-------------+-------------+
| brown_room | 2,3,4,6 |
| gold_room | 1,2,3,4,5,6 |
| silver_room | 1,2,3 |
+-------------+-------------+
I think this is a lot of work, and impossible to optimize. I don't recommend it.
The problem is that you are storing comma-separated lists of numbers, and then you want to query it as if the elements in the list are discrete values. This is a problem for SQL.
It would be much better if you did not store your numbers in a comma-separated list. Store multiple rows per room, with one number per row. You can run a wider variety of queries if you do this, and it will be more flexible.
For example, the query you asked about, to produce a result with numbers in a comma-separated list is more simple, and you don't need the extra n table:
select room, group_concat(n order by n) as csv from rooms group by room
See also my answer to Is storing a delimited list in a database column really that bad?

MySQL In result are NULL's when join left 3 tables

I have problem with result when join 3 tables because I have in some place NULL a should be number or empty cell.
My tables in database:
Table nr 1: rysunek
id_rys | nazwa_rys | nazwa_klienta | ...
3 |01_116230_C0 |PHILIPS
7 |11_002177_A0 |P&G
20 |01_101854_B0 |MARS FOOD
333 |None |None
( + 7 columns which do not use in this query)
Table nr 2: artykul
id_art |id_rys |nazwa_art | id_status | ...
1 |3 |00_16_1234 | 1
2 |7 |00_16_1235 | 3
3 |7 |00_16_1236 | 0
4 |333 |00_16_1237 | 0
( + 10 columns which do not use in this query)
Table nr 3: statusy
id_status |kod_status
1 |IA
2 |NC
3 |861
Mysql query looks like this:
SELECT r.nazwa_klienta
, r.nazwa_rys
, a.nazwa_art
, s.kod_status
FROM artykul a
LEFT
JOIN rysunek r
ON a.id_rys = r.id_rys
LEFT
JOIN statusy s
ON a.id_status = s.id_status;
And result looks like this:
nazwa_klienta | nazwa_rys | nazwa_art | kod_status
NULL | NULL | 00_16_1234 | IA
NULL | NULL | 00_16_1235 | 861
P&G | 11_002177_A0 | 00_16_1236 | NULL
None | None | 00_16_1237 | NULL
I need to the result of query above look like this:
nazwa_klienta | nazwa_rys | nazwa_art | kod_status
PHILIPS | 01_116230_C0 | 00_16_1234 | IA
P&G | 11_002177_A0 | 00_16_1235 | 861
P&G | 11_002177_A0 | 00_16_1236 | [empty cell]
None | None | 00_16_1237 | [empty cell]
How should looks like my query? I tried all join methods but none of them work.
Maybe I should change structure of my tables? I'm waiting for some suggestion from somebody... :)
Left joins are going to create empty entries for your rysunek columns whenever there is no row in that table that matches artykul's. If you don't want them listed, then don't use an outer join between those two tables (I note you didn't list them in your desired output).
For the other NULLs, from kod_status, since it is a numeric column, pretty much your choices are: take the null; or turn it into a 0 with a COALESCE(kod_status,0), or cast the result to a string and turn its nulls into empty strings. There are command-line options in the mysql command-line tool (which your output appears to be from) (see http://dev.mysql.com/doc/refman/5.7/en/mysql-commands.html ) there may be a way to change how it outputs nulls if that would fit your use-model needs.

Rewriting a select query

I have a rather simple (I think) question at hand. The example tables and the result I need are provided below (in reality those tables containt much more columns and data, I jest left what is relevant). There is also the query which returns exactly what I need. However, I dont like rather crude way in which it works (I dont like subqueries in general). The question is, how can I rewrite the query so it will automatically react to more columns appearing in TABLE2 in the future? Right now if the "z" column would be added to TABLE2, I need to modify each query in the code and add one more relevant subquery. I just want the select to read the entire content of TABLE2 and translate the id numbers to corresponding strings from TABLE1.
TABLE1
-----------------
id |x |
-----------------
567 |AAA |
345 |BBB |
341 |CCC |
827 |DDD |
632 |EEE |
503 |FFF |
945 |GGG |
234 |HHH |
764 |III |
123 |JJJ |
-----------------
TABLE2
-------------------------
id |x |y |
-------------------------
1 |123 |341 |
2 |567 |632 |
3 |345 |945 |
4 |764 |503 |
5 |234 |827 |
-------------------------
THE RESULT I NEED
-----------------
A |B |
-----------------
JJJ |CCC |
AAA |EEE |
BBB |GGG |
III |FFF |
HHH |DDD |
-----------------
The query I have
SELECT
(SELECT `x` FROM `TABLE1` WHERE `TABLE2`.`x` LIKE `TABLE1`.`id` LIMIT 1) as A,
(SELECT `x` FROM `TABLE1` WHERE `TABLE2`.`y` LIKE `TABLE1`.`id` LIMIT 1) as B
FROM `TABLE2` ORDER BY `id` DESC;
You might want to restructure your data model:
Instead of:
-------------------------
id |x |y |
-------------------------
1 |123 |341 |
2 |567 |632 |
3 |345 |945 |
4 |764 |503 |
5 |234 |827 |
-------------------------
You would have:
----------------------
col_id |col |
----------------------
1 |x |
2 |y |
----------------------
---------------------------
id |col_id |col_val |
---------------------------
1 |1 |123 |
1 |2 |341 |
2 |1 |567 |
2 |2 |632 |
etc
---------------------------
Probably not worth the hassle (you would effectively need to pivot when you're accessing multiple columns at a time) but it would allow you to do the query that you want across all current and future columns.
You can't do that with a plain select.
What you can do is creating a view with the translated values. You still have to modify the view when the original table is changed but your queries don't have to.
You can use dynamic sql statements, but still you can use the dynamic statements only if you are sure that table 2 will have the columns of same type like x and y(Apart from id).
Let me know if you are not sure how to write it.
All the best.

Combining a LEFT JOIN with GROUP BY, whilst also including NULLs

I'm having trouble combining tables that have a one-to-many mapping using LEFT JOIN and GROUP BY.
I have the following table with a unique ID (in the illustrative example this is house_number)
Houses:
|house_number| bedrooms|
|0 | 4 |
|1 | 3 |
|2 | 1 |
And I want to LEFT JOIN with a second table USING the unique ID, where the second table may or may not have multiple entries per unique ID. E.g,
Occupants:
| house_number | occupant_id | type |
| 0 | 3 | 19 |
| 0 | 1 | 20 |
| 0 | 2 | 21 |
| 2 | 7 | 20 |
Now what I want to achieve is exactly ONE entry per house number, but giving a preference in the LEFT JOIN to occupants with a type of 20, whilst also keeping those houses which do not have any occupants listed, e.g,
|house_number| bedrooms| occupant_id | type |
|0 | 4 | 1 | 20 |
|1 | 3 | null | null |
|2 | 1 | 7 | 20 |
I can use a GROUP BY to achieve only one entry per house, however, I need to ensure that the occupant row returned with it (if it exists) has type = 20.
If I simply use a WHERE (type = 20), then I wouldn't get an entry returned for house_number = 1.
How would I achieve this final table?
What about trying WHERE (type = 20 OR type is null) condition instead?
SELECT h.house_number,h.bedrooms
, o.occupant_id,o.ztype
FROM houses h
LEFT JOIN occupants o ON h.house_number = o.house_number
AND o.ztype =20
;
BTW I had to replace "type" by "ztype" because type is a reserved word in Postgres.

SQL: Compare if column values in two tables are equal

I'm working on mysql and have two tables with the same schema:
preTrial
|id|accusedId|articleid|
------------------------
|1 | 1 | 1 |
|2 | 1 | 2 |
|3 | 1 | 3 |
|4 | 2 | 1 |
|5 | 2 | 2 |
trial
|id|accusedId|articleid|
------------------------
|1 | 1 | 1 |
|2 | 1 | 2 |
|3 | 2 | 1 |
|4 | 2 | 2 |
I want to get those accusedIds where all the articleIds of the first and the second tables are equal.
The above example should only return the accusedId 2, cause for accusedId 1 there is no articleId 3 in the second table.
I hope you understand what i mean. I'm currently writing my thesis in law, and the the time i was into sql is long gone by. Of course i already did some research, and tried several joins, but i was not able to find a solution. Hopefully you can help me.
Try something like this:
select a.accusedId , sum(a.accusedid) as cnt_a, sum(coalesce(b.accusedId, 0)) as cnt_b
from a left join b on a.accusedId = b.accusedId and a.articleId = b.articleId
group by accusedId
having cnt_a = cnt_b
I haven't even run that, so it might be a little off, but give it a lash. What it's doing is returning zeroes for a row in a not matched by b, so the HAVING clause will filter your grouped results to those where the article counts are equal.