MYSQL - How to merge 2 rows of related data into 1 - mysql

I am new to mysql and tried to research it but couldn't find any solution. I have a table like this:
| SW_Pair1 | SW_Pair2 | Pair1_VLAN1| Pair1_VLAN2| Pair2_VLAN1| Pair2_VLAN2| Inter | Mgmt| OSPF| Env | Domain|
|-----------------|-----------------|------------|------------|------------|------------|-------|-----|-----|-----|-------|
| Switch1.abc.com | Switch2.abc.com | VLAN-111 | VLAN-333 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch2.abc.com | Switch1.abc.com | VLAN-222 | VLAN-444 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch3.abc.com | Switch4.abc.com | VLAN-121 | VLAN-123 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch4.abc.com | Switch3.abc.com | VLAN-515 | VLAN-717 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch5.abc.com | Switch6.abc.com | VLAN-919 | VLAN-101 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch6.abc.com | Switch5.abc.com | VLAN-105 | VLAN-108 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch7.abc.com | Switch8.abc.com | VLAN-110 | VLAN-115 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
| Switch8.abc.com | Switch7.abc.com | VLAN-199 | VLAN-200 | Unknown | Unknown | 47 | 24 | 0.1 | Dev | abc |
Lets take first 2 rows as an example.
SW_Pair1 in row 1 == SW_Pair2 in row 2
SW_Pair1 in row 2 == SW_Pair2 in row 1
I put them in next row but they can be anywhere in database. Now I would like to merge these 2 so that data in Pair1_VLAN1 and Pair1_VLAN2 in row 2 goes in Pair2_VLAN1 and Pair2_VLAN2 of row 1 and then row 2 disappears. So, here is how the table should look after merge:
| SW_Pair1 | SW_Pair2 | Pair1_VLAN1| Pair1_VLAN2| Pair2_VLAN1| Pair2_VLAN2| Inter | Mgmt| OSPF| Env | Domain|
|-----------------|-----------------|------------|------------|------------|------------|-------|-----|-----|-----|-------|
| Switch1.abc.com | Switch2.abc.com | VLAN-111 | VLAN-333 | VLAN-222 | VLAN-444 | 47 | 24 | 0.1 | Dev | abc |
| Switch3.abc.com | Switch4.abc.com | VLAN-121 | VLAN-123 | VLAN-515 | VLAN-717 | 47 | 24 | 0.1 | Dev | abc |
and so on ..
I am using python 2.7 to push data to sql.
Edit:
I tried below query to add additional checks on DELETE but it failed:
UPDATE yourTable AS a
DELETE FROM yourTable AS b ON a.SW_Pair1 = b.SW_Pair2 AND a.SW_Pair2 = b.SW_Pair1
WHERE Pair2_VLAN1 IS Unknown;
Or better, can it SET the values of Pair1_VLAN1 and Pair1_VLAN2 rows of second switch after it moves it data to switch 1? Maybe over-write the vlan to something like "MERGED". I can then safely remove anything that has "MERGED" in Pair1_VLAN1 and Pair1_VLAN2. I know it will only say that when it's data was successfully got moved to another row.
EDIT2:
nvm .. figured it out. See below:
UPDATE yourTable AS a
JOIN yourTable AS b ON a.SW_Pair1 = b.SW_Pair2 AND a.SW_Pair2 = b.SW_Pair1
SET a.Pair2_VLAN1 = b.Pair1_VLAN1,
a.Pair2_VLAN2 = b.Pair1_VLAN2,
b.Pair1_VLAN1 = "MERGED",
b.Pair1_VLAN2 = "MERGED
WHERE a.SW_Pair1 < a.SW_Pair2;

