Select from two tables based on values of one of the table - mysql

1) Table1 say table1 with structure as :
moduleID | moduleName
10 | XYZ
20 | PQR
30 | ABC
2) Table2 say table2 with structure as :
moduleID | Level | Value
10 | 1 | 20
10 | 2 | 30
30 | 3 | 40
10 | 3 | 50
20 | 2 | 30
moduleID being primary key in table1,and value of the column level can have values 1 to 3.
Now it is required to display the data as follows :
moduleID | moduleName | Level1 | Level2 | Level3
10 | XYZ | 20 | 30 | 50
20 | PQR | NULL | 30 | NULL
30 | ABC | NULL | NULL | 50
In simpler terms, values of column Level in table2 is displayed as Level1, Level2 and Level3 and values corresponding to each level is populated in the corresponding moduleID row.
Any help on this? beginner here in SQL. Something to do with Views?

You can use conditional aggregation:
select t1.moduleID, t1.moduleName,
MAX(CASE WHEN Level = 1 THEN Value END) Level1,
MAX(CASE WHEN Level = 2 THEN Value END) Level2,
MAX(CASE WHEN Level = 3 THEN Value END) Level3
from table1 as t1
left join table2 as t2 on t1.moduleID = t2.moduleID
group by t1.moduleID, t1.moduleName

