Comma separated values from multi columns as rows - sql

I have a table tbl_commaseperate with columns ID, MONNAME, IP and POLICY whose values are:
ID | MONNAME | IP | POLICY
-------------------------------
X | NOV | 1,2,3 | 4,5,6,7
where IP and POLICY have comma separated values.
My desired result looks like as below:
ID | MONNAME | IP | POLICY
------------------------------
X | NOV | 1 | 4
X | NOV | 2 | 5
X | NOV | 3 | 6
X | NOV | null | 7

The output is in no particular order. Also, in your desired output you don't seem to care which pair was first, which second etc. (but that can be preserved in the query, if needed).
I added a row for more testing; I have NULL for policy - which is how I realized I needed the coalesce() around the regexp_count.
with
inputs ( id ,monname, ip , policy ) as (
select 'X', 'NOV', '1,2,3' , '4,5,6,7' from dual union all
select 'X', 'DEC', '6,3,8', null from dual
)
-- end of test data; solution (SQL query) begins below this line
select id, monname,
regexp_substr(ip , '[^,]+', 1, level) as ip,
regexp_substr(policy, '[^,]+', 1, level) as policy
from inputs
connect by level <= 1 + greatest( coalesce(regexp_count(ip , ','), 0),
coalesce(regexp_count(policy, ','), 0) )
and prior id = id
and prior monname = monname
and prior sys_guid() is not null
;
ID MONNAME IP POLICY
-- ------- ----- -------
X DEC 6
X DEC 3
X DEC 8
X NOV 1 4
X NOV 2 5
X NOV 3 6
X NOV 7
7 rows selected

Please try this one.
Create a function to split comma separated string.
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(max) -- List of delimited items
, #sDelimiter VARCHAR(max) = ',' -- delimiter that separates items
) RETURNS #List TABLE (SplitValue VARCHAR(max))
BEGIN
DECLARE #sItem VARCHAR(max)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
And then write your query like this.
select * from (
select SplitValue,ROW_NUMBER() over(order by SplitValue) rowNo FROM dbo.fnSplit('1,2,3',',')
) as a
full join (
select SplitValue,ROW_NUMBER() over(order by SplitValue) rowNo FROM dbo.fnSplit('4,5,6,7',',')
) as b on a.rowNo=b.rowNo
replace your column in hardcore string and. Note: You can also write with query instead of function.

Oracle
with r (id,monname,ip,policy,n,max_tokens)
as
(
select id,monname,ip,policy
,1
,greatest (nvl(regexp_count(ip,'[^,]+'),0),nvl(regexp_count(policy,'[^,]+'),0))
from tbl_commaseperate
union all
select id,monname,ip,policy
,n+1
,max_tokens
from r
where n < max_tokens
)
select r.id
,r.monname
,regexp_substr (ip ,'[^,]+',1,n) as ip
,regexp_substr (policy,'[^,]+',1,n) as policy
from r
;

CREATE TABLE #Table ( ID VARCHAR(100),MONNAME VARCHAR(100), IP VARCHAR(100) , POLICY VARCHAR(100))
INSERT INTO #Table (ID ,MONNAME, IP, POLICY)SELECT 'X','NOV',1,2,3,4','4,5,6,7'
;WITH _CTE ( _id ,_MONNAME , _IP , _POLICY , _RemIP , _RemPOLICY) AS (
SELECT ID ,MONNAME , SUBSTRING(IP,0,CHARINDEX(',',IP)), SUBSTRING(POLICY,0,CHARINDEX(',',POLICY)),
SUBSTRING(IP,CHARINDEX(',',IP)+1,LEN(IP)),
SUBSTRING(POLICY,CHARINDEX(',',POLICY)+1,LEN(POLICY))
FROM #Table
UNION ALL
SELECT _id ,_MONNAME , CASE WHEN CHARINDEX(',',_RemIP) = 0 THEN _RemIP ELSE
SUBSTRING(_RemIP,0,CHARINDEX(',',_RemIP)) END, CASE WHEN CHARINDEX(',',_RemPOLICY) = 0 THEN _RemPOLICY ELSE SUBSTRING(_RemPOLICY,0,CHARINDEX(',',_RemPOLICY)) END,
CASE WHEN CHARINDEX(',',_RemIP) = 0 THEN '' ELSE SUBSTRING(_RemIP,CHARINDEX(',',_RemIP)+1,LEN(_RemIP)) END,
CASE WHEN CHARINDEX(',',_RemPOLICY) = 0 THEN '' ELSE SUBSTRING(_RemPOLICY,CHARINDEX(',',_RemPOLICY)+1,LEN(_RemPOLICY)) END
FROM _CTE WHERE _RemIP <> '' OR _RemPOLICY <> ''
)
SELECT _id id,_MONNAME MONNAME, _IP IP , _POLICY POLICY
FROM _CTE

