How to search SQL column containing JSON array - sql

I have a SQL column that has a single JSON array:
{"names":["Joe","Fred","Sue"]}
Given a search string, how can I use SQL to search for a match in the names array? I am using SQL 2016 and have looked at JSON_QUERY, but don't know how to search for a match on a JSON array. Something like below would be nice.
SELECT *
FROM table
WHERE JSON_QUERY(column, '$.names') = 'Joe'

For doing a search in a JSON array, one needs to use OPENJSON
DECLARE #table TABLE (Col NVARCHAR(MAX))
INSERT INTO #table VALUES ('{"names":["Joe","Fred","Sue"]}')
SELECT * FROM #table
WHERE 'Joe' IN ( SELECT value FROM OPENJSON(Col,'$.names'))
or as an alternative, one can use it with CROSS APPLY.
SELECT * FROM
#table
CROSS APPLY OPENJSON(Col,'$.names')
WHERE value ='Joe'

Postgres syntax
When you know the key that holds the data:
SELECT column_name from table_name where column_name->>'key' LIKE '%QUERYSTRING%';
When you don't know the key that holds the data:
SELECT column_name from table_name where column_name::text LIKE '%QUERYSTRING%';

It's very simple , can be easily done using JSON_CONTAINS() function.
SELECT * FROM table
where JSON_CONTAINS(column, 'joe','$.name');

You can search for a match on a JSON array via below query:
SELECT JSON_EXTRACT(COLUMN, "$.names") AS NAME
FROM TABLE JSON_EXTRACT(COLUMN, "$.names") != ""
Replace the TABLE with your table name and COLUMN with the column name in the table.
the key I have mentioned name as it was there in your question.

