Convert and merge two string as a table - sql

Assume we have two below strings:
DECLARE #AllowedCardBoardIds NVARCHAR(MAX) = '1,2,3,4,5,6'
DECLARE #AllowedCardBoardIdsAccessTypes NVARCHAR(MAX) = '11,22,33,44,55,66'
Also, assume we have a function to split a string as a table.
It returns a sequence values
Select * FROM dbo.SplitString(',', #AllowedCardBoardIds)
-- result:
Value
------
1
2
3
4
5
6
Now I want to convert two string to table and merge them as follows:
Id | AccessType
--------------------
1 | 11
2 | 22
3 | 33
4 | 44
5 | 55
6 | 66
How should I do it?
I wrote a query as follows:
DECLARE #AllowedCardBoardsTable TABLE(
Id INT NOT NULL,
AccessType INT NOT NULL
)
INSERT INTO #AllowedCardBoardsTable
(
Id, AccessType
)
SELECT id.[Value], accessType.[Value]
FROM dbo.SplitString(',', #AllowedCardBoardIds) AS id
But I don't know how to fill AccessType column !

Please try the following solution.
SQL
DECLARE #AllowedCardBoardsTable TABLE(
Id INT NOT NULL,
AccessType INT NOT NULL
);
DECLARE #AllowedCardBoardIds NVARCHAR(MAX) = '1,2,3,4,5,6';
DECLARE #AllowedCardBoardIdsAccessTypes NVARCHAR(MAX) = '11,22,33,44,55,66';
DECLARE #separator CHAR(1) = ',';
;WITH rs AS
(
SELECT CardBoardIds = CAST('<root><r><![CDATA[' +
REPLACE(#AllowedCardBoardIds, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
, CardBoardIdsAccessTypes = CAST('<root><r><![CDATA[' +
REPLACE(#AllowedCardBoardIdsAccessTypes, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
)
, rs2 AS
(
SELECT rn = ROW_NUMBER() OVER(ORDER BY (t.c))
, Id = c.value('.', 'INT')
FROM rs
CROSS APPLY CardBoardIds.nodes('/root/r/text()') AS t(c)
)
, rs3 AS
(
SELECT rn = ROW_NUMBER() OVER(ORDER BY (t.c))
, Id = c.value('.', 'INT')
FROM rs
CROSS APPLY CardBoardIdsAccessTypes.nodes('/root/r/text()') AS t(c)
)
INSERT INTO #AllowedCardBoardsTable (Id, AccessType)
SELECT rs2.id, rs3.Id
FROM rs2 INNER JOIN rs3 ON rs3.rn = rs2.rn;
-- test
SELECT * FROM #AllowedCardBoardsTable;
Output
+----+------------+
| Id | AccessType |
+----+------------+
| 1 | 11 |
| 2 | 22 |
| 3 | 33 |
| 4 | 44 |
| 5 | 55 |
| 6 | 66 |
+----+------------+

I would suggest you use one of the built-in string-splitting methods, rather than trying to roll your own.
Unfortunately, current versions of SQL Server do not support STRING_SPLIT with an ordinal column. But in this case you can hack it with OPENJSON
DECLARE #AllowedCardBoardIds NVARCHAR(MAX) = '1,2,3,4,5,6';
DECLARE #AllowedCardBoardIdsAccessTypes NVARCHAR(MAX) = '11,22,33,44,55,66';
SELECT
id = acb.value,
AccessType = at.value
FROM OPENJSON('[' + #AllowedCardBoardIds + ']') acb
JOIN OPENJSON('[' + #AllowedCardBoardIdsAccessTypes + ']') at
ON at.[key] = acb.[key];
db<>fiddle
I strongly suggest you store your data properly normalized in the first place, such as in a table variable, temp table, or normal table.

Related

How to get SQL query result column name as first row

I have a dynamic SQL query that gets me result sets after execution. However, the UI model that I am rendering results back from SQL server engine doesn't provide a way to render query column names.
Due to the dynamic nature of the query, I can't hard code the column names at design time. So my question is how do I get column names along with the data set returned by the query?
This Query:
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
convert(date, DATEADDED) DATEADDED
,COUNT(1) as NUMBEROFRECORDS
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
Gives me this table (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
But I want this (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
Thanks
It's doable, but not very pretty. A Stored Procedure where you pass the dynamic SQL would be much cleaner
We're essentially doing Dynamic SQL within Dynamic SQL
One caveat: I reserved the field RN
Example (Using my FRED Series Data)
-- This is Your Base/Initial Query, or the only portion you need to supply
Declare #SQL varchar(max) = 'Select Updated as Updated,Count(*) as NumberOfRecords From [dbo].[FRED-Series] Group By Updated'
Select #SQL = '
;with cte0 as ('+#SQL+')
, cte1 as (Select *,RN = Row_Number() over (Order By (Select null)) From cte0 )
, cte2 as (
Select A.RN,C.*
From cte1 A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value(''local-name(.)'',''varchar(100)'')
,Value = attr.value(''.'',''varchar(max)'')
,ColNr = Row_Number() over (Order By (Select Null))
From B.XMLData.nodes(''/row'') as A(r)
Cross Apply A.r.nodes(''./#*'') AS B(attr)
Where attr.value(''local-name(.)'',''varchar(100)'') not in (''RN'')
) C
)
Select Distinct RN=0,Item,Value=Item,ColNr Into #Temp From cte2 Union All Select * from cte2
Declare #SQL varchar(max) = Stuff((Select '','' + QuoteName(Item) From #Temp Where RN=0 Order by ColNr For XML Path('''')),1,1,'''')
Select #SQL = ''Select '' + #SQL + '' From (Select RN,Item,Value From #Temp ) A Pivot (max(Value) For [Item] in ('' + #SQL + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
Updated NumberOfRecords
Updated NumberOfRecords
2017-03-22 597
2017-03-23 40
2017-03-20 228
2017-03-21 1404
Just some Commentary
cte0 is your primary query
cte1 will take the results of your initial query and add a Row Number
cte2 will dynamically unpivot your data
The results of cte2 are dropped into a #temp table for convenience (assuming this is allowed)
Then we perform a dynamic pivot
Union a static query with the column names. You have to cast the results of the second query to varchar or nvarchar so they are the same data type as your column names.
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
''DATEADDED'' AS [DATEADDED]
,''NUMBEROFRECORDS'' AS [NUMBEROFRECORDS]
SELECT
CAST(convert(date, DATEADDED) AS NVARCHAR(MAX)
,CAST(COUNT(1) AS NVARCHAR(MAX))
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
With this said, you should be able to reference the column names via code and not have to add them to the query. This way you could keep the data types of the result set.

split the accounid and dbids between braces into two columns

I have a query to split the accounid and dbids between braces separately.
example: [14801].[42]
acid scid
14801 42
but I'm not able to separate after comma ,
example :
[27784].[41],[27781].[41],[27779].[41]
need a query to split this data into rows
you need to create function to split values into rows
create function [dbo].[udf_splitstring] (#tokens varchar(max),
#delimiter varchar(5))
returns #split table (
token varchar(200) not null )
as
begin
declare #list xml
select #list = cast('<a>'
+ replace(#tokens, #delimiter, '</a><a>')
+ '</a>' as xml)
insert into #split
(token)
select ltrim(t.value('.', 'varchar(200)')) as data
from #list.nodes('/a') as x(t)
return
end
select * from into #a udf_splitstring ('[27784].[41],[27781].[41],[27779].[41]',',')
output
[27784].[41]
[27781].[41]
[27779].[41]
coming result store in one temp table
SELECT TOKEN,REPLACE(REPLACE(SUBSTRING(TOKEN,0,CHARINDEX('.',TOKEN)),'[',''),']','') AS first_id
,REPLACE(REPLACE(REVERSE(SUBSTRING(REVERSE(TOKEN),0,CHARINDEX('.',REVERSE(TOKEN)))),'[',''),']','') AS second_id
FROM #a
output
TOKEN first_id second_id
[27784].[41] 27784 41
[27781].[41] 27781 41
[27779].[41] 27779 41
Try this:
DECLARE #START_ID VARCHAR(100)='[27784].[41],[27781].[41],[27779].[41]'
DECLARE #ID VARCHAR(MAX)
DECLARE #COUNT INT
DECLARE #TEMP TABLE(C1 VARCHAR(MAX))
WHILE( CHARINDEX(',',#START_ID))>0
BEGIN
INSERT INTO #TEMP SELECT SUBSTRING(#START_ID,0,CHARINDEX(',',#START_ID))
SET #START_ID=(SELECT REPLACE(#START_ID,(SUBSTRING(#START_ID,0,CHARINDEX(',',#START_ID)+1)),''))
END
INSERT INTO #TEMP SELECT #START_ID
SELECT C1,REPLACE(REPLACE(SUBSTRING(C1,0,CHARINDEX('.',C1)),'[',''),']','') AS SOURCE_ACCOUT_ID
,REPLACE(REPLACE(REVERSE(SUBSTRING(REVERSE(C1),0,CHARINDEX('.',REVERSE(C1)))),'[',''),']','') AS SOURCE_DATABASE_ID
FROM #TEMP
select n.ids.value ('id[1]','int') as accountid
,n.ids.value ('id[2]','int') as dbid
from (select cast (replace('<r><e><id>'+replace(replace(replace(
ids,'[',''),']',''),',','</id></e><e><id>')+
'</id></e></r>','.','</id><id>') as xml) as x
from mytable
) t
cross apply x.nodes ('/r/e') n(ids)
+-----------+------+
| accountid | dbid |
+-----------+------+
| 27784 | 41 |
+-----------+------+
| 27781 | 41 |
+-----------+------+
| 27779 | 41 |
+-----------+------+
| 28021 | 30 |
+-----------+------+
| 28024 | 30 |
+-----------+------+
| 29007 | 56 |
+-----------+------+
DDL + DML for demo
create table mytable (ids varchar(1000))
insert into mytable values
('[27784].[41],[27781].[41],[27779].[41]')
, ('[28021].[30],[28024].[30]')
, ('[29007].[56]')

Convert Decimal to String on Pivot

example Code
Declare
#table1 VARCHAR(MAX)
Set #table1 = 'Select * from #tempTbl'
Declare
#List VARCHAR(MAX),
#Pivot VARCHAR(MAX)
Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB'
Set #Pivot = '
SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, *
FROM (
Select Distinct
c.UserId,
d.Name,
b.TrxCd
SUM(b.Amount) As Amount
From tbl_InvH a
Inner Join tbl_InvD b on a.subCd = b.subCd and a.InvNo = b.InvNo
Left Join tbl_User c On a.UserId = c.UserId
Left Join tbl_Personnel d on c.PersonnelId = d.PersonnelId
Group By c.UserId, d.Name, b.TrxCd
) as s
PIVOT
(
SUM(Amount)
FOR TrxCd IN (' + #List + ')
)AS pvt'
Declare #Result nVarchar(MAX)
Set #Result = #table1 + '
Union All
' + #Pivot
Exec sp_executesql #Result
I want to Convert Field Amount from decimal to String, because after this I want to UNION with another tables, but field amount and field from another table is different type.
I have tried CAST(SUM(Amount) as Varchar) But Error :
'CAST' is not a recognized aggregate function.
I can't Convert on Main Select because Field of TrxCd is Dynamic
Result Of Pivot
RowIndex | UserId | Name | IT01 | IT02 | IT03 | IT04
--------------------------------------------------------------------------------
1 | John | John Ivy | 2,000 | 2,000 | 1,000 | 5,000
2 | Merry | Merry Ish | 1,000 | 1,000 | 1,000 | 6,000
other Table
RowIndex | UserId | Name | Transac1 | Transac2 | Transac3 | Transac4
-------------------------------------------------------------------------------------------------
1 | John | John Ivy | Trx Bank A | Trx Bank B | Trx Bank C | Trx Bank D
What should I do to Convert Field Amount from Pivot.
Because of the dynamic nature, you could take the same #List expression Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB' and create a second for the dynamic casting in the main select;
Select #List2 = ISNULL(#List2 + ',', '') + 'cast(' + TrxCd + 'as varchar)' From TransacMaster Where Module = 'CB'
Then use this and additional columns required to replace the asterix in the main select;
'SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, UserId, Name,' + #List2 +'....'

SQL Server : query to pivot and show not set values

I have a SQL Server table with 3 columns: UserID, SettingID, SettingValue.
Example
UserID | SettingID | Value
1 | 10 |0
1 | 11 |1
1 | 14 |0
2 | 10 |1
2 | 13 |1
Need to convert into columns per Setting ID
Can be that there is no row for Setting ID -> want to grab that non exisitng and Display as "not set"
Desired result:
UserID | Setting10 | Setting11 | Setting13 | Setting14
1 | Off | On | not set | off
2 | On | not set | on | not set
The list of SettingID is given, there is no need to analyze and automatically find them.
Have no idea how to approach this
SQL Fiddle
MS SQL Server 2008 Schema Setup:
DECLARE #Table TABLE (UserID INT, SettingID INT, Value INT )
INSERT INTO #Table VALUES
(1 , 10 ,0),
(1 , 11 ,1),
(1 , 14 ,0),
(2 , 10 ,1),
(2 , 13 ,1)
Query 1:
SELECT UserID
,COALESCE( Setting10 , 'not set') Setting10
,COALESCE( Setting11 , 'not set') Setting11
,COALESCE( Setting13 , 'not set') Setting13
,COALESCE( Setting14 , 'not set') Setting14
FROM (
SELECT UserID
, 'Setting' + CAST(SettingID AS VARCHAR(10)) AS Settings
,CASE Value WHEN 0 THEN 'off'
WHEN 1 THEN 'on'
END AS Value
FROM #Table
) t
PIVOT (MAX(Value)
FOR Settings
IN (Setting10 , Setting11 , Setting13 , Setting14)
)p
Results:
| UserID | Setting10 | Setting11 | Setting13 | Setting14 |
|--------|-----------|-----------|-----------|-----------|
| 1 | off | on | not set | off |
| 2 | on | not set | on | not set |
As you say the list of settingid is fixed, you can get the desired result with conditional aggregation.
select userid,
max(case when settingid = '10' then
case when value = 0 then 'Off'
when value = 1 then 'On'
else 'not set' end
end) as setting10,
--proceed similarly for other settings
from yourtable
group by userid;
Here is the answer of your question
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #ColumnCaseName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(SettingID)
FROM (SELECT DISTINCT SettingID FROM TableName) AS TableName
SELECT #ColumnCaseName= ISNULL(#ColumnCaseName + ',','')
+ 'case when '+QUOTENAME(SettingID)+' = 0 then ''Off'' when '+QUOTENAME(SettingID)+' = 1 then ''On'' else ''not set'' end '
+ QUOTENAME(SettingID)
FROM (SELECT DISTINCT SettingID FROM TableName) AS TableName
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT UserID, ' + #ColumnCaseName + '
FROM TableName
PIVOT(Max(Value)
FOR SettingID IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This will work for any number of rows you add in your table.
Happy Coding

updating values in one table from another table using DYNAMIC SQL in MSSQL

I have a temp table A having 2 columns col1: ID col2: Value generated using XML. I need to update the columns in table B corresponding to column1:ID of table A with values present in col2: Value of table A. NOTE: Only specific columns and not all in table B need to be updated
table A
+----+-------+
| ID | Value |
+----+-------+
| 1 | 533 |
| 5 | 34 |
| 6 | 56 |
+----+-------+
table B
+-----+---+---+---+----+----+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+-----+---+---+---+----+----+---+
| 533 | | | | 34 | 56 | |
+-----+---+---+---+----+----+---+
declare dynsql varchar(4000) = ' update table B set....... '
Try following query:
UPDATE TableB
SET [1] = ISNULL(z.[1],TableB.[1]),
[2] = ISNULL(z.[2],TableB.[2]),
[3] = ISNULL(z.[3],TableB.[3]),
[4] = ISNULL(z.[4],TableB.[4]),
[5] = ISNULL(z.[5],TableB.[5]),
[6] = ISNULL(z.[6],TableB.[6]),
[7] = ISNULL(z.[7],TableB.[7])
FROM (
SELECT [1],[2],[3],[4],[5],[6],[7]
FROM (SELECT Id, Value
FROM TableA)AS p
PIVOT (MAX(Value) FOR Id IN([1],[2],[3],[4],[5],[6],[7]))AS pvt
)z
EDIT
In order to have dynamic pivot use following query:
DECLARE #columns1 NVARCHAR(1000) = '',
#columns2 NVARCHAR(1000) = '',
#sql NVARCHAR(MAX)
SELECT #Columns1 = STUFF((SELECT ',['+Value+'] = ISNULL(z.['+Value+'],TableB.['+Value+'])'
FROM (SELECT DISTINCT Value FROM TableA)z
FOR XML PATH('')),1,1,''),
#Columns2 = STUFF((SELECT ',['+Value+']'
FROM (SELECT DISTINCT Value FROM TableA)z
FOR XML PATH('')),1,1,'')
SET #sql = 'Update TableB
Set '+#columns1+'
From (
Select '+ #columns2+'
From (Select Id, Value From TableA) AS p
Pivot (MAX(Value) For Id IN ('+#columns2+')) AS Pvt
)z'
EXECUTE(#sql)
INSERT INTO tb
SELECT [1],[2],[3],[4],[5],[6],[7] FROM
(SELECT
id,value
from ta)as p
PIVOT
(AVG(value) FOR id IN([1],[2],[3],[4],[5],[6],[7])
)as bah
Fiddle