First update the first row in each pair with the data from the matching row:
UPDATE yourTable AS a
JOIN yourTable AS b ON a.SW_Pair1 = b.SW_Pair2 AND a.SW_Pair2 = b.SW_Pair1
SET a.Pair2_VLAN1 = b.Pair1_VLAN1,
a.Pair2_VLAN2 = b.Pair1_VLAN2
WHERE a.SW_Pair1 < a.SW_Pair2;
The WHERE clause ensures that only one row in each pair (the one with the lower name in SW_Pair1) is updated.
Then delete the rows that weren't updated. They will still have NULL in the columns that were updated by the first query.
DELETE FROM yourTable
WHERE Pair2_VLAN1 IS NULL;
This assumes that there are matching rows for everything. If you need something safer, you'll need to do a join that checks that there's a matching row with the opposite names.
DELETE a FROM yourTable AS a
JOIN yourTable AS b ON a.SW_Pair1 = b.SW_Pair2 AND a.SW_Pair2 = b.SW_Pair1
WHERE a.Pair2_VLAN1 IS NULL

I'm sure there is a cleaner way to do this, but this is a hacky way I came up with.
select
a.sw_pair1,
a.sw_pair2,
a.pair1_vlan1,
a.pair2_vlan1 as pair1_vlan2,
b.pair1_vlan1 as pair2_vlan1,
b.pair2_vlan1 as pair2_vlan2
from TABLENAME a
join TABLENAME b on a.sw_pair1 = b.sw_pair2
where cast(substring_index(substring_index(a.sw_pair1, '.abc.com', 1), 'Switch', -1) as unsigned) % 2 > 0
I'm using the modulo (% 2) to make sure we get the odd numbers in the first column only, therefore having the even numbers in column 2. I'd be curious to see if someone else can come up with a cleaner solution for that than I did. If so, that would help me with some of the things I do from time to time.

This method worked for me and seems simpler than the current answers. This gave your desired output from the sample data.
SELECT
a.SW_Pair1,
a.SW_Pair2,
a.Pair1_VLAN1,
a.Pair1_VLAN2,
b.Pair1_VLAN1 as Pair2_VLAN1,
b.Pair1_VLAN2 as Pair2_VLAN2
FROM test as a , test as b
WHERE a.SW_Pair1 = b.SW_Pair2 AND a.SW_Pair2>b.SW_Pair2;

If you want to store the merged data into table, then #Barmar's solution will work perfectly.
But if you just want to display the data then following query will get the job done:
select least(t1.SW_Pair1,t1.SW_Pair2),greatest(t1.SW_Pair1,t1.SW_Pair2),
t2.Pair1_VLAN1,t2.Pair1_VLAN2,
t1.Pair1_VLAN1 as Pair2_VLAN1,t1.Pair1_VLAN2 as Pair2_VLAN2
from tablet1 as t1
inner join tablet2 as t2
on t2.SW_Pair1 = t1.SW_Pair2 and t2.SW_Pair2=t1.SW_Pair1
group by least(t1.SW_Pair11,t1.SW_Pair2),greatest(t1.SW_Pair1,t1.SW_Pair2);
Hope it helps!

Related

MySQL: Add Default Value to Joined Table when Row not Found