Just want to add to the existing answers a simple solution how you can check if array inside json contains a value:
DECLARE #Json NVARCHAR(max) = '{"names":["Joe","Fred","Sue"]}'
IF EXISTS (SELECT value FROM OPENJSON(#Json,'$.names') WHERE value = 'Joe')
PRINT 'Array contains "Joe"'
ELSE
PRINT 'Array does not contain "Joe"'

Related

JSONB array contains like OR and AND operators

Consider a table temp (jsondata jsonb)
Postgres provides a way to query jsonb array object for contains check using
SELECT jsondata
FROM temp
WHERE (jsondata->'properties'->'home') ? 'football'
But, we can't use LIKE operator for array contains. One way to get LIKE in the array contains is using -
SELECT jsondata
FROM temp,jsonb_array_elements_text(temp.jsondata->'properties'->'home')
WHERE value like '%foot%'
OR operation with LIKE can be achieved by using -
SELECT DISTINCT jsondata
FROM temp,jsonb_array_elements_text(temp.jsondata->'properties'->'home')
WHERE value like '%foot%' OR value like 'stad%'
But, I am unable to perform AND operation with LIKE operator in JSONB array contains.
After unnesting the array with jsonb_array_elements() you can check values meeting one of the conditions and sum them in groups by original rows, example:
drop table if exists temp;
create table temp(id serial primary key, jsondata jsonb);
insert into temp (jsondata) values
('{"properties":{"home":["football","stadium","16"]}}'),
('{"properties":{"home":["football","player","16"]}}'),
('{"properties":{"home":["soccer","stadium","16"]}}');
select jsondata
from temp
cross join jsonb_array_elements_text(temp.jsondata->'properties'->'home')
group by jsondata
-- or better:
-- group by id
having sum((value like '%foot%' or value like 'stad%')::int) = 2
jsondata
---------------------------------------------------------
{"properties": {"home": ["football", "stadium", "16"]}}
(1 row)
Update. The above query may be expensive with a large dataset. There is a simplified but faster solution. You can cast the array to text and apply like to it, e.g.:
select jsondata
from temp
where jsondata->'properties'->>'home' like all('{%foot%, %stad%}');
jsondata
---------------------------------------------------------
{"properties": {"home": ["football", "stadium", "16"]}}
(1 row)
I have the following, but it was a bit fiddly. There's probably a better way but this is working I think.
The idea is to find the matching JSON array entries, then collect the results. In the join condition we check the "matches" array has the expected number of entries.
CREATE TABLE temp (jsondata jsonb);
INSERT INTO temp VALUES ('{"properties":{"home":["football","stadium",16]}}');
SELECT jsondata FROM temp t
INNER JOIN LATERAL (
SELECT array_agg(value) AS matches
FROM jsonb_array_elements_text(t.jsondata->'properties'->'home')
WHERE value LIKE '%foo%' OR value LIKE '%sta%'
LIMIT 1
) l ON array_length(matches, 1) = 2;
jsondata
-------------------------------------------------------
{"properties": {"home": ["football", "stadium", 16]}}
(1 row)
demo: db<>fiddle
I would cast the array into text. Then you are able to search for keywords with every string operator.
Disadvantage: because it was an array the text contains characters like braces and commas. So it's not that simple to search for keyword with a certain beginning (ABC%): You always have to search like %ABC%
SELECT jsondata
FROM (
SELECT
jsondata,
jsondata->'properties'->>'home' as a
FROM
temp
)s
WHERE
a LIKE '%stad%' AND a LIKE '%foot%'

I am looking to update an integer value of one column based on partial data contained in a VARCHAR Column

Column A (VAR CHAR) contains a UNC Path eg.
lab03-app01\66\2016\10\3\LAB03-REC01\4989\1_6337230127359919046_6337230127366210371.wav
Within the UNC Path is an index number 4989.
I need to be able to update Column B (INT) to be equal to the value of Index Number located in Column A.
Is this possible?
This would be much easier with CLR and regular expressions.
One way of doing what you need in TSQL is below (demo).
DECLARE #T TABLE (A VARCHAR(255), B INT NULL);
INSERT INTO #T(A) VALUES ('lab03-app01\66\2016\10\3\LAB03-REC01\4989\1_6337230127359919046_6337230127366210371.wav')
UPDATE T
SET B = CAST(REVERSE(SUBSTRING(ReverseA, FinalSlash, PenultimateSlash - FinalSlash)) AS INT)
FROM #T T
CROSS APPLY (SELECT REVERSE(A)) ca1(ReverseA)
CROSS APPLY (SELECT 1 + CHARINDEX('\', ReverseA)) ca2(FinalSlash)
CROSS APPLY (SELECT CHARINDEX('\', ReverseA, FinalSlash)) ca3(PenultimateSlash);
SELECT *
FROM #T;
I think charindex() does what you want:
select charindex('4989', a)
To be safe, you might want to include the delimiters:
select charindex('\4989\', a)

compare some lists in where condition sql

I have some question in Sqlserver2012. I have a table that contains a filed that save who System Used from this information and separated by ',', I want to set into parameter the name of Systems and query the related rows:
declare #System nvarchar(50)
set #System ='BPM,SEM'
SELECT *
FROM dbo.tblMeasureCatalog t1
where ( ( select Upper(value) from dbo.split(t1.System,','))
= any( select Upper(value) from dbo.split(#System,',')))
dbo.split is a function to return systems in separated rows
Forgetting for a second that storing delimited lists in a relational database is abhorrent, you can do it using a combination of INTERSECT and EXISTS, for example:
DECLARE #System NVARCHAR(50) = 'BPM,SEM';
DECLARE #tblMeasureCatalog TABLE (System VARCHAR(MAX));
INSERT #tblMeasureCatalog VALUES ('BPM,XXX'), ('BPM,SEM'), ('XXX,SEM'), ('XXX,YYY');
SELECT mc.System
FROM #tblMeasureCatalog AS mc
WHERE EXISTS
( SELECT Value
FROM dbo.Split(mc.System, ',')
INTERSECT
SELECT Value
FROM dbo.Split(#System, ',')
);
Returns
System
---------
BPM,XXX
BPM,SEM
XXX,SEM
EDIT
Based on your question stating "Any" I assumed that you wanted rows where the terms matched any of those provided, based on your comment I now assume you want records where the terms match all. This is a fairly similar approach but you need to use NOT EXISTS and EXCEPT instead:
Now all is still quite ambiguous, for example if you search for "BMP,SEM" should it return a record that is "BPM,SEM,YYY", it does contain all of the searched terms, but it does contain additional terms too. So the approach you need depends on your requirements:
DECLARE #System NVARCHAR(50) = 'BPM,SEM,XXX';
DECLARE #tblMeasureCatalog TABLE (System VARCHAR(MAX));
INSERT #tblMeasureCatalog
VALUES
('BPM,XXX'), ('BPM,SEM'), ('XXX,SEM'), ('XXX,YYY'),
('SEM,BPM'), ('SEM,BPM,XXX'), ('SEM,BPM,XXX,YYY');
-- METHOD 1 - CONTAINS ALL SEARCHED TERMS BUT CAN CONTAIN ADDITIONAL TERMS
SELECT mc.System
FROM #tblMeasureCatalog AS mc
WHERE NOT EXISTS
(
SELECT Value
FROM dbo.Split(#System, ',')
EXCEPT
SELECT Value
FROM dbo.Split(mc.System, ',')
);
-- METHOD 2 - ONLY CONTAINS ITEMS WITHIN THE SEARCHED TERMS, BUT NOT
-- NECESSARILY ALL OF THEM
SELECT mc.System
FROM #tblMeasureCatalog AS mc
WHERE NOT EXISTS
( SELECT Value
FROM dbo.Split(mc.System, ',')
EXCEPT
SELECT Value
FROM dbo.Split(#System, ',')
);
-- METHOD 3 - CONTAINS ALL ITEMS IN THE SEARCHED TERMS, AND NO ADDITIONAL ITEMS
SELECT mc.System
FROM #tblMeasureCatalog AS mc
WHERE NOT EXISTS
( SELECT Value
FROM dbo.Split(#System, ',')
EXCEPT
SELECT Value
FROM dbo.Split(mc.System, ',')
)
AND LEN(mc.System) = LEN(#System);
You have a problem with your data structure because you are storing lists of things in a comma-delimited list. SQL has a great data structure for storing lists. It goes by the name "table". You should have a junction table with one row per "measure catalog" and "system".
Sometimes, you are stuck with other people's really bad design decisions. One solution is to use split(). Here is one method:
select mc.*
from dbo.tblMeasureCatalog mc
where exists (select 1
from dbo.split(t1.System, ',') t1s join
dbo.split(#System, ',') ss
on upper(t1s.value) = upper(ss.value)
);
you can try this :
declare #System nvarchar(50)
set #System ='BPM,SEM'
SELECT * from dbo.tblMeasureCatalog t1 inner join dbo.Split (#System ,',') B on t1.it=B.items

SQL pattern matching in search conditions

I have a table with a field name.
For a given name like ABC I want to get the records with that name and the records which have an L appended at the end.
So for ABC I want all records with name either ABC or ABCL.
I tried getting the records using the following code but it doesn't work.
SELECT * FROM tbl
WHERE name like "ABC[|L]"
I am using TSQL.
How can pattern match these names?
Use this SQL:
SELECT * FROM tbl WHERE name IN ('ABC', 'ABCL')
If you are using this SQL within a Stored Procedure / Function, something like following would work. Assuming, you are passing in the value for name in a #name variable.
SELECT * FROM tbl WHERE name IN (#name, #name + 'L')
Use the IN operator.
SELECT *
FROM tbl
WHERE name IN ('ABC', 'ABCL')
In case you will be using variables instead of literal strings, try this:
select *
from tbl
where name like (#YourName + #ExtraLetter)
or name = #YourName

How to select a column without its name in sql server?

how to
select name,family from student where name="X"
without its column name.
for example :
select "column1","column2" from student where "column1"="x"
or for example
select "1","2" from student where "1"="x"
"1" is column1
"2" is column2
i dont want to say columns name. i want to say just its number or other....
idont tired from select *. but it just for that i dont know the columns name but i know where they are. my columns name are change every i want to read the file but its data are same, and the data are in the same columns in each file.
Although you can not use field positions specifiers in the SELECT statement, the SQL standard includes the INFORMATION_SCHEMA where the dictionary of your tables is defined. This includes the COLUMNS view where all the fields of all tables are defined. And in this view, there is a field called ORDINAL_POSITION which you can use to assist in this problem.
If you query
SELECT ORDINAL_POSITION, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TABLE'
ORDER BY ORDINAL_POSITION
then you can obtain the column names for the ordinal positions you want. From there you can prepare a SQL statement.
You could use temp table as:
DECLARE #TB TABLE(Column1 NVARCHAR(50),...)
INSERT #TB
SELECT * FROM student
Then use it:
SELECT Column1 FROM #TB WHERE Column1='aa'
If it's a string you can do this :
Select Column1 + '' From Table
If it's a number you can do this :
Select Column1 + 0 From Table
If it's a datetime you can do this :
Select dateadd(d, 0, Column1) From Table
And similarly for other data types..
No, you can not use the ordinal (numeric) position in the SELECT clause. Only in Order by you can.
however you can make your own column alias...
Select Column1 as [1] From Table
You can use alias:
SELECT name AS [1], family AS [2] FROM student WHERE name="X"
It's just not possible. Unfortunately, they didn't think about table-valued functions, for which information_schema is not available, so good luck with that.