SQL SELECT statement with dynamic column names - mysql

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

Related

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;

outer query column in inner query case expression

I am trying to write below query in vertica
`SELECT a.*
FROM a
WHERE a.country="India"
AND a.language ="Hindi"
AND ( CASE WHEN (a.spoken = true
AND exist ( select 1
FROM b
WHERE b.country=a.country
AND b.language=a.language
AND ( CASE WHEN (a.population <b.population
AND a.statsyear > b.statsyear))
THEN true //pick recent stats
WHEN (a.population > b.population)
THEN true
ELSE false
END)) THEN true
WHEN (a.written = true ) THEN
true
ELSE false
END)`
it is not working, because we can't reference "a.population" outer query field in case expression of innerquery. I tried rewriting it wil OR caluse Vertica is not allowing it.
How can I re-write this
I created below tables in MySQL local box
Example of Tables and Results
CREATE TABLE tableA
(
id INT,
country VARCHAR(20),
language VARCHAR(20),
spoken INT,
written INT,
population INT,
stats INT
)
insert into tableA values(1,'India','Hindi',1,0,9,2010)
insert into tableA values(2,'India','Hindi',1,0,11,2011)
insert into tableA values(3,'India','Hindi',1,0,10,2012)
insert into tableA values(4,'India','Hindi',0,1,10,2013)
insert into tableA values(5,'India','Hindi',1,1,10,2012)
insert into tableA values(6,'India','English',1,1,10,2012)
CREATE TABLE tableB
(
id INT,
country VARCHAR(20),
language VARCHAR(20),
population INT,
stats INT
)
insert into TableB values(1,'India','Hindi',10,2009)
insert into TableB values(2,'India','Hindi',10,2011)
insert into TableB values(3,'India','Hindi',10,2012)
Rewrote the query slightly in different way
select distinct a.id
from (
SELECT a.*
FROM TableA a
WHERE a.country="India"
AND a.language ="Hindi" ) a, TableB b
WHere ( CASE WHEN a.written=1 THEN
TRUE
WHEN ( (a.spoken = 1) AND (a.country=b.country) AND (a.language=b.language)) THEN
(case WHEN ((a.population < b.population) AND (a.stats > b.stats)) THEN
TRUE
WHEN (a.population > b.population) THEN
TRUE
ELSE
FALSE
END)
ELSE
FALSE
END)
got below results
1,2,4,5
This is what I need, now could you please help me in writing it more efficient manner
Boolean logic equivalent:
SELECT DISTINCT a.*
FROM TableA a
left join TableB b on a.country=b.country AND a.language=b.language
WHERE a.country='India'
AND a.language ='Hindi'
AND (
a.written=1
OR
(a.spoken = 1 AND a.population < b.population AND a.stats > b.stats)
OR
a.population > b.population
)
;
Result:
+----+---------+----------+--------+---------+------------+-------+
| id | country | language | spoken | written | population | stats |
+----+---------+----------+--------+---------+------------+-------+
| 1 | India | Hindi | 1 | 0 | 9 | 2010 |
| 2 | India | Hindi | 1 | 0 | 11 | 2011 |
| 4 | India | Hindi | 0 | 1 | 10 | 2013 |
| 5 | India | Hindi | 1 | 1 | 10 | 2012 |
+----+---------+----------+--------+---------+------------+-------+
Demo

Create a Join leaving one row for any possible duplicate

I have a 2 tables that looks like this
How can I call the same column wihout duplicating it
TYSM for help
Please try below mentioned Query:
select distinct t2.data,t1.key,t1.data from t1.table1 JOIN table as t2 ON t1.key = t2.key
You could assign a row number using a variable to t2 then join to t1 supressing the output of the t1.key.
for example
drop table if exists t1,t2;
create table t1 (id int);
create table t2 (id int, name varchar(2));
insert into t1 values(1),(2),(3),(4);
insert into t2 values(1,'s1'),(1,'s2'),(2,'s3'),(3,'s4'),(4,'s5');
select s.id, s.name,
case when s.rn = 1 then s.rn
else ''
end as something
from t1
join
(
select t2.id,t2.name,
if(t2.id <> #p, #rn:=1,#rn:=#rn+1) rn,
#p:=t2.id
from t2,(select #rn:=0,#p:=0) r
) s on t1.id = s.id
order by t1.id, s.name
Result
+------+------+-----------+
| id | name | something |
+------+------+-----------+
| 1 | s1 | 1 |
| 1 | s2 | |
| 2 | s3 | 1 |
| 3 | s4 | 1 |
| 4 | s5 | 1 |
+------+------+-----------+
5 rows in set (0.00 sec)

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

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.)

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