System info:
$ uname -srvm
Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
$ mysql --version
mysql Ver 8.0.31-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
I am very inexperienced with MySQL & have been looking for an answer to this for about half a week. I am working with two tables named character_stats & halloffame that I want to join in a query. They look like this:
mysql> SELECT name, level FROM character_stats;
+-----------+-------+
| name | level |
+-----------+-------+
| foo | 0 |
| bar | 0 |
| baz | 3 |
| tester | 4 |
| testertoo | 2 |
+-----------+-------+
mysql> SELECT * from halloffame;
+----+-----------+----------+--------+
| id | charname | fametype | points |
+----+-----------+----------+--------+
| 1 | bar | T | 0 |
| 2 | foo | T | 0 |
| 3 | baz | T | 0 |
| 4 | tester | T | 0 |
| 5 | testertoo | T | 0 |
| 6 | tester | D | 40 |
| 7 | tester | M | 92 |
| 8 | bar | M | 63 |
+----+-----------+----------+--------+
In my query, I want to display all the rows from character_stats & I want to join the points column from halloffame for fametype='M'. If there is no row for fametype='M', I want to set points to 0 for that character name, instead of omitting the entire row as is done in the following:
mysql> SELECT name, level, points FROM character_stats JOIN
-> (SELECT charname, points FROM halloffame WHERE fametype='M')
-> AS hof ON (hof.charname=name);
+--------+-------+--------+
| name | level | points |
+--------+-------+--------+
| tester | 4 | 92 |
| bar | 0 | 63 |
+--------+-------+--------+
So I want it to output this:
+-----------+-------+--------+
| name | level | points |
+-----------+-------+--------+
| foo | 0 | 0 |
| bar | 0 | 63 |
| baz | 3 | 0 |
| tester | 4 | 92 |
| testertoo | 2 | 0 |
+-----------+-------+--------+
I have tried to learn how to use IFNULL, IF-THEN-ELSE, CASE, COALESCE, & COUNT statements from what I have found in documentation & answers on stackoverflow.com. But as I said, I am very inexperienced & don't know how to implement them.
The following works on its own:
SELECT IFNULL((SELECT points FROM halloffame WHERE fametype='M'
AND charname='foo' LIMIT 1), 0) as points;
But I don't know how to join it to the character_stats table. The following would work if I knew how to get the value of character_stats.name before COALESCE is called:
SELECT name, level, 'M' AS fametype, points FROM character_stats
JOIN (SELECT COALESCE((SELECT points FROM halloffame WHERE
fametype='M' AND charname=name LIMIT 1), 0) AS points) AS hof;
According to Adding Default Values on Joining Tables I should be able to use CROSS JOIN, but I am doing something wrong as it still results in Unknown column 'cc.name' in 'where clause':
SELECT name, level, points FROM character_stats
CROSS JOIN (SELECT DISTINCT name FROM character_stats) AS cc
JOIN (SELECT COALESCE((SELECT points FROM halloffame WHERE
fametype='M' AND charname=cc.name LIMIT 1), 0) AS points) AS hof;
Some references I have looked at:
Returning a value even if no result
Usage of MySQL's "IF EXISTS"
Return Default value if no row found
MySQL.. Return '1' if a COUNT returns anything greater than 0
How do write IF ELSE statement in a MySQL query
Simple check for SELECT query empty result
Is there a function equivalent to the Oracle's NVL in MySQL?
MySQL: COALESCE within JOIN
Unknown Column In Where Clause With Join
Adding Default Values on Joining Tables
https://www.tutorialspoint.com/returning-a-value-even-if-there-is-no-result-in-a-mysql-query
I found that I can do the following:
SELECT name, level, COALESCE((SELECT points FROM
halloffame WHERE fametype='M' AND charname=name
LIMIT 1), 0) AS points FROM character_stats;
Though I would still like to know how to do it within a JOIN statement.

MySQL - Interchange column value from one row onto another

SELECT OlineDate, OlineOrder, OlineDesc, OlineGroup, OlinePrice
FROM tblorderlines
WHERE DATE(OlineDate) = '2019-10-19' AND OlineOrder = 170
AND OlineGroup IN ('spec')
|====================================================================|
| OlineOrder |OlineDate | OlineDesc | OlineGroup | OlinePrice |
|============+============+===========+================+=============|
| 10 | 2019-10-19 | Coupon | spec | -2.42 |
|------------+------------+-----------+----------------|-------------|
| 10 | 2019-10-19 | 10% OFF | spec | 0.00 |
|------------+------------------------+----------------+-------------|
I am looking for a query that would interchange the '10% off' value over the 'Coupon' value. The only results I've found that may produce the result I want are pivot tables but those don't exist in MySQL. Is there another route I can take?

INNER JOIN same value, but the difference is the other table are having extra word in front of the value

