I have to make JSON from the table.
Problem is, that I have to show NULL values, but hide all fixed constant values.
For example, I have dataset #table. In the JSON output I want to show all values, where Value != 0.
Deleting row (select Value from #table where cn = 'c') as 'c' isn't an option.
How can I do that?
create table #table (
Value int,
cn nvarchar(1)
)
insert into #table
values
(null, 'a'),
(null, 'b'),
(0, 'c'),
(3, 'd'),
(3, 'f')
select
(select Value from #table where cn = 'a') as 'a',
(select Value from #table where cn = 'b') as 'b',
(select Value from #table where cn = 'c') as 'c',
(select Value from #table where cn = 'd') as 'd',
(select Value from #table where cn = 'f') as 'f'
FOR JSON PATH, INCLUDE_NULL_VALUES
Expected output would be:
[{"a":null,"b":null,"d":3,"f":3}]
You can do it with a dynamic sql. I use conditional aggregation to pivot the table.
declare #cmd nvarchar(max) = 'select ' + stuff(
(select ', max(case cn when ''' + cn + ''' then value end) as ''' + cn +''''
from #table
where value <> 0 or value is null
for xml path(''))
, 1, 1, '') + ' from #table FOR JSON PATH, INCLUDE_NULL_VALUES';
--select #cmd;
exec (#cmd);
Another option builds on the type of the value column (int), so 0.5 is impossible as a column value. Cast the original values to decimal, then replace impossible values with null and remove decimal point and 0 after it.
select replace(replace(
(select
max(case cn when 'a' then value end) 'a',
max(case cn when 'b' then value end) 'b',
max(case cn when 'c' then value end) 'c',
max(case cn when 'd' then value end) 'd',
max(case cn when 'f' then value end) 'f'
from(
select coalesce(cast(value as decimal), 0.5) value, cn
from #table
where value <> 0 or value is null) t
FOR JSON PATH)
,'0.5', 'null'), '.0', '');
I do not think you can vary the number of selected column in a SQL statement, but you can do this:
declare #statement varchar(max) ='
select
(select Value from #table where cn = ''a'') as ''a'',
(select Value from #table where cn = ''b'') as ''b'',
--(select Value from #table where cn = ''c'') as ''c'',
(select Value from #table where cn = ''d'') as ''d'',
(select Value from #table where cn = ''f'') as ''f''
FOR JSON PATH, INCLUDE_NULL_VALUES'
exec sp_sqlexec #statement
Effectively build you SQL statement, to only select those values which are not equal to the value you want to hide.
It is not elegant, but one possibility is manually building the string:
select concat('[{',
string_agg(concat('"', cn, '":', coalesce(convert(varchar(255), value), 'null')
), ','
), '}]'
)
from t
where value <> 0 or value is null
Related
wanted to perform data masking according to mapping as below by using MSSQL 2008R2:
Mapping Table
A = C
B = A
C = E
1 = 3
2 = 1
3 = 9
Original
ABC123
Masked
CAE319
The idea would be using replace however the second replace function will replacing previous replaced value.
select Replace(Replace(Replace(Replace(Replace(REPLACE('ABC123', 'A', 'C'), 'B', 'A'), 'C', 'E'), '1', '3'), '2', '1'), '3', '9')
Result: CAE319
P.s. value edited, because Reverse or reverse replace cannot be use in this case
any idea?
If you want a more table approach.
There are two code segments below which will Mask or UnMask a string. Easily converted into a UDF or even placed in a CROSS APPLY
Declare #Mask table (MapFrom varchar(10),MapTo varchar(10))
Insert into #Mask values
('A','C'),
('B','D'),
('C','E'),
('1','2'),
('2','3'),
('3','9')
Declare #Yourtable table (ID int,SomeCol varchar(max))
Insert Into #Yourtable values
(1,'ABC123')
-- To Mask
Declare #U varchar(max) ='ABC123'
Select NewSting = Stuff((Select ''+S
From (
Select N
,S=IsNull(MapTo,Substring(#U,N,1))
From (Select Top (Len(#U)) N=Row_Number() Over (Order By (Select null)) From master..spt_values) N
Left Join #Mask on Substring(#U,N,1)=MapFrom
) X
Order By N
For XML Path ('')),1,0,'')
-- To UnMask
Declare #M varchar(max) = 'CDE239'
Select NewSting = Stuff((Select ''+S
From (
Select N
,S=IsNull(MapFrom,Substring(#M,N,1))
From (Select Top (Len(#M)) N=Row_Number() Over (Order By (Select null)) From master..spt_values) N
Left Join #Mask on Substring(#M,N,1)=MapTo
) X
Order By N
For XML Path ('')),1,0,'')
Just change the order of replace and reverse the result
select REVERSE( Replace(Replace(Replace(Replace(Replace(REPLACE('321CBA', '3', '9'), '2', '3'), '1', '2'), 'C', 'E'), 'B', 'D'), 'A', 'C'))
RESULT :
CDE239
EDIT:
Declare #Mask table (MapFrom varchar(10),MapTo varchar(10))
Insert into #Mask values
('A','C'),
('B','A'),
('C','E'),
('1','3'),
('2','1'),
('3','9')
DECLARE #pos INT
,#result VARCHAR(100)
,#maskfrom NCHAR(1)
,#mask_to NCHAR(1);
SET #result = 'ABC123';
SET #pos = 1
WHILE #pos < LEN(#result) + 1
BEGIN
SELECT #mask_to = MapTo
FROM #mask
WHERE MapFrom = substring(#result, #pos, 1)
SET #result = STUFF(#result, #pos, 1, #mask_to);
SET #pos = #pos + 1;
END
SELECT #result
RESULT
CAE319
I have a table set up that tracks changes to a user's account.
It has ID, UserAccountNo, OldVal, NewVal, ChangeColumnName columns.
I have a query set up similar to this:
Select case
when ChangeColumnName = 'Address1' then NewVal else '' end as Address1,
when ChangeColumnName = 'Address2' then NewVal else '' end as Address2,
when ChangeColumnName = 'City' then NewVal else '' end as City,
when ChangeColumnName = 'State' then NewVal else '' end as State,
when ChangeColumnName = 'Zip' then NewVal else '' end as Zip,
when ChangeColumnName = 'Phone' then NewVal else '' end as Phone
from table
Where (Conditions)
If someone changes the city, state, and zip, there are 3 entries in the table. When I run this query, I get 3 rows returned. I would like to get them all together in one row, and haven't been able to figure out how.
When I tried using groupby with max(colname) as suggested in other posts, it gives the max NewVal value, so I end up with email addresses in Phone columns.
Is this possible to do in SQL 2008 without reforming the entire table?
Try this
create table #t
(
id int,
userAccountNo int,
oldVal varchar(255),
newVal varchar(255),
changeColName varchar(255)
);
insert #t values (1, 1, '123 main st', '123 s. main st.', 'Address1'),
(2, 1, 'Springville', 'Springfield', 'City'),
(3, 1, 'Springfield', 'N. Springfield', 'City'),
(4, 2, '12345', '12346', 'Zip');
with U as (select distinct userAccountNo from #t),
Address1 as (select userAccountNo, newVal from #t as T1 where changeColName = 'Address1' and id >=ALL
(select id from #t as T2 where T1.userAccountNo = T2.userAccountNo and T1.changeColName = T2.changeColName)),
City as (select userAccountNo, newVal from #t as T1 where changeColName = 'City' and id >=ALL
(select id from #t as T2 where T1.userAccountNo = T2.userAccountNo and T1.changeColName = T2.changeColName)),
Zip as (select userAccountNo, newVal from #t as T1 where changeColName = 'Zip' and id >=ALL
(select id from #t as T2 where T1.userAccountNo = T2.userAccountNo and T1.changeColName = T2.changeColName))
select
U.userAccountNo,
A1.newVal as [Address1],
C.newVal as [City],
Z.newVal as [Zip]
from
U
full outer join Address1 as A1 on U.userAccountNo = A1.userAccountNo
full outer join City as C on U.userAccountNo = C.userAccountNo
full outer join Zip as Z on U.userAccountNo = Z.userAccountNo;
And if it seems to work it can be extended to cover all of your columns.
I suggest that you use pivot command, use this script and let me know :
IF OBJECT_ID('_temp') IS NOT NULL DROP TABLE _temp
SELECT *
INTO _temp
FROM (
Select 'PostalCode' AS ChangeColumnName, '95100' AS NewValue UNION ALL
Select 'City' AS ChangeColumnName, 'Argenteuil' AS NewValue UNION ALL
Select 'LastName' AS ChangeColumnName, 'DAOUI' AS NewValue UNION ALL
Select 'FirstName' AS ChangeColumnName, 'Youssef' AS NewValue UNION ALL
Select 'Phone Number' AS ChangeColumnName, '00212 6 60 93 36 12' AS NewValue
) AS Temp
DECLARE #v_ListeColonnes VARCHAR(MAX) = ''
,#v_sql VARCHAR(MAX) = ''
SELECT #v_ListeColonnes = #v_ListeColonnes + ',' + QUOTENAME(ChangeColumnName)
FROM _temp
IF LEN(#v_ListeColonnes) > 1
BEGIN
SELECT #v_ListeColonnes = RIGHT(#v_ListeColonnes, LEN(#v_ListeColonnes)-1)
SET #v_sql = 'SELECT '+CHAR(13)
+' ' + #v_ListeColonnes + ' '+CHAR(13)
+'FROM _temp '+CHAR(13)
+'PIVOT (MAX(NewValue) '+CHAR(13)
+' FOR ChangeColumnName in(' + #v_ListeColonnes + ')) as pvt '+CHAR(13)
EXEC(#v_sql)
END
IF OBJECT_ID('_temp') IS NOT NULL DROP TABLE _temp
I hope this will help you.
I assumed you need one row and one column for all changes, it works for any number of columns changed.
SQL FIDDLE TEST
declare #changes as varchar(max)
declare #UserAccountNo int
set #UserAccountNo=1
set #changes=''
select #changes=#changes + ColumnChanged +'-'
from changes where UserAccountNo=#UserAccountNo
select #UserAccountNo 'UserAccountNo', #changes 'Changes'
I have a problem where I want to replace characters
I am using replace function but that is not giving desired output.
Values of column table_value needs to replaced with their fill names like
E - Email
P - Phone
M - Meeting
I am using this query
select table_value,
replace(replace(replace(table_value, 'M', 'MEETING'), 'E', 'EMAIL'), 'P', 'PHONE') required_value
from foobar
so second required_value row should be EMAIL,PHONE,MEETING and so on.
What should I do so that required value is correct?
The below will work (even it's not a smart solution).
select
table_value,
replace(replace(replace(replace(table_value, 'M', 'MXXTING'), 'E', 'XMAIL'), 'P', 'PHONX'), 'X', 'E') required_value
from foobar
You can do it using CTE to split the table values into E, P and M, then replace and put back together.
I assumed each record has a unique identifer Id but please replace that with whatever you have.
;WITH cte
AS
(
SELECT Id, SUBSTRING(table_value, 1, 1) AS SingleValue, 1 AS ValueIndex
FROM replacetable
UNION ALL
SELECT replacetable.Id, SUBSTRING(replacetable.table_value, cte.ValueIndex + 1, 1) AS SingleValue, cte.ValueIndex + 1 AS ValueIndex
FROM cte
INNER JOIN replacetable ON cte.ValueIndex < LEN(replacetable.table_value)
)
SELECT DISTINCT Id,
STUFF((SELECT DISTINCT ','+ CASE SingleValue
WHEN 'E' THEN 'EMAIL'
WHEN 'P' THEN 'PHONE'
WHEN 'M' THEN 'MEETING'
END
FROM cte c
WHERE c.Id = cte.Id
AND SingleValue <> ','
FOR XML PATH ('')),1,1,'')
FROM cte
Sorry , for mess code, maybe this is not best way to solve this, but what I've tried:
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE Function [dbo].[fn_CSVToTable]
(
#CSVList Varchar(max)
)
RETURNS #Table TABLE (ColumnData VARCHAR(100))
AS
BEGIN
DECLARE #S varchar(max),
#Split char(1),
#X xml
SELECT #Split = ','
SELECT #X = CONVERT(xml,' <root> <s>' + REPLACE(#CSVList,#Split,'</s> <s>') + '</s> </root> ')
INSERT INTO #Table
SELECT CASE RTRIM(LTRIM(T.c.value('.','varchar(20)'))) WHEN 'M' THEN 'Meeting'
WHEN 'P' THEN 'Phone'
WHEN 'E' THEN 'Email'
End
FROM #X.nodes('/root/s') T(c)
RETURN
END
GO
Then When I run this:
Select Main.table_value,
Left(Main.ColumnData,Len(Main.ColumnData)-1) As ColumnData
From
(
Select distinct tt2.table_value,
(
Select tt1.ColumnData+ ',' AS [text()]
From (
SELECT
*
FROM dbo.TestTable tt
CROSS APPLY dbo.fn_CSVToTable(tt.table_value)
) tt1
Where tt1.table_value = tt2.TABLE_value
ORDER BY tt1.table_value
For XML PATH ('')
) ColumnData
From dbo.TestTable tt2
) [Main]
I get this:
table_value ColumnData
-------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
E,P Email,Phone
E,P,M Email,Phone,Meeting
P,E Phone,Email
P,M Phone,Meeting
(4 row(s) affected)
You could also use a DECODE or CASE to translate the values.
ID State Name Department City
1 O George Sales Phoenix
1 N George Sales Denver
2 O Michael Order Process San diego
2 N Michael Marketing San jose
I got a situation that I need to convert the above tables values to the following format.(Consider the top row is column names)
ID Column OldValue New Value
1 Department Phoenix Denver
2 Department Order Process Marketing
2 City San diego San jose
I.e : I need to capture the changed column values for a table from its old and new records and record them in a different table.But the problem is we have many tables like that and the column names and no of columns are different for each table.
If anyone come with a solution that would be greatly appreciated..!
Thank you in advance.
Is this what you want?
ID Column OldValue New Value
1 City Phoenix Denver
2 Department Order Process Marketing
2 City San Diego San jose
Here is the dynamic code:
DECLARE #sqlStm varchar(max);
DECLARE #sqlSelect varchar(max);
DECLARE #tablename varchar(200);
SET #tablename = 'testtable';
-- Assume table has ID column and State column.
SET #sqlSelect = ''
SET #sqlStm = 'WITH old AS
(
SELECT *
FROM '+#tablename+'
WHERE State=''O''
), new AS
(
SELECT *
FROM '+#tablename+'
WHERE State=''N''
)';
DECLARE #aCol varchar(128)
DECLARE curCols CURSOR FOR
SELECT column_name
FROM information_schema.columns
WHERE table_name = #tablename
AND UPPER(column_name) NOT IN ('ID','STATE')
OPEN curCols
FETCH curCols INTO #aCol
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #sqlStm = #sqlStm +
', changed'+#aCol+' AS
(
SELECT n.ID, '''+#aCol+''' AS [Column], o.['+#aCol+'] AS oldValue, n.['+#aCol+'] AS newValue
FROM new n
JOIN old o ON n.ID = o.ID AND n.['+#aCol+'] != o.['+#aCol+']
)'
IF LEN(#sqlSelect) > 0 SET #sqlSelect = #sqlSelect + ' UNION ALL '
SET #sqlSelect = #sqlSelect + '
SELECT * FROM changed'+#aCol
FETCH curCols INTO #aCol
END
CLOSE curCols
DEALLOCATE curCols
SET #sqlSelect = #sqlSelect + '
ORDER BY id, [Column]'
PRINT #sqlStm+#sqlSelect
EXEC (#sqlStm+#sqlSelect)
Which in my test output the following:
WITH old AS
(
SELECT *
FROM testtable
WHERE State='O'
), new AS
(
SELECT *
FROM testtable
WHERE State='N'
), changedName AS
(
SELECT n.ID, 'Name' AS [Column], o.[Name] AS oldValue, n.[Name] AS newValue
FROM new n
JOIN old o ON n.ID = o.ID AND n.[Name] != o.[Name]
), changedDepartment AS
(
SELECT n.ID, 'Department' AS [Column], o.[Department] AS oldValue, n.[Department] AS newValue
FROM new n
JOIN old o ON n.ID = o.ID AND n.[Department] != o.[Department]
), changedCity AS
(
SELECT n.ID, 'City' AS [Column], o.[City] AS oldValue, n.[City] AS newValue
FROM new n
JOIN old o ON n.ID = o.ID AND n.[City] != o.[City]
)
SELECT * FROM changedName UNION ALL
SELECT * FROM changedDepartment UNION ALL
SELECT * FROM changedCity
ORDER BY id, [Column]
Original answer below:
I would do it like this -- because I think it is clearer than other ways which might be faster:
with old as
(
Select ID, Name,Department,City
From table1
Where State='O'
), new as
(
Select ID, Name,Department,City
From table1
Where State='N'
), oldDepartment as
(
Select ID, 'Department' as Column, o.Department as oldValue, n.Department as newValue
From new
join old on new.ID = old.ID and new.Department != old.Department
), oldCity as
(
Select ID, 'City' as Column, o.City as oldValue, n.City as newValue
From new
join old on new.ID = old.ID and new.City != old.City
)
select * from oldDepartment
union all
select * from oldCity
Depending on many things (size of tables and indexes etc) it might actually be faster than using pivots or cases or grouping. It really depends on your data. If this is a one-off run I'd just go for the easiest to grok.
The cleanest approach is probably to unpivot the data and then use aggregation. This does require custom coding for each table, which you might be able to generalize by using some form a dynamic SQL.
For your particular example, here is an illustration of what to do:
select id, col,
max(case when OldNew = 'Old' then value end) as OldValue,
max(case when OldNew = 'New' then value end) as NewValue
from ((select ID, OldNew, 'Name' as col, Name as value
from t
) union all
(select ID, OldNew, 'Department' as col, Department as value
from t
) union all
(select ID, OldNew, 'City' as col, City as value
from t
)
) unpvt
group by id, col
having max(value) <> min(value) and max(value) is not null;
This is for illustration purposes. The unpivot can be done more efficiently than using union all, particularly when there are many scans. Here is a more efficient version, although the exact syntax depends on the database:
select id, col,
max(case when OldNew = 'Old' then value end) as OldValue,
max(case when OldNew = 'New' then value end) as NewValue
from (select ID, OldNew, cols.col,
(case when cols.col = 'Name' then Name
when cols.col = 'Department' then Department
when cols.col = 'City' then City
end) as value
from t cross join
(select 'Name' as col union all select 'Department' union all select 'City') cols
) unpvt
group by id, col
having max(value) <> min(value) and max(value) is not null;
This is more efficient because it will typically only scan your table once, rather than once for each column as in the union all version.
In either version, there is an implicit assumption that all the columns have the same character type. This is implicit in the format you are converting to, where all the values are in a single column.
Is there any way to count all fields with null values for specific record excluding PrimaryKey column?
Example:
ID Name Age City Zip
1 Alex 32 Miami NULL
2 NULL 24 NULL NULL
As output I need to get 1 and 3. Without explicitly specifying column names.
declare #T table
(
ID int,
Name varchar(10),
Age int,
City varchar(10),
Zip varchar(10)
)
insert into #T values
(1, 'Alex', 32, 'Miami', NULL),
(2, NULL, 24, NULL, NULL)
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select *
from #T as T2
where T1.ID = T2.ID
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Result:
ID NullCount
----------- -----------
1 1
2 3
Update:
Here is a better version. Thanks to Martin Smith.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Update:
And with a bit faster XQuery expression.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(//*/#ns:nil)', 'int') as NullCount
from #T as T1
SELECT id,
CASE WHEN Name IS NULL THEN 1 ELSE 0 END +
CASE WHEN City IS NULL THEN 1 ELSE 0 END +
CASE WHEN Zip IS NULL THEN 1 ELSE 0 END
FROM YourTable
If you do not want explicit column names in query, welcome to dynamic querying
DECLARE #sql NVARCHAR(MAX) = ''
SELECT #sql = #sql + N' CASE WHEN '+QUOTENAME(c.name)+N' IS NULL THEN 1 ELSE 0 END +'
FROM sys.tables t
JOIN sys.columns c
ON t.object_id = c.object_id
WHERE
c.is_nullable = 1
AND t.object_id = OBJECT_ID('YourTableName')
SET #sql = N'SELECT id, '+#sql +N'+0 AS Cnt FROM [YourTableName]'
EXEC(#sql)
This should solve your problem:
select count (id)
where ( isnull(Name,"") = "" or isnull(City,"") = "" or isnull(Zip,"") = "" )
Not a smart solution, but it should do the work.
DECLARE #tempSQL nvarchar(max)
SET #tempSQL = N'SELECT '
SELECT #tempSQL = #tempSQL + 'sum(case when ' + cols.name + ' is null then 1 else 0 end) "Null Values for ' + cols.name + '",
sum(case when ' + cols.name + ' is null then 0 else 1 end) "Non-Null Values for ' + cols.name + '",' FROM sys.columns cols WHERE cols.object_id = object_id('TABLE1');
SET #tempSQL = SUBSTRING(#tempSQL, 1, LEN(#tempSQL) - 1) + ' FROM TABLE1;'
EXEC sp_executesql #tempSQL