Related

SQL: Get Parent Tree Hierarchy from child

I have a table with Id, ParentId, Tree, TopParentId. Here's an example structure:
0
___/ \___
/ \
1 4
/ \ / \
2 7 5 8
/ /
3 6
Input Table:
Id ParentId Tree TopParentId
--- ---------- -------------------- --------
1 0 NULL NULL
2 1 NULL NULL
7 1 NULL NULL
3 2 NULL NULL
4 0 NULL NULL
5 4 NULL NULL
6 5 NULL NULL
8 4 NULL NULL
This is the output I am looking for when I pass Id = 3
OUTPUT:
Id ParentId Tree TopParentId
--- ---------- -------------------- --------
1 2 3 > 2 > 1 > 0 0
The CTE query should be able to handle multiple ids like 3,7
OUTPUT:
Id ParentId Tree TopParentId
--- ---------- -------------------- --------
3 2 3 > 2 > 1 > 0 0
7 1 7 > 1 > 0 0
The end goal is to take the Tree and TopParentId columns and update the corresponding Id
Here's the query I have tried till now:
WITH CTE AS (
SELECT Id, ParentId,0 AS [Level], CAST(Id AS varchar(1000)) AS Heirarchy,Id AS TopParentId
FROM dbo.table
WHERE Id IN (SELECT Id FROM table WHERE ParentId IS NULL)
UNION ALL
SELECT mgr.Id, mgr.ParentId, TASKCTE.[Level] +1 AS [Level],
CAST(( CAST(mgr.Id AS VARCHAR(1000)) + '>' + CTE.Heirarchy) AS varchar(1000)) AS Heirarchy, CTE.TopParentId
FROM CTE
INNER JOIN dbo.table AS mgr
ON CTE.Id = mgr.ParentId
)
UPDATE t SET t.[LEVEL] = TC.[LEVEL], t.ParentTree = TC.Heirarchy, t.TopParentId = TC.TopParentId
FROM dbo.table AS t
JOIN (SELECT * FROM CTE WHERE Id IN(SELECT DISTINCT Id FROM INSERTED) AND ParentId IS NOT NULL) TC
ON
t.Id = TC.Id
The above query works but its CPU/RAM intensive as it starts from Parent. I need the CTE to start from the Child but the Tree needs to be exactly same as the example outputs.
This appears to work:
drop table if exists #t;
with cte as (
select * from (values
(1, 0),
(2, 1),
(7, 1),
(3, 2),
(4, 0),
(5, 4),
(6, 5),
(8, 4)
)
as x(ID, ParentID)
)
select *
into #t
from cte;
with cte as (
select * ,
ID as [start] ,
ParentID as [FirstParent] ,
1 as [level] ,
cast(ID as varchar(max)) as Tree
from #t
where ID in (3, 7)
union all
select p.*,
c.[start] ,
c.FirstParent ,
c.[level] + 1 ,
cast(concat(c.Tree, ' > ', p.ID) as varchar(max))
from cte as c
join #t as p
on c.ParentID = p.ID
), top_level as (
select *, row_number() over (partition by [start] order by [level] desc) as rn
from cte
)
select [start] as ID ,
FirstParent as ParentID ,
concat(Tree, ' > ', ParentID) ,
ParentID as TopParentID
from top_level
where rn = 1;
By way of exposition, the first part just creates your test data (pro-tip: if you do this, people are more likely to be able to help since you've lowered the friction to do so!). The meat of the solution just uses the desired IDs as the "base case" for the recursion and the recursive step says "take the previous level's ParentID to find the next level's ID". The rest is just to keep track of the starting and ending point.
You simply need to create procedure to get the desired result.
Try this or variant of this will help.
Create proc CalculateLevel
( #passid nvarchar(max) )
As
Begin
declare #query nvarchar(max)
set #query = '
WITH CTE AS (
SELECT Id, ParentId,0 AS [Level], CAST(Id AS varchar(1000)) AS Heirarchy,Id AS TopParentId
FROM dbo.tab
WHERE Id IN (SELECT Id FROM tab WHERE ParentId IS NULL)
UNION ALL
SELECT mgr.Id, mgr.ParentId, CTE.[Level] +1 AS [Level],
CAST(( CAST(mgr.Id AS VARCHAR(1000)) + ''>'' + CTE.Heirarchy) AS varchar(1000)) AS Heirarchy, CTE.TopParentId
FROM CTE
INNER JOIN dbo.tab AS mgr
ON CTE.Id = mgr.ParentId
)
UPDATE t SET t.[LEVEL] = TC.[LEVEL], t.ParentTree = TC.Heirarchy, t.TopParentId = TC.TopParentId
FROM dbo.tab AS t
JOIN (SELECT * FROM CTE WHERE ParentId IS NOT NULL) TC
ON
t.Id = TC.Id where t.id in ( ' + #passid + ')'
print #query
exec sp_executesql #query
End

SQL: How to split a string at each character to display in a separate row?

I want to split a string at each character and display each of them in a separate row. I also need an extra column (Col2) which should display whether the character is a number or not (if number, then 1 else 0).
Example:
If the data is 'October 11, 2017', I should get
Col1 Col2
O 0
c 0
t 0
o 0
b 0
e 0
r 0
0
1 1
1 1
, 0
0
2 1
0 1
1 1
7 1
You can use this.
DECLARE #data VARCHAR(100) = 'October 11, 2017'
;WITH CTE AS
(
SELECT STUFF(#data,1,1,'') TXT, LEFT(#data,1) Col1
UNION ALL
SELECT STUFF(TXT,1,1,'') TXT, LEFT(TXT,1) Col1 FROM CTE
WHERE LEN(TXT) > 0
)
select Col1, ISNUMERIC(Col1) from CTE
Result:
Col1 Col2
---- -----------
O 0
c 0
t 0
o 0
b 0
e 0
r 0
0
1 1
1 1
, 1
0
2 1
0 1
1 1
7 1
Try this:
CREATE TABLE tbSeperate (Data NVARCHAR(100))
INSERT INTO tbSeperate SELECT 'October 11, 2017'
SELECT SUBSTRING(Data,Number,1) rt , CASE WHEN TRY_CAST(SUBSTRING(Data,Number,1) AS INT) IS NULL THEN 0
WHEN SUBSTRING(Data,Number,1) = ' ' THEN 0 ELSE 1 END c FROM tbSeperate
CROSS APPLY (SELECT DISTINCT number FROM master..spt_values WHERE number > 0 AND number <= LEN(Data))V
One method uses a recursive CTE:
with cte as (
select cast('October 11, 2017' as varchar(max)) as str,
cast(NULL as varchar(max)) as letter, 0 as lev
union all
select substring(str, 2, len(str)), left(str, 1), lev + 1
from cte
where str <> ''
)
select letter,
(case when letter between '0' and '9' then 1 else 0 end) as is_digit
from cte
where lev > 0;
If the string can have more than 99 characters, then you would want to use the maximum recursion option.
Here is a Rextester.
Using recursive CTE in MySQL we can achieve this.
following query will print the character of each string in different rows.
replace your table name and column name as per requirement.
WITH RECURSIVE cte
as
(
select 1 as n,1 as f,first_name ,'Temporary Variable' as name1 from customer
union
select (n+1) as n,f,first_name,substr(first_name,n,f) as name1 from cte
where substr(first_name,n,n)<>""
)
select n,name1,first_name from cte where n<>1 and first_name='KAREN'
Screenshot

SQL query create cross column

I have this table
customer | product | quantity
-------------------------------
CLI01 | A | 10
CLI01 | B | 20
CLI02 | A | 31
CLI03 | A | 10
CLI03 | C | 12
and I want to create in SQL Server this output:
customer | crossProduct | quantity
-----------------------------------
CLI01 | A+B | 30
CLI02 | Only A | 31
CLI03 | B+C | 22
Thanks in advance
Niko
If you only care about two products, then this is simple aggregation:
select customer,
(case when count(distinct product) > 2 then 'Lots of Products'
when min(product) = max(product) then 'Only ' + min(product)
else min(product) + '+' + max(product)
end) as crossproduct,
sum(quantity)
from t
group by customer;
If you care about more than two products, then you'll need to do aggregation string concatenation. That is a bit painful in SQL Server. Start by Googling "sql server aggregate string concatenation".
This is s sample:
----- Test Data ----------
DECLARE #TestData TABLE (customer VARCHAR(10),product VARCHAR(10),quantity INT)
INSERT INTO #TestData
SELECT 'CLI01','A',10 UNION ALL
SELECT 'CLI01','B',20 UNION ALL
SELECT 'CLI02','A',31 UNION ALL
SELECT 'CLI03','A',10 UNION ALL
SELECT 'CLI03 ','C',12
----- Query -------------
SELECT customer,CASE WHEN COUNT( DISTINCT t.product)=1 THEN 'Only ' ELSE '' END + LEFT(c.product,LEN(c.product)-1) AS Product,SUM(quantity) AS quantity
FROM #TestData AS t
CROSS APPLY(SELECT a.product+'+' FROM #TestData AS a WHERE a.customer=t.customer FOR XML PATH('')) c(product)
GROUP BY customer,c.product
ORDER BY t.customer
customer Product quantity
CLI01 A+B 30
CLI02 Only A 31
CLI03 A+C 22
Have you tried using stuff? This will give you what you need. Works with as many products as necessary, from sql 2008 onwards.
CREATE TABLE x (customer VARCHAR (20), product CHAR(1), quantity INT )
INSERT INTO x
VALUES( 'CLI01', 'A', 10),
( 'CLI01', 'B', 20),
( 'CLI02', 'A', 31),
( 'CLI03', 'A', 10),
( 'CLI03', 'C', 12)
SELECT x1.customer, x3.Products, SUM(x1.quantity)
FROM x x1
CROSS APPLY ( SELECT Products = STUFF( (select '+' + product AS [text()]
FROM x x2
WHERE x2.customer = x1.customer
FOR XML PATH ('') ), 1, 1,'') ) x3
GROUP BY x1.customer, x3.Products

How to order the numbers?

Table1
id value
---------
1 100
2A 200
2 300
10 500
8 200
....
Select *
from table1
order by id
Showing output as
id value
------------
1 100
10 500
2A 200
2 300
8 200
....
How to make a proper order?
Expected output
id value
----------
1 100
2 300
2A 200
8 200
10 500
....
If it is fixed that last character may be character then you can try following query
WITH A(ID)
AS
(
SELECT '1'
UNION ALL
SELECT '2C'
UNION ALL
SELECT '2A'
UNION ALL
SELECT '2'
UNION ALL
SELECT '10'
)
SELECT *
FROM A
ORDER BY
convert(int,
Case When IsNumeric(ID) = 0 then left(ID,len(id)-1)
Else ID END
) , Case When IsNumeric(ID) = 0 then RIGHT(ID,1) Else '0' END
and if it is variable then you can write a function that replace charecter with its ansi value or 0 . and then put order by close on that column .
CREATE FUNCTION [dbo].[RemoveNonAlphaCharacters](#Temp varchar(1000))
RETURNS int
AS
BEGIN
WHILE PatIndex ('%[^0-9]%', #Temp) > 0
SET #Temp = Stuff( #Temp, PatIndex('%[^0-9]%' , #Temp ), 1, '')
RETURN #Temp
END
SELECT id, value
FROM dbo.Table1
ORDER BY [dbo].[RemoveNonAlphaCharacters](id) ASC
SELECT
LEFT(ID,1),
RIGHT(ID,1),
*
FROM table1
ORDER BY LEFT(ID,1),RIGHT(ID,1)
should do the trick, I'm not even sure if the left and right is needed in the selected statement.
Select *
from table1
order by cast(replace(lower(id), 'abcdefg', '') as int),
replace(id, '0123456789','');
SELECT * FROM table1 ORDER BY CAST(id as varchar(50))

Get MAX value of a BIT column

I have a SELECT request with 'inner join' in the joined table is a column with bit type.
I want to select 1 if in the joined table is at most one value with 1. If it is not the case the value will be 0.
So If I have:
PERSID | NAME
1 | Toto
2 | Titi
3 | Tata
And the second table
PERSID | BOOL
1 | 0
1 | 0
2 | 0
2 | 1
I would like to have for result
Toto -> 0
Titi -> 1
Tata -> 0
I try this:
SELECT
sur.*
,MAX(bo.BOOL)
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS
But MAX is not available on BIT column.. So how can I do that?
Thanks,
you can cast it to an INT, and even cast it back to a BIT if you need to
SELECT
sur.*
,CAST(MAX(CAST(bo.BOOL as INT)) AS BIT)
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS
Try:
max(cast(bo.BOOL as int))
One way
SELECT
sur.*
,MAX(convert(tinyint,bo.BOOL))
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS
You can avoid the messy looking double cast by forcing an implicit cast:
SELECT
sur.*
,CAST(MAX(1 * bo.BOOL) AS BIT)
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS
If you want only those people with exactly one set bit:
declare #Surname as Table ( PersId Int, Name VarChar(10) )
insert into #Surname ( PersId, Name ) values
( 1, 'Toto' ), ( 2, 'Titi' ), ( 3, 'Tata' ), ( 4, 'Tutu' )
declare #Bool as Table ( PersId Int, Bool Bit )
insert into #Bool ( PersId, Bool ) values
( 1, 0 ), ( 1, 0 ),
( 2, 0 ), ( 2, 1 ),
( 4, 1 ), ( 4, 0 ), ( 4, 1 )
select Sur.PersId, Sur.Name, Sum( Cast( Bo.Bool as Int ) ) as [Sum],
case Sum( Cast( Bo.Bool as Int ) )
when 1 then 1
else 0
end as [Only One]
from #Surname as Sur left outer join
#Bool as Bo on Bo.PersId = Sur.PersId
group by Sur.PersId, Sur.Name
order by Sur.Name