Is there a way to query JSON column in SQL Server ignoring capitalization of keys? - sql

I am trying to query a JSON column that has mixed capitalization. For instance, some rows have keys that are all lower case like below:
{"name":"Screening 1","type":"template","pages":[{"pageNumber":1,...}
However, some of the rows have keys that are capitalized on its first letter like this:
{"Type":"template","Name":"Screening2","Pages":[{"PageNumber":1,...}
Unfortunately, SQL Server seems to only supports JSON path system that is case sensitive. Therefore, I can't query on all rows successfully. If I use lower case path like '$.pages' in a query like below:
SELECT ST.Id AS Screening_Tool_Id
, ST.Name AS Screening_Tool_Name
, ST.Description AS Screening_Tool_Description
, COUNT(JSON_VALUE (SRQuestions.value, '$.id')) AS Question_Specific_Id
FROM dbo.ScreeningTemplate AS ST
CROSS APPLY OPENJSON(ST.Workflow, '$.pages') AS SRPages
CROSS APPLY OPENJSON(SRPages.Value, '$.sections') AS SRSections
I miss any row that has capitalized keys. Is there any way to query all rows ignoring their capitalization?

According to MS, looks like you're stuck with a case-sensitive query:
When OPENJSON parses a JSON array, the function returns the indexes of
the elements in the JSON text as keys.+ The comparison used to match
path steps with the properties of the JSON expression is
case-sensitive and collation-unaware (that is, a BIN2 comparison).
https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql
If the only variations are in the capitalization of the first character, you could try to work around this limitation by creating queries with the variants and UNION the results together.

Maybe you can just lower the json:
COUNT(JSON_VALUE (lower(SRQuestions.value), '$.id')) AS Question_Specific_Id

Old question but I came across this when googling a similar issue so I will chip in with my solution:
SELECT #pb = PB from
OPENJSON(#PropertyBagsAsJson, '$."$values"')
WITH (
PbId1 nvarchar(MAX) 'lax $.Id',
PbId2 nvarchar(MAX) 'lax $.id',
PB nvarchar(MAX) '$' AS JSON
)
WHERE COALESCE(PbId1,PbId2) = #PropertyBagId
I hope that the example is clear. Basically I just add all possible casing of the property and then just use Coalesce to filter the results.

You can use openjson. Instead of
JSON_VALUE (SRQuestions.value, '$.id')
you can write
(select Value
from openjson( SRQuestions.value )
where [Key] collate latin1_general_ci = 'id')
You must use a Case-Insensitive "_ci" collation here. "UTF8_General_CI" works too, as does "database_default" if the database uses a CI collation.

Related

SQL append in middle of column data in update query

When we do data refresh we need to append \\prod01\\Test\Load server name with \\prod01.qa.com\\Test\Load
How do I write update query for this. There can be different server names all I need to do is write update script to append server name with qa.com
This is my query that gives all results that have server location.
select * from AppSetting where Value like '%\\%\%' or Value like '%//%/%';
My Prod data looks like this
Value
\\prod01\Images\Load
\\prod01prod6253\Images\Load
\\server05ser\Images\Delete
\\pgdg1076\Email
\\pgdg1076ythg\Test\Load
http://prod7/delta/
My QA data should looks like this after update query
Value
\\prod01.qa.com\Images\Load
\\prod01prod6253.qa.com\Images\Load
\\server05ser.qa.com\Images\Delete
\\pgdg1076.qa.com\Email
\\pgdg1076ythg.qa.com\Test\Load
http://prod7.qa.com/delta/
This is the update query I have. Can I write a generic query
UPDATE eroom.AppSetting
SET Location = REPLACE(Location, '\\prod01\', '\\prod01.qa.tbc.com\')
WHERE Location like '%\\prod01\%';
UPDATE eroom.AppSetting
SET Location = REPLACE(Location, '\\server05ser\', '\\server05ser.qa.tbc.com\')
WHERE Location like '%\\server05ser\%';
I'm posting this as a new answer, as the OP moved the goal posts quite a bit. Instead, I now use CHARINDEX to find the location of each slash (forward or back). As fortunately the injection needs to happen before the 3rd slash, we can use that to our advantage:
SELECT STUFF(V.Value,CI3.I,0,'.qa.tbc.com') AS NewValue,*
FROM (VALUES('\\prod01\Images\Load'),
('\\prod01prod6253\Images\Load'),
('\\server05ser\Images\Delete'),
('\\pgdg1076\Email'),
('\\pgdg1076ythg\Test\Load'),
('http://prod7/delta/'))V([value])
CROSS APPLY (VALUES(CASE WHEN V.[value] LIKE '%/%' THEN '/' ELSE '\' END)) L(C) --So I don't have to keep checking what character I need
CROSS APPLY (VALUES(CHARINDEX(L.C,V.[value]))) CI1(I)
CROSS APPLY (VALUES(CHARINDEX(L.C,V.[value],CI1.I+1))) CI2(I)
CROSS APPLY (VALUES(CHARINDEX(L.C,V.[value],CI2.I+1))) CI3(I);
This is one method. I put the expressions for the PATINDEX and CHARINDEX in the FROM, as I find it far easier to read, and means less repetition:
SELECT V.[value],
ISNULL(STUFF(V.Value,ISNULL(CI.fs,CI.bs),0,'.qa.tbc.com'),V.[value]) AS NewValue
FROM (VALUES('\\prod01\Images\Load'),
('\\prod05\Images\Delete'),
('\\prod10\Email'),
('//http://prod7/delta/'))V([value])
CROSS APPLY (VALUES(NULLIF(PATINDEX('%prod[0-9]%',V.value),0)))PI(I)
CROSS APPLY (VALUES(NULLIF(CHARINDEX('/',V.[value],PI.I),0),NULLIF(CHARINDEX('\',V.[value],PI.I),0))) CI(fs,bs);
This answers the original version of the question.
The simplest way might be with stuff() and a case expression:
select (case when location like '\\prod[0-9][0-9]\*'
then stuff(location, 9, 0, '.qa.tbc.com'
else location
end)
The "prod" portion looks to be fixed length, so you don't need to search for a pattern.

How to extract a part of a JSON String from an SQL string Base64

Situation:
I have a column of encoded Base64 strings that I would like to decode and extract a specific part of it. It seems the decoded value is in JSON format (not familiar with JSON at all)
Objective:
How can I extract the value of a dictionary part of a JSON string.
Current query:
SELECT
CONVERT(VARCHAR(MAX),CAST('' AS XML).value('xs:base64Binary(sql:column("BASE64_COLUMN"))', 'VARBINARY(MAX)')) AS RESULT
FROM
(SELECT [value] AS BASE64_COLUMN FROM #test1) as testtable
Output:
The output looks like this:
RESULT
{"a":1,"b":2,"c":3,"d":4}
Desired Output:
How can i extract the value of the first dictionary: 1 ?
I feel like we don't have all the pieces here, but, using that JSON string you can get the value for the key 'a' by simply using OPENJSON:
DECLARE #JSON nvarchar(MAX) = N'{"a":1,"b":2,"c":3,"d":4}';
SELECT [value]
FROM OPENJSON(#JSON)
WHERE [key] = 'a';
I, of course, am assuming you are using SQL Server 2016. Otherwise SQL Server does not support JSON parsing, and you'll be better using an application to do so.
For 2014-, if you just want the first node's value then you could do
SELECT V.JSON, SUBSTRING([JSON],CHARINDEX(':',[JSON])+1,CHARINDEX(',',[JSON])-(CHARINDEX(':',[JSON])+1))
FROM (VALUES(#JSON))V([JSON]);
So, for a blind guess against your table (as I can't test):
SELECT SUBSTRING([JSONString],CHARINDEX(':',[JSONString])+1,CHARINDEX(',',[JSONString])-(CHARINDEX(':',[JSONString])+1))
FROM (SELECT [value] AS BASE64_COLUMN FROM #test1) as testtable
CROSS APPLY (VALUES(CONVERT(VARCHAR(MAX),CAST('' AS XML).value('xs:base64Binary(sql:column("BASE64_COLUMN"))', 'VARBINARY(MAX)')))V(JSONString);
This assumes that every string from your expression contains both at least 1 : character and , character. If it dosen't, then we need more (representative) sample data.
I don't know how permanent/clean this solution is (in fact it's quite ugly), and in most cases, you should reconsider using json-encoded strings for these values or parse it in an application as #Larnu suggests. In any case, you could use PATINDEX() and SUBSTRING() to get the first value:
SELECT
SUBSTRING (
CONVERT(VARCHAR(MAX),CAST('' AS XML).value('xs:base64Binary(sql:column("BASE64_COLUMN"))', 'VARBINARY(MAX)')),
6,
PATINDEX(
'%,"b":%',
CONVERT(VARCHAR(MAX),CAST('' AS XML).value('xs:base64Binary(sql:column("BASE64_COLUMN"))', 'VARBINARY(MAX)'))
) -6
)
FROM
(SELECT [value] AS BASE64_COLUMN FROM #test1) as testtable;

Using a GUID In The Where Clause

For some reason I'm unable to use comparisons on GUID columns, it does not return any results.
See below, with the WHERE clause set to the exact value of the 'secguid' column, it does not return any results. What's going on?
SELECT * FROM dbMobileFile
SELECT * FROM dbMobileFile WHERE secguid = '3137459D-EFDE-449E-94A3-89345A8580FA'
SELECT * FROM dbMobileFile WHERE secguid LIKE '3137459D-EFDE-449E-94A3-89345A8580FA'
Using LIKE does not work either.
Try this
SELECT [fileID],
[fileCOde],
[filePassword],
[fileDescription],
[rowguid],
[secguid]
FROM [dbo].[dbMobileFile]
WHERE CAST(secguid as uniqueidentifier) = CAST('3137459D-EFDE-449E-94A3-89345A8580FA' as uniqueidentifier)
Since you mention that the column is stored as NVARCHAR, its possible that the string has leading or trailing whitespaces, which is why it might not be popping up in the query with the WHERE clause.
You can try this :
SELECT [fileID],
[fileCOde],
[filePassword],
[fileDescription],
[rowguid],
[secguid]
FROM [dbo].[dbMobileFile]
WHERE LTRIM(RTRIM(secguid)) = '3137459D-EFDE-449E-94A3-89345A8580FA'
which should show you the result as leading and trailing whitespaces are eliminated in the WHERE clause.
Also, in case you want to make use of the LIKE operator, you can write your query as :
SELECT [fileID],
[fileCOde],
[filePassword],
[fileDescription],
[rowguid],
[secguid]
FROM [dbo].[dbMobileFile]
WHERE secguid LIKE '%3137459D-EFDE-449E-94A3-89345A8580FA%'
Hope this helps!!!
I've had this problem with a corrupt database. Some GUIDs be contained in the WHERE clause, with other GUIDS on the same table would not return results.
Turns out that database had Index issues. Run DBCC to make sure your database isn't corrupt.
The accepted answer works, but it is a bit verbose and probably not the intended way to do this. UniqueIdentifier is qualified by {}, so the following is the easiest;
SELECT * FROM dbMobileFile WHERE secguid = '{3137459D-EFDE-449E-94A3-89345A8580FA}'
See inside the database that guid value stored as 32 hex digits:00000000000000000000000000000000 so if we search by 32 hex digits separated by hyphens: 00000000-0000-0000-0000-000000000000, it's not get any output
Try this:
SELECT * FROM dbMobileFile WHERE secguid = ('3137459D-EFDE-449E-94A3-89345A8580FA')
Use parentheses to enclose GUID string LIKE ('GUID')

How to substring records with variable length

I have a table which has a column with doc locations, such as AA/BB/CC/EE
I am trying to get only one of these parts, lets say just the CC part (which has variable length). Until now I've tried as follows:
SELECT RIGHT(doclocation,CHARINDEX('/',REVERSE(doclocation),0)-1)
FROM Table
WHERE doclocation LIKE '%CC %'
But I'm not getting the expected result
Use PARSENAME function like this,
DECLARE #s VARCHAR(100) = 'AA/BB/CC/EE'
SELECT PARSENAME(replace(#s, '/', '.'), 2)
This is painful to do in SQL Server. One method is a series of string operations. I find this simplest using outer apply (unless I need subqueries for a different reason):
select *
from t outer apply
(select stuff(t.doclocation, 1, patindex('%/%/%', t.doclocation), '') as doclocation2) t2 outer apply
(select left(tt.doclocation2), charindex('/', tt.doclocation2) as cc
) t3;
The PARSENAME function is used to get the specified part of an object name, and should not used for this purpose, as it will only parse strings with max 4 objects (see SQL Server PARSENAME documentation at MSDN)
SQL Server 2016 has a new function STRING_SPLIT, but if you don't use SQL Server 2016 you have to fallback on the solutions described here: How do I split a string so I can access item x?
The question is not clear I guess. Can you please specify which value you need? If you need the values after CC, then you can do the CHARINDEX on "CC". Also the query does not seem correct as the string you provided is "AA/BB/CC/EE" which does not have a space between it, but in the query you are searching for space WHERE doclocation LIKE '%CC %'
SELECT SUBSTRING(doclocation,CHARINDEX('CC',doclocation)+2,LEN(doclocation))
FROM Table
WHERE doclocation LIKE '%CC %'

Regular expressions inside SQL Server

I have stored values in my database that look like 5XXXXXX, where X can be any digit. In other words, I need to match incoming SQL query strings like 5349878.
Does anyone have an idea how to do it?
I have different cases like XXXX7XX for example, so it has to be generic. I don't care about representing the pattern in a different way inside the SQL Server.
I'm working with c# in .NET.
You can write queries like this in SQL Server:
--each [0-9] matches a single digit, this would match 5xx
SELECT * FROM YourTable WHERE SomeField LIKE '5[0-9][0-9]'
stored value in DB is: 5XXXXXX [where x can be any digit]
You don't mention data types - if numeric, you'll likely have to use CAST/CONVERT to change the data type to [n]varchar.
Use:
WHERE CHARINDEX(column, '5') = 1
AND CHARINDEX(column, '.') = 0 --to stop decimals if needed
AND ISNUMERIC(column) = 1
References:
CHARINDEX
ISNUMERIC
i have also different cases like XXXX7XX for example, so it has to be generic.
Use:
WHERE PATINDEX('%7%', column) = 5
AND CHARINDEX(column, '.') = 0 --to stop decimals if needed
AND ISNUMERIC(column) = 1
References:
PATINDEX
Regex Support
SQL Server 2000+ supports regex, but the catch is you have to create the UDF function in CLR before you have the ability. There are numerous articles providing example code if you google them. Once you have that in place, you can use:
5\d{6} for your first example
\d{4}7\d{2} for your second example
For more info on regular expressions, I highly recommend this website.
Try this
select * from mytable
where p1 not like '%[^0-9]%' and substring(p1,1,1)='5'
Of course, you'll need to adjust the substring value, but the rest should work...
In order to match a digit, you can use [0-9].
So you could use 5[0-9][0-9][0-9][0-9][0-9][0-9] and [0-9][0-9][0-9][0-9]7[0-9][0-9][0-9]. I do this a lot for zip codes.
SQL Wildcards are enough for this purpose. Follow this link: http://www.w3schools.com/SQL/sql_wildcards.asp
you need to use a query like this:
select * from mytable where msisdn like '%7%'
or
select * from mytable where msisdn like '56655%'