ID COL1 COL2 COL3 COL4 COL5 COL6 COL7
-------------------------------------
1 4 13 8 0 9 11 2
2 12 3 3 10 17 12 9
3 17 0 0 19 3 1 3
4 5 0 16 0 9 11 2
Here is an example table of data.
What I need to be able to do is to select and label the three smallest values in each row so I can identify each one.
For instance I want to know that in Row2 the three smallest values are 3,3,9 and that they are in COL2,COL3,COL7
I am thinking that I need to incorporate the LEAST() command provided in mysql but it only seems to return one value (the smallest one).
SELECT LEAST(COL1,COL2,COL3,COL4,COL5,COL6,COL7)
I cant seem to figure out how to get the 3 smallest values instead of just one.
Unfortunately your table is not normalized :(
In this case a possible solution is to unpivot the table using a query like this:
CREATE VIEW unpivoted AS
SELECT id, 'col1' colname, col1 as value FROM Table1
UNION ALL
SELECT id, 'col2' colname, col2 FROM Table1
UNION ALL
SELECT id, 'col3' colname, col3 FROM Table1
UNION ALL
SELECT id, 'col4' colname, col4 FROM Table1
UNION ALL
SELECT id, 'col5' colname, col5 FROM Table1
UNION ALL
SELECT id, 'col6' colname, col6 FROM Table1
UNION ALL
SELECT id, 'col7' colname, col7 FROM Table1
and then use a query like the below to find 3 minimum values and then pivot results back:
SET #x = 0;
Set #lastid = -999;
SELECT id,
min( IF( x = 0, colname, null )) As Colname1,
min( IF( x = 0, value, null )) As value1,
min( IF( x = 1, colname, null )) As Colname2,
min( IF( x = 1, value, null )) As value2,
min( IF( x = 2, colname, null )) As Colname3,
min( IF( x = 2, value, null )) As value3
FROM (
SELECT id, colname, value,
IF( #lastid = id, #x:=#x+1,
IF( (#lastid:=id), #x:=0, #x:=0 )
) As x
FROM unpivoted
ORDER BY id, value
) q
WHERE x < 3
GROUP BY id
Demo: http://sqlfiddle.com/#!2/f20ee4/5
But the speed of these queries will be horribly slow, don't even try them on a big table.
You need to normalize the table.
Kordirko's solution is may be the fastest way to do it (without a whole lot of comparison logic on each row). However, the following is more intuitive, in my opinion:
select id,
substring_index(group_concat(colname order by value), ',', 3) as Top3Columns,
substring_index(group_concat(value order by value), ',', 3) as Top3Values
from unpivoted
group by id;
This puts the names and values each in one column, as a concatenated list. You can use a similar idea if you want them in separate columns.
Related
I have a Mysql table That looks like this:
Col 1. Col 2.
a 1
b 2
c,d,e 3
What I would like to do is run a query that would replace the row with c,d,e with multiple broken out rows so that the result for Select * From table would look like:
Col 1. Col 2.
a 1
b 2
c 3
d 3
e 3
If you know the maximum number, you can use union all:
select substring_index(col1, ',', 1) as col1, col2
from t
union all
select substring(substring_index(col1, ',', 2), ',', -1) as col1, col2
from t
where col1 like '%,%'
union all
select substring(substring_index(col1, ',', 3), ',', -1) as col1, col2
from t
where col1 like '%,%,%';
Use a tally table. For example, mine is generated on the fly by recursive CTE
with recursive nmbs as (
select 1 n
union all
select n+1
from nmbs
where n<100
),
testdata as (
select 'a' c1, 1 c2 union all
select 'b' c1, 2 c2 union all
select 'c, ddd , e ,fgh' c1, 3
)
select c1, c2, trim(replace(substring(substring_index(c1, ',', n), length(substring_index(c1, ',', n-1))+1), ',', '')) s
from testdata
join nmbs on n <= 1 + length(c1) - length(replace(c1,',',''))
order by c1,n
db<>fiddle
I have a table tbl1 with two columns col1 and col2 containing strings:
col1 | col2
--------+--------
bar | foo
foo | foobar
bar1foo | bar2foo
Corresponding SQL dump:
CREATE TABLE `tbl1` (
`col1` varchar(20) COLLATE latin1_general_ci NOT NULL,
`col2` varchar(20) COLLATE latin1_general_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
INSERT INTO `tbl1` (`col1`, `col2`) VALUES
('bar', 'foo'),
('foo', 'foobar'),
('bar1foo', 'bar2foo');
The strings of an entry share a common prefix in most cases. I need a query that strips those common prefixes. Expected result:
bar | foo
| bar
1foo | 2foo
My approach, so far:
SELECT
SUBSTR(`col1`, 1+GREATEST(LENGTH(`col1`), LENGTH(`col2`)) - CEIL(LENGTH(TRIM(TRAILING '0' FROM HEX(ABS(CONV(HEX(REVERSE(`col1`)),16,10) - CONV(HEX(REVERSE(`col2`)),16,10)))))/2)),
SUBSTR(`col2`, 1+GREATEST(LENGTH(`col1`), LENGTH(`col2`)) - CEIL(LENGTH(TRIM(TRAILING '0' FROM HEX(ABS(CONV(HEX(REVERSE(`col1`)),16,10) - CONV(HEX(REVERSE(`col2`)),16,10)))))/2))
FROM tbl1
Short explanation: The strings are reversed (REVERSE), converted into integers (HEX and CONV), subtracted from each other (- and ABS), converted into hexadecimal representation (HEX), 0's are trimmed from the end (TRIM), the length of this result is subtracted from the length of the longest string (-, LENGTH and GREATEST) and then used by SUBSTR to get the result.
Problems with my approach:
Does not work with strings longer than 64bit.
Does not work with strings containing multi-byte characters
Very lengthy and ugly
Does not have good performance.
Sadly, the most general and performance method is probably a giant case expression. However, this works only up to a certain length:
select substr(col1, prefix_length + 1),
substr(col2, prefix_length + 1)
from (select tbl1.*,
(case when left(col1, 10) = left(col2, 10) then 10
when left(col1, 9) = left(col2, 9) then 9
. . .
else 0
end) as prefix_length
from tbl1
) t;
Actually, you can do this with a recursive CTE, which is the most general approach:
with recursive cte as (
select col1, col2, 1 as lev, col1 as orig_col1, col2 as orig_col2
from tbl1
union all
select substr(col1, 2), substr(col2, 2), lev + 1, orig_col1, orig_col2
from cte
where left(col1, 1) = left(col2, 1)
)
select col1, col2
from (select cte.*,
dense_rank() over (partition by orig_col1, orig_col2 order by lev desc) as seqnum
from cte
) x
where seqnum = 1;
Although the performance will definitely be worse than your solution or the massive case expression, it is probably not that bad, and you might find it sufficient for your purposes.
Here is a db<>fiddle with both solutions.
This code works, although it's lengthy and ugly and (maybe) unperformant:
select
substring(t.col1, g.maxlen + 1) col1,
substring(t.col2, g.maxlen + 1) col2
from tbl1 t inner join (
select t.col1, t.col2,
max(case when left(col1, tt.n) = left(col2, tt.n) then tt.n else 0 end) maxlen
from tbl1 t inner join (
select 1 n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select 11 union all select 12 union all
select 13 union all select 14 union all select 15 union all select 16 union all
select 17 union all select 18 union all select 19 union all select 20
) tt on least(length(t.col1), length(t.col2)) >= tt.n
group by t.col1, t.col2
) g on g.col1 = t.col1 and g.col2 = t.col2
See the demo.
For MySql 8.0+ you can use a recursive CTE and in this case there is no need of prior knowledge of the length of the columns:
with
recursive lengths as (
select 1 n
union all
select n + 1
from lengths
where n < (select max(least(length(col1), length(col2))) from tbl1)
),
cte as (
select t.col1, t.col2,
max(case when left(col1, l.n) = left(col2, l.n) then l.n else 0 end) maxlen
from tbl1 t inner join lengths l
on least(length(t.col1), length(t.col2)) >= l.n
group by t.col1, t.col2
)
select
substring(t.col1, c.maxlen + 1) col1,
substring(t.col2, c.maxlen + 1) col2
from tbl1 t inner join cte c
on c.col1 = t.col1 and c.col2 = t.col2
See the demo.
Results:
| col1 | col2 |
| ---- | ---- |
| | bar |
| bar | foo |
| 1foo | 2foo |
I have a table of two columns
Col1 Col2
A 1
A 2
A 3
B 1
B 2
B 3
Output I need is like this
Col1 Col2
A 1
A 1,2
A 1,2,3
B 1
B 1,2
B 1,2,3
Thank you in advance.
Here is a solution which would work for MySQL. It uses a correlated subquery in the select clause to group concatenate together Col2 values. The logic is that we only aggregate values which are less than or equal to the current row, for a given group of records sharing the same Col1 value.
SELECT
Col1,
(SELECT GROUP_CONCAT(t2.Col2 ORDER BY t2.Col2) FROM yourTable t2
WHERE t2.Col2 <= t1.Col2 AND t1.Col1 = t2.Col1) Col2
FROM yourTable t1
ORDER BY
t1.Col1,
t1.Col2;
Demo
Here is the same query in Oracle:
SELECT
Col1,
(SELECT LISTAGG(t2.Col2, ',') WITHIN GROUP (ORDER BY t2.Col2) FROM yourTable t2
WHERE t2.Col2 <= t1.Col2 AND t1.Col1 = t2.Col1) Col2
FROM yourTable t1
ORDER BY
t1.Col1,
t1.Col2;
Demo
Note that the only real change is substituting LISTAGG for GROUP_CONCAT.
with s (Col1, Col2) as (
select 'A', 1 from dual union all
select 'A', 2 from dual union all
select 'A', 3 from dual union all
select 'B', 1 from dual union all
select 'B', 2 from dual union all
select 'B', 3 from dual)
select col1, ltrim(sys_connect_by_path(col2, ','), ',') path
from s
start with col2 = 1
connect by prior col2 = col2 - 1 and prior col1 = col1;
C PATH
- ----------
A 1
A 1,2
A 1,2,3
B 1
B 1,2
B 1,2,3
6 rows selected.
The database scheme consists:
Table1(code, col1, col2, col3, col4, col5)
What to do is:
For the Table1 with the maximal code value from Table1 table, obtain all its characteristics (except for a code) in two columns:
The name of the characteristic (a name of a corresponding column in the PC table);
Value of the characteristic.
I don't have any idea how to get the table column names in my result column set.
The final result will look like:
chr value
col1 133
col2 80
col3 28
col4 2
col5 50
This is an unpivot operation. The simplest way is using union all. However, the following is generally more efficient:
select (case when n.n = 1 then 'col1'
when n.n = 2 then 'col2'
when n.n = 3 then 'col3'
when n.n = 4 then 'col4'
when n.n = 5 then 'col5'
end) as chr,
(case when n.n = 1 then col1
when n.n = 2 then col2
when n.n = 3 then col3
when n.n = 4 then col4
when n.n = 5 then col5
end) as value
from table t cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all select 5
) n;
This is more efficient when your table is big or a complicated subquery.
The union all version is:
select 'col1', col1 from table t union all
select 'col2', col2 from table t union all
select 'col3', col3 from table t union all
select 'col4', col4 from table t union all
select 'col5', col5 from table t;
SELECT 'col1', MAX(col1) FROM table1
UNION
SELECT 'col2', MAX(col2) FROM table1
UNION
...
SELECT 'cd' as chr, cd as value
FROM pc
WHERE code = (SELECT max(code) FROM pc)
UNION
SELECT 'model' as chr, cast(model as varchar)as value
FROM pc
WHERE code = (SELECT max(code) FROM pc)
UNION
SELECT 'speed' as chr,cast(speed as varchar) as value
FROM pc
WHERE code = (SELECT max(code) FROM pc)
UNION
SELECT 'ram' as chr, cast(ram as varchar) as value
FROM pc
WHERE code = (SELECT max(code) FROM pc)
UNION
SELECT 'hd' as chr, cast(hd as varchar) as value
FROM pc
WHERE code = (SELECT max(code) FROM pc)
UNION
SELECT 'price' as chr,cast(price as varchar) as value
FROM pc
WHERE code = (SELECT max(code) FROM pc)
Select char, value
From
(Select code,
cast(speed as varchar(20)) speed,
cast(ram as varchar(20)) ram,
cast(hd as varchar(20)) hd,
cast(model as varchar(20)) model,
cast(cd as varchar(20)) cd,
cast(price as varchar(20)) price
FROM pc
) src
UNPIVOT
(value For char in (speed,ram,hd,model,cd,price)
) As unpvt
where
code in (Select max(code) from PC)`
I have a input variable
#inputData varchar(Max)
e.g :
Victor:2;John:22;Jo:100
how can I split the variable into two columns?
Col1 Col2
----------
Victor 2
John 22
Jo 100
Here is the script, this gives multiple columns and rows from single string value.
declare #inputData varchar(Max) = 'Victor:2;John:22;Jo:100' + ';'
;with row(c1,c2)
as
(
SELECT LEFT
( #inputData
, CHARINDEX(';', #inputData, 0) - 1
) col1
, SUBSTRING
( #inputData
, CHARINDEX(';', #inputData, 0) + 1
, LEN(#inputData)
) col2
UNION ALL
SELECT LEFT
( c2
, CHARINDEX(';', c2, 0) - 1
) col1
, SUBSTRING
( c2
, CHARINDEX(';', c2, 0) + 1
, LEN(c2)
) col2
FROM row
WHERE CHARINDEX(';', c2, 0) >0
)
select LEFT(C1, CHARINDEX(':', c1, 0) - 1) col1, SUBSTRING( c1 , CHARINDEX(':', c1, 0) + 1, LEN(c1)) col2 from row
Output :
col1 col2
Victor 2
John 22
Jo 100
May not be a very good/efficient solution and typically based on your example; you can try the below:
create table tab1(col varchar(100))
insert into tab1 values ('key1:val1;key2:val2;key3:valu3')
Query:
select SUBSTRING((SUBSTRING((left(col,CHARINDEX(';',Col))),0,
charindex(';',col))),0,charindex(':',col)) as Name,
SUBSTRING((SUBSTRING((left(col,CHARINDEX(';',Col))),0,
charindex(';',col))),(charindex(':',col)+1),4) as Age
from tab1
union
select SUBSTRING((SUBSTRING(right(col,CHARINDEX(';',Col)),0,
charindex(';',col))) ,0,charindex(':',col)) as Name,
SUBSTRING((SUBSTRING(right(col,CHARINDEX(';',Col)),0,
charindex(';',col))),(charindex(':',col)+1),4) as Age
from tab1
union
select SUBSTRING((SUBSTRING(substring(col,(CHARINDEX(';',Col) +
1),10),0,charindex(';',col))),0,charindex(':',col)) as Name,
SUBSTRING((SUBSTRING(substring(col,(CHARINDEX(';',Col) + 1),10),0,
charindex(';',col))),(charindex(':',col)+1),4) as Age
from tab1
The best way to achieve this would be UDF (user Defined Functions). This is just a
example and you may like to take it further from here.