Refer this all process it will work fine for your expected answer.
CREATE TABLE Table1
(moduleName VARCHAR(50),moduleID INT)
GO
--Populate Sample records
INSERT INTO Table1 VALUES('.NET',10)
INSERT INTO Table1 VALUES('Java',20)
INSERT INTO Table1 VALUES('SQL',30)
CREATE TABLE Table2
(moduleID INT,[Level] INT,Value INT)
GO
--Populate Sample records
INSERT INTO Table2 VALUES(10,1,20)
INSERT INTO Table2 VALUES(10,2,30)
INSERT INTO Table2 VALUES(30,3,40)
INSERT INTO Table2 VALUES(10,3,50)
INSERT INTO Table2 VALUES(20,2,30)
INSERT INTO Table2 VALUES(20,4,60)
GO
CREATE VIEW [dbo].[vw_tabledata]
AS
SELECT t1.[moduleID],[moduleName]
,[Level]
,[Value]
FROM [db_Sample].[dbo].[Table2] t2 inner join [db_Sample].[dbo].[Table1] t1 on t1.[moduleID] = t2.[moduleID]
GO
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME([Level])
FROM (SELECT DISTINCT [Level] FROM Table2) AS [Level]
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT moduleID,moduleName, ' + #ColumnName + '
FROM [vw_tabledata]
PIVOT(MAX(Value)
FOR [Level] IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery

Do a LEFT JOIN for each Level in table 2.
select t1.*, t2_l1.value as Level1, t2_l2.value as Level2, t2_l3.value as Level3
from table1 t1
left join table2 t2_l1 on t1.moduleID = t2_l1.moduleID and t2_l1.Level = 'Level1'
left join table2 t2_l2 on t1.moduleID = t2_l2.moduleID and t2_l2.Level = 'Level2'
left join table2 t2_l3 on t1.moduleID = t2_l3.moduleID and t2_l3.Level = 'Level3'
If a moduleID has several different values for a level, all of them will be returned. (If you want the sum instead, take a look at Giorgos Betsos' answer.)

Related

Get all parents of multiple objects in same table

for an mass edit function I need to load the parents of multiple objects.
Doing this with single querys would kill the db. I'm using MySQL 5.7 My table is build like this:
CREATE TABLE `testtable` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`content` text,
`parentid` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
INSERT INTO testtable(`id`, `content`, `parentid`)
VALUES(1, 'parentA1', 0),(5, 'parentA2', 1),(3, 'childA', 2),
(4, 'parentB', 0),(5, 'childB', 4);
For single object querys I use this statement to get all parents:
SELECT t.id, t.content, #pid := t.parentid AS parentid
FROM (SELECT * FROM Table1 order by id DESC) t
JOIN (SELECT #pid := 3) tmp
WHERE t.id = #pid
But I have absolutly no clue how this could work for multiple object at once without using union.
My whised ouput should look like this:
id | content | parentid | searchingChildID
1 | parentA1| 0 | 3
2 | parentA2| 1 | 3
3 | childA | 2 | 3
4 | parentB | 0 | 5
5 | childB | 4 | 5
Thanks in advance!
If you are running MySQL 8.0, this is a typical use case for a recursive query. Say you want all parents of objects 3 and 4, you can do:
with recursive cte as (
select t.id as originalid, t.* from table1 where id in (3, 4)
union all
select c.originalid, t.*
from cte c
inner join table1 t on t.id = c.parentid
)
select * from cte
for an mass edit function I need to load the parents of multiple objects.
For this operation, I would expect:
id | content | parentid | ultimateparent
1 | parentA1| 0 | 0
2 | parentA2| 1 | 0
3 | childA | 2 | 0
4 | parentB | 0 | 0
5 | childB | 4 | 0
Because 0 is the ultimate parent of all the rows. If you know the maximum depth, you can use left joins in pre-8 versions of MySQL:
select t1.*, coalesce(t3.parentid, t2.parentid, t1.parentid) as ultimateparent
from testtable t1 left join
testtable t2
on t2.id = t1.parentid left join
testtable t3
on t3.id = t2.parentid
You can trivially extend this with more left joins to handle deeper levels of parentage.
If 0 really means "no parent" (which is an odd choice when NULL is available) then you would seem to want:
id | content | parentid | ultimateparent
1 | parentA1| 0 | 1
2 | parentA2| 1 | 1
3 | childA | 2 | 1
4 | parentB | 0 | 4
5 | childB | 4 | 4
Then you can actually just tweak the above query to:
select t1.*, coalesce(t3.id, t2.id, t1.id) as ultimateparent
from testtable t1 left join
testtable t2
on t2.id = t1.parentid left join
testtable t3
on t3.id = t2.parentid;
Here is a db<>fiddle.
You can get the maximum child for each parent as well (although that seems quite arbitrary). In pre-8.0 versions of MySQL, variables are the simplest approach:
select t1.*,
(#max_child := if(#up = ultimateparent, #max_child,
if(#up := ultimateparent, id, id)
)
) as max_childid
from (select t1.*, coalesce(t3.id, t2.id, t1.id) as ultimateparent
from testtable t1 left join
testtable t2
on t2.id = t1.parentid left join
testtable t3
on t3.id = t2.parentid
order by ultimateparent, id desc
) t1 cross join
(select #up := -1, #max_child := -1) params;

Select duplicated values from one coloum and get there value in one row

i have table with 2 columns like below
+----------+----------+
| Column A | Column B |
+----------+----------+
| 123 | ABC |
| 123 | XYC |
| 123 | FGH |
| 145 | QWE |
| 147 | YUI |
+----------+----------+
I want to select all values from table but view it like below:
+----------+---------+---------+----------+
| Column A | value 1 | value 2 | value 3 |
+----------+---------+---------+----------+
| 123 | ABC | XYC | FGH |
| 145 | QWE | | |
| 147 | YUI | | |
+----------+---------+---------+----------+
If you're not trying to create extra columns in your output, you can simply use GROUP_CONCAT with the separator of your choice. For example:
SELECT `Column A`,
GROUP_CONCAT(`Column B` SEPARATOR ' | ') AS `Values`
FROM table1
GROUP BY `Column A`
Output:
Column A Values
123 ABC | XYC | FGH
145 QWE
147 YUI
Demo on dbfiddle
I'm not sure how are you going to execute the query? but if you can manage to create dynamic SQL query string to find all duplicates rows and insert each row into a temp table and other values (unique) into a separate temp table. Then create another query to join all temp tables (with duplicate) value into a new data set, union all of them with the (unique) data set.
It may be a long and not a good solution but here's my experiment:
Insert all duplicates rows into #temp tables (3 rows= 3 #temp tables)
SELECT Id,Name
INTO #temp1
FROM TestTable
WHERE Name='ABC'
SELECT Id,Name
INTO #temp2
FROM TestTable
WHERE Name='XYC'
SELECT Id,Name
INTO #temp3
FROM TestTable
WHERE Name='FGH'
Insert all unique rows into single #temptable
SELECT Id,Name
INTO #temp4
FROM TestTable
WHERE Id!=123
Query
SELECT t1.Id,t1.Name as Value1,t2.Name as Value2,t3.Name as Value3
FROM #temp1 t1
INNER JOIN #temp2 t2 on t1.Id=t2.Id
INNER JOIN #temp3 t3 on t1.Id=t3.Id
UNION ALL
SELECT t4.Id,t4.Name as Value1,null as Value2,null as Value3
FROM #temp4 t4
Result
If you want three different columns, you can use row_number() and conditional aggregation:
select a,
max(case when seqnum = 1 then b end) as b_1,
max(case when seqnum = 2 then b end) as b_2,
max(case when seqnum = 3 then b end) as b_3
from (select t.*,
row_number() over (partition by a order by b) as seqnum
from t
) t
group by a;

SQL SELECT statement with dynamic column names

I try to select columns which names are the content of other columns. I'm using MySQL 5.6.
Let's say I have "table1":
+------+------------+------------+---------------+---------------+
| id | val_int1 | val_int2 | val_string1 | val_string2 |
+------+------------+------------+---------------+---------------+
| 1 | 70 | 88 | xxx | yyy |
+------+------------+------------+---------------+---------------+
And "table2":
+------+--------+----------+
| id | type | ref_id |
+------+--------+----------+
| 10 | i1 | 1 |
| 20 | s2 | 1 |
+------+--------+----------+
What I want to do is: join table1 and table2, the table2.type field contains the name of the column from table1 which I want to select. And then there's the problem that the type field only contains abbreviations which I have to extend.
This ends up in the following SQL statement:
SELECT
t1.id,
IF(t2.type REGEXP 'i[0-9]+', REPLACE(t2.type, 'i', 'val_int'), REPLACE(t2.type, 's', 'val_string'))
FROM
table1 t1, table2 t2
WHERE
t1.id = t2.ref_id AND t1.id = 1
The result is that the REPLACE functions return val_int1 and val_string2 as fixed strings and not handle it as column names.
What I really expect is:
+-----+-------+
| 1 | 70 |
| 1 | yyy |
+-----+-------+
You need some sort of case expression:
select t1.id,
(case when t2.type = 'i1' then cast(val_int_1 as varchar(255))
when t2.type = 'i2' then cast(val_int_2 as varchar(255))
when t2.type = 's1' then val_string_1
when t2.type = 's2' then val_string_2
end) as val
from table1 t1 cross join
table2 t2;
You are likely to complain "oh, I have so many columns". Basically, too bad. You have a poor database design. You are trying to do a partial match on strings and column names. Even a dynamic SQL solution is not very feasible.
Using case expression, this is how i would solve this:
DECLARE #table1 TABLE
(
id INT,
val_int1 INT,
val_int2 INT,
val_string1 NVARCHAR(100),
val_string2 NVARCHAR(100)
)
INSERT INTO #table1 VALUES
(1,70,88,'xxx','yyy')
DECLARE #table2 TABLE
(
id INT,
type NVARCHAR(MAX),
ref_id INT
)
INSERT INTO #table2 VALUES
(10,'i1',1),
(20,'s2',1)
SELECT
id,
CASE WHEN type = 'i1' THEN CAST((SELECT TOP 1 val_int1 FROM #table1) AS NVARCHAR(100)) ELSE
CASE WHEN type = 'i2' THEN CAST((SELECT TOP 1 val_int2 FROM #table1) AS NVARCHAR(100)) ELSE
CASE WHEN type = 's1' THEN (SELECT TOP 1 val_string1 FROM #table1) ELSE
(SELECT TOP 1 val_string2 FROM #table1) END END END
FROM #table2 t2
OUTPUT:
10 70
20 yyy

Query for looping values in column

I need to make a query that moves values of only one column one row up ↑ at a time:
+------------+----------------+
| anotherCOL | values_to_loop |
+------------+----------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
| 10 | 10 |
+------------+----------------+
So, the next time i run the query, it should look like this
+------------+----------------+
| anotherCOL | values_to_loop |
+------------+----------------+
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 5 | 6 |
| 6 | 7 |
| 7 | 8 |
| 8 | 9 |
| 9 | 10 |
| 10 | 1 |
+------------+----------------+
I need to loop the values of only one MYSQL COLUMN, as in move the values one ROW UP ↑ each time I run the query.
Notice: Tables provided are just illustrative, the data is different.
Here's how you can do it within a single UPDATE query:
UPDATE tbl a
INNER JOIN (
SELECT values_to_loop
FROM (SELECT * FROM tbl) c
ORDER BY anotherCOL
LIMIT 1
) b ON 1 = 1
SET a.values_to_loop =
IFNULL(
(SELECT values_to_loop
FROM (SELECT * FROM tbl) c
WHERE c.anotherCOL > a.anotherCOL
ORDER BY c.anotherCOL
LIMIT 1),
b.values_to_loop
)
It works as follows:
Updates all records from tbl
Joins with a temporary table to retrieve the top value of values_to_loop (the one that will go to the bottom)
Set the new value for values_to_loop to the corresponding value from the next row (c.anotherCOL > a.anotherCOL ... LIMIT 1)
Notes:
This works even if there are gaps in anotherCOL (eg: 1, 2, 3, 6, 9, 15)
It is required to use (SELECT * FROM tbl) instead of tbl because you're not allowed to use the table that you're updating in the update query
Faster query when there are no gaps in anotherCOL
If there are no gaps for values in anotherCOL you can use the query below that should work quite fast if you have an index on anotherCOL:
UPDATE tbl a
LEFT JOIN tbl b on b.anotherCOL = a.anotherCOL + 1
LEFT JOIN (
SELECT values_to_loop
FROM tbl
WHERE anotherCOL = (select min(anotherCOL) from tbl)
) c ON 1 = 1
SET a.values_to_loop = ifnull(
b.values_to_loop,
c.values_to_loop
)
I`ve created a sample table and added both a select to get the looped values and update to loop the values in the table. Also, using a #start_value variable to know the "1" which might be other. Try this:
CREATE TEMPORARY TABLE IF NOT EXISTS temp_table
(other_col INT, loop_col int);
INSERT INTO temp_table (other_col, loop_col) VALUES (1,1);
INSERT INTO temp_table (other_col, loop_col) VALUES (2,2);
INSERT INTO temp_table (other_col, loop_col) VALUES (3,3);
INSERT INTO temp_table (other_col, loop_col) VALUES (4,4);
INSERT INTO temp_table (other_col, loop_col) VALUES (5,5);
DECLARE start_value INT;
SELECT start_value = MIN(loop_col) FROM temp_table;
SELECT T1.other_col, ISNULL(T2.loop_col, start_value)
FROM temp_table T1
LEFT JOIN temp_table T2
ON T1.loop_col = T2.loop_col - 1;
UPDATE T1 SET
T1.loop_col = ISNULL(T2.loop_col, #start_value)
FROM temp_table T1
LEFT JOIN temp_table T2
ON T1.loop_col = T2.loop_col - 1;
SELECT *
FROM temp_table;
Let me know if it works for you.
Step by step:
1 - created a temp_table with values 1 to 5
2 - declared a start_value which will keep the lowest value for the column you to need to loop through
3 - select all rows from temp_table self left join with same temp_table. join condition is on loop_col - 1 so it can shift the rows up
4 - the same self left join, but this time update the values in place too.
please note that in case i get a null value, it should be the start_value there, because it cannot match
Perhaps these are what you had in mind:
update T
set values_to_loop = mod(values_to_loop, 10) + 1
update T
set values_to_loop =
coalesce(
(
select min(t2.values_to_loop) from T t2
where t2.values_to_loop > T.values_to_loop
),
(
select min(values_to_loop) from T
)
)

SQL SELECT rows where rows of column elsewhere are null

First, sorry if the title is confusing.
SQL is not my strong suit and I've been working on this for a while, my thoughts at the mmoment is something with a join, and group maybe.
Soto Example:
record | type | key1 | key2 | data1
---------------------------------------
1 | 1 | joe | smoe | 10
2 | 2 | homer | simpson | 20
3 | 1 | null | null | 30
4 | 3 | bart | simpson | 40
Where primary key is made up of id, key1, key2.
I only want rows of 'type' WHERE key1 is not null AND key2 is not null.
So since in record 3, type 1 has null keys, I therefore want all records of type 1 to not be included in the derived table.
Here's a correlated, "not exists" approach:
select *
from T as t1
where not exists (
select *
from T as t2
where t2.type = t1.type and (t2.key1 is null or t2.key2 is null)
)
And here's one that uses a non-correlated query along with grouping. Perhaps it's what you had in mind:
select *
from T as t1
where t1.type in (
select t2.type
from T as t2
group by t2.type
having count(*) = count(t2.key1) and count(*) = count(t2.key2)
)
Since I understand mysql query plans can be sensitive to these things. Here's the equivalent with a join:
select t1.*
from T as t1
inner join
(
select t2.type
from T as t2
group by t2.type
having count(*) = count(t2.key1) and count(*) = count(t2.key2)
) as goodtypes
on goodtypes.type = t1.type