SQL string into multiple columns - sql-server-2008

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.

Related

MYSQL Split String and Duplicate Row

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

How to merge multiple values in a column with other column which also have multiple values sperated by comma

column1 | column2 | Result
32,33 | A,B | A32,A33,B32,B33
I have given an example above In which I have two columns with multiple values separated by a comma and I want to merge the first two columns and want to get a result as shown in the Result column.
On MySQL 8.0, You can first split the columns on the basis of comma separated values -
with recursive col1 as (select 1 id, '32,33' as column1
union all
select 2, '34,35'),
rec_col1 as (select id, column1 as remain, substring_index( column1, ',', 1 ) as column1
from col1
union all
select id, substring( remain, char_length( column1 )+2 ),substring_index( substring( remain, char_length( column1 ) +2 ), ',', 1 )
from rec_col1
where char_length( remain ) > char_length( column1 )
RESULT
id column1
1 32
1 33
2 34
2 35
Similarly, Second column -
with recursive col2 as (select 1 id, 'A,B' as column2
union all
select 2, 'C,D'),
rec_col2 as (select id, column2 as remain, substring_index( column2, ',', 1 ) as column2
from col2
union all
select id, substring( remain, char_length( column2 )+2 ),substring_index( substring( remain, char_length( column2 ) +2 ), ',', 1 )
from rec_col2
where char_length( remain ) > char_length( column2 ))
select id, column2 from rec_col2
order by id;
RESULT
id column2
1 A
1 B
2 C
2 D
After splitting both the columns, You can join them on the basis of id -
select concat(c2.column2, c1.column1) result_rows
from rec_col1 c1
join rec_col2 c2 on c1.id = c2.id
RESULT
result_rows
A32
B32
C34
D34
A33
B33
C35
D35
Finally you can use GROUP_CONCAT to combine them -
select group_concat(c2.column2, c1.column1 order by c2.column2, c1.column1) result
from rec_col1 c1
join rec_col2 c2 on c1.id = c2.id
group by c2.id
RESULT
result
A32,A33,B32,B33
C34,C35,D34,D35

How to select a primary key which has exact foreign keys matches a given list of values?

For example:
pk_ref fk
====== ===
1 a
1 b
1 c
2 a
2 b
2 d
How do I do a query like the "pseudo" query:
select distinc pk_ref
where fk in all('a', 'c');
The return query result must match all given values for the foreign key in the list.
The result should be:
1
While the following select must not return any records.
select distinc pk_ref
where fk in all('a', 'c', 'd');
How do I do that?
Try this
select pk_ref
from yourtable
group by pk_ref
having count(case when fk = 'a', then 1 end) >= 1
and count(case when fk = 'c' then 1 end) >= 1
To do it dynamically. (considering you are using SQL SERVER)
Create a split string function and pass the input as comma separated values
Declare #input varchar(8000)= 'a,c',#cnt int
set #cnt = len(#input)-len(replace(#input,',','')) + 1
select pk_ref
from yourtable
Where fk in (select split_values from udf_splitstring(#input , ','))
group by pk_ref
having count(Distinct fk) >= #cnt
You can create a split string function from the below link
https://sqlperformance.com/2012/07/t-sql-queries/split-strings
:list is the input list (bind variable). The difference of length() return values is the number of commas in the bind variable. This query, or something very close to it, should work in pretty much any DB product. Tested in Oracle.
select pk_ref
from tbl -- enter your table name here
where ',' || :list || ',' like '%,' || fk || ',%'
group by pk_ref
having count(distinct fk) = 1 + length(:list) - length(replace(:list, ',', ''))
If you can pass the IN operator values as Set, then you can do this as below
Schema:
SELECT * INTO #TAB FROM (
SELECT 1 ID, 'a' FK
UNION ALL
SELECT 1, 'b'
UNION ALL
SELECT 1, 'c'
UNION ALL
SELECT 2, 'a'
UNION ALL
SELECT 2, 'b'
UNION ALL
SELECT 2, 'd'
UNION ALL
SELECT 1, 'a'
)AS A
Used CTE to make 'a','c' as Set
;WITH CTE AS (
SELECT 'a' FK --Here 'a','c' passed as a Set through CTE
UNION
SELECT 'c'
)
,FINAL AS(
SELECT DENSE_RANK() OVER (PARTITION BY ID ORDER BY (FK))AS COUNT_ID, ID, FK
FROM #TAB where FK IN (select FK FROM CTE)
)
SELECT ID FROM FINAL WHERE COUNT_ID>=(SELECT COUNT( FK) FROM CTE)
Select pk_ref where fk='a' and pk_ref in (select pk_ref where fk='c' from yourtable) from yourtable;
or
select pk_ref where fk='a' from yourtable intersect select pk_ref where fk='c' from yourtable;
DECLARE #inputVariable VARCHAR(200) = 'a,b,c,d'
DECLARE #inputValue INT
DECLARE #tblInput TABLE
(
FK VARCHAR(100)
)
INSERT INTO #tblInput
SELECT SUBSTRING( #inputVariable+',',RN,1)
FROM (SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY s.object_id) RN
FROM sys.objects s) s
where LEN(#inputVariable) >= RN
AND SUBSTRING(','+ #inputVariable,RN,1) = ','
SELECT #inputValue = COUNT(1) FROm #tblInput
--#inputVariable
DECLARE #tbl TABLE
(
ID INT,
FK VARCHAR(100)
)
INSERT INTO #tbl
SELECT 1 ID, 'a' FK
UNION ALL
SELECT 1, 'b'
UNION ALL
SELECT 1, 'c'
UNION ALL
SELECT 2, 'a'
UNION ALL
SELECT 2, 'b'
UNION ALL
SELECT 2, 'd'
UNION ALL
SELECT 1, 'a'
SELECT t.ID ,COUNT(DISTINCT t.FK)
FROM #tbl t
INNER JOIn #tblInput ti
ON t.FK = ti.FK
GROUP BY ID
HAVING COUNT(DISTINCT t.FK) = #inputValue

MySQL - How to sort within a record?

So I have a single record row; and it is like this:
data1 data2 data3 data4
4 3 1 2
Now I want to sort the data's, in 1 2 3 4 order.
So: data3, data4, data2, data1 for row 1
Would anyone know how to sort within a record this way?
As soon you have columns data1, data2 etc. you need to change your database model. Mosttimes it means that you need a new extra table.
wrong:
users:
- user_id
- name
- address1
- address2
- address3
right:
users:
- user_id
- name
addresses:
- address_id
- user_id
- address
You can run the following code.
SELECT * FROM table_name ORDER BY coloumn_name ASC;
You can also ignore ASC, as it means arranging in an Ascending order.
Here are two methods. Here is the unpivot and repivot method:
select substring_index(group_concat(col order by col), ',', 1) as data1,
substring_index(substring_index(group_concat(col order by col), ',', 2), ',', -1) as data2,
substring_index(substring_index(group_concat(col order by col), ',', 3), ',', -1) as data3,
substring_index(substring_index(group_concat(col order by col), ',', 4), ',', -1) as data4
from ((select data1 as col from table) union all
(select data2 as col from table) union all
(select data3 as col from table) union all
(select data4 as col from table)
) t
group by col1, col2, col3, col4; # A real id would be better for this
One option is to create a simple function:
DELIMITER $$
CREATE FUNCTION get_nth(vn INT, v1 INT, v2 INT, v3 INT, v4 INT)
RETURNS INT
BEGIN
DECLARE v_offset INT;
DECLARE v_return INT;
SET v_offset = vn-1;
SELECT o.val INTO v_return
FROM ( SELECT v1 AS val
UNION ALL SELECT v2
UNION ALL SELECT v3
UNION ALL SELECT v4
ORDER BY 1
LIMIT v_offset,1
) o;
RETURN v_return;
END$$
DELIMITER ;
With that function created, you can do something like this:
SELECT get_nth(1,t.data1,t.data2,t.data3,t.data4) AS od1
, get_nth(2,t.data1,t.data2,t.data3,t.data4) AS od2
, get_nth(3,t.data1,t.data2,t.data3,t.data4) AS od3
, get_nth(4,t.data1,t.data2,t.data3,t.data4) AS od4
FROM (
SELECT 4 AS data1
, 3 AS data2
, 1 AS data3
, 2 AS data4
) t
(Just replace the inline view t with a reference to your table that contains columns data1..data4)
SQL Fiddle demonstration: http://sqlfiddle.com/#!9/e0e39/2

mysql - Select the 3 smallest values from multiple columns

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.