As I said in the title, or maybe my question is a little bit confusing. Here it is....
So, I want to combine 2 tables using INNER JOIN (ofcourse) with some difference.
This is my tables
Table 1, PK = steam_id
SELECT * FROM nmrihstats ORDER BY points DESC LIMIT 4;
+---------------------+----------------+--------+-------+--------+
| steam_id | name | points | kills | deaths |
+---------------------+----------------+--------+-------+--------+
| STEAM_0:1:88467338 | Alan14 | 50974 | 5438 | 12 |
| STEAM_0:0:95189481 | ? BlacKEaTeR ? | 35085 | 24047 | 316 |
| STEAM_0:1:79891668 | Lowell | 34410 | 44076 | 993 |
| STEAM_0:1:170948255 | Rain | 29780 | 30167 | 278 |
+---------------------+----------------+--------+-------+--------+
4 rows in set (0.01 sec)
Table 2, PK = authid
SELECT * FROM store_players ORDER BY credits DESC LIMIT 4;
+-----+-------------+---------------+---------+--------------+-------------------+
| id | authid | name | credits | date_of_join | date_of_last_join |
+-----+-------------+---------------+---------+--------------+-------------------+
| 309 | 1:88467338 | Alan14 | 15543 | 1475580801 | 1482260232 |
| 368 | 1:79891668 | Lowell | 10855 | 1475603908 | 1482253619 |
| 256 | 1:128211488 | Fuck[U]seLF | 10422 | 1475570061 | 1482316480 |
| 428 | 1:74910707 | Mightybastard | 7137 | 1475672897 | 1482209608 |
+-----+-------------+---------------+---------+--------------+-------------------+
4 rows in set (0.00 sec)
Now, how can I use INNER JOIN without doing like removing "STEAM_0:" or adding it. Also with explanation, please
You can join witn like operator, e.g.:
SELECT n.*, sp.*
FROM nmrihstats n JOIN store_players sp ON n.steam_id LIKE CONCAT('%', sp.authid);
Here's the SQL Fiddle.
Another approach would be to use String functions of MySQL to extract out relevant part from steam_id but I believe that's not what you want:
SELECT SUBSTR(steam_id, LOCATE('STEAM_0:', steam_id) + CHAR_LENGTH('STEAM_0:'))
FROM nmrihstats;
it is not possible, you need to remove "STEAM_0:", matching with WHERE, using substring for remove STEAM_0: from column equals to column in other table, or a new field into the T1 without "STEAM_0:", that 2 columns match for INNER JOIN

check if row has a particular value (perl,mysql)

I'm trying to identify if a row has the value '1' anywhere in it (AKA any column). Is there an easy way to do this without searching every column with fetchrow_array? I've included a small table I've been working with as an example.
mysql> select * from Case_Tracking;
+--------------------+---------+---------+----------+---------+----------+---------------+
| accession_number | cyp2d6 | cyp2c19 | factorII | factorV | apoe | vkorc1_cyp2c9 |
+--------------------+---------+---------+----------+---------+----------+---------------+
| AIB14-1116-0000453 | Luminex | Luminex | Hologic | 1 | ABI 7500 | Genmark |
| AIB14-1123-0000074 | NULL | Luminex | Hologic | Hologic | ABI 7500 | Genmark |
+--------------------+---------+---------+----------+---------+----------+---------------+
It would be better to search every column using SQL (WHERE accession_number = '1' OR cyp2d6 = '1' OR ...) since you'd only need to fetch matching results.

MySQL: optimize query for scoring calculation

I have a data table that I use to do some calculations. The resulting data set after calculations looks like:
+------------+-----------+------+----------+
| id_process | id_region | type | result |
+------------+-----------+------+----------+
| 1 | 4 | 1 | 65.2174 |
| 1 | 5 | 1 | 78.7419 |
| 1 | 6 | 1 | 95.2308 |
| 1 | 4 | 1 | 25.0000 |
| 1 | 7 | 1 | 100.0000 |
+------------+-----------+------+----------+
By other hand I have other table that contains a set of ranges that are used to classify the calculations results. The range tables looks like:
+----------+--------------+---------+
| id_level | start | end | status |
+----------+--------------+---------+
| 1 | 0 | 75 | Danger |
| 2 | 76 | 90 | Alert |
| 3 | 91 | 100 | Good |
+----------+--------------+---------+
I need to do a query that add the corresponding 'status' column to each value when do calculations. Currently, I can do that adding the following field to calculation query:
select
...,
...,
[math formula] as result,
(select status
from ranges r
where result between r.start and r.end) status
from ...
where ...
It works ok. But when I have a lot of rows (more than 200K), calculation query become slow.
My question is: there is some way to find that 'status' value without do that subquery?
Some one have worked on something similar before?
Thanks
Yes, you are looking for a subquery and join:
select s.*, r.status
from (select s.*
from <your query here>
) s left outer join
ranges r
on s.result between r.start and r.end
Explicit joins often optimize better than nested select. In this case, though, the ranges table seems pretty small, so this may not be the performance issue.