Retrieve json value into columns when values are not named - json

I have a column named Username (varchar max, allows nulls) in a table. The table may have multiple entries that are all stored in this column. It is currently displayed in this format:
["name1","name2"]
I am trying to pull out name1 and name2 into separate rows. I cannot figure out how to do so. Here is the query that I currently have that is closest:
select u.id, a.[Username]
from dbo.Users u
CROSS APPLY OPENJSON(Username) WITH ([Username] varchar(max)) a
This is pulling them out in rows correctly but the rows display NULL instead of the value. I checked that the name matches the column name in syntax and it does.

The OPENJSON function is looking for a column named 'Username'. Instead, try giving OPENJSON 'oj' an alias and opening the JSON without specifying a schema. Then add oj.value to the SELECT list. Something like this
select u.id, oj.[value] as Username
from dbo.Users u
cross apply openjson(u.Username) oj;

The reason for the unexpected results is explained in the documentation: "...OPENJSON matches keys in jsonExpression with the column names in with_clause (in this case, matches keys implies that it is case sensitive). If a column name does not match a key name, you can provide an optional column_path...". Your input JSON is an array of scalar values and there isn't a key "username".
But, you can use OPENJSON() with explicit schema (the WITH clause). Just use the correct path ('$' in your case).
Table:
CREATE TABLE Users (id int, username varchar(max))
INSERT INTO Users (id, username) VALUES (1, '["name1","name2"]')
Statement:
SELECT u.id, a.[username]
FROM Users u
CROSS APPLY OPENJSON (u.[username]) WITH ([username] varchar(max) '$') a
Result:
id username
1 name1
1 name2

Related

Add alias to the column name where have postfix in column name in sql

Is there any possible way I can find and set the column name by giving alias
for example
I have a sql queries which contain 4 column name fields. 3 fields are common in all the queries
id, name, field
and there is another field which column name get change every time but the only common thing in that field it has a postfix as __type
so my sql query looks like this
SELECT * from table_name
id, name, field, system_data__value
is there any possible way I can add alias to the name where I found __type as type
so if I run my queries then it look like this
SELECT * from table_name
id, name, field, type
You may use UNION ALL for to set the aliases to the columns posessionally.
You must know some value which cannot present in some column (id = -1 in shown code) for fake row removing.
SELECT *
FROM (
SELECT -1 id, NULL name, NULL field, NULL alias_for_column_4
UNION ALL
SELECT * from table_name -- returns id, name, field, system_data__value
) subquery
WHERE id > 0 -- removes fake row
It is possible that the values in fake subquery needs in explicit CAST() calls for correct datatype of the output columns setting.

How to efficiently check a JSON array for values in T-SQL

I want to find multiple rows where a JSON array contains a specific value or values. Sometimes all match items will need to match (ANDs), sometimes only some (ORs) and sometimes a combination of both (ANDs and ORs).
This is in Microsoft SQL Server 2017.
I've tried doing an AS statement in the select but that resulted in the alias created for the subquery not being recognised later on in the subquery.
The bellow example works, it just seems innificent and has code duplication.
How would I only specify SELECT VALUE FROM OPENJSON(JsonData, '$.categories' once? Or perhaps there is some other way to do this?
DECLARE #TestTable TABLE
(
Id int,
JsonData nvarchar(4000)
);
INSERT INTO #TestTable
VALUES
(1,'{"categories":["one","two"]}'),
(2,'{"categories":["one"]}'),
(3,'{"categories":["two"]}'),
(4,'{"categories":["one","two","three"]}');
SELECT [Id]
FROM #TestTable
WHERE ISJSON(JsonData) = 1
-- These two lines are the offending parts of code
AND 'one' in (SELECT VALUE FROM OPENJSON(JsonData, '$.categories'))
AND 'two' in (SELECT VALUE FROM OPENJSON(JsonData, '$.categories'));
The table format cannot change, though I can add computed columns - if need be.
Well, I'm not sure if this helps you...
It might help to transform the nested array to a derived table to use it as a CTE. Check this out:
DECLARE #TestTable TABLE
(
Id int,
JsonData nvarchar(4000)
);
INSERT INTO #TestTable
VALUES
(1,'{"categories":["one","two"]}'),
(2,'{"categories":["one"]}'),
(3,'{"categories":["two"]}'),
(4,'{"categories":["one","two","three"]}');
--This is the query
WITH JsonAsTable AS
(
SELECT Id
,JsonData
,cat.*
FROM #TestTable tt
CROSS APPLY OPENJSON(tt.JsonData,'$.categories') cat
)
SELECT *
FROM JsonAsTable
The approach is very close to the query you formed yourself. The result is a table with one line per array entry. The forme Id is a repeated grouping key, the key is the ordinal position within the array, while the value is one of the words you are searching for.
In your query you can use JsonAsTable like you'd use any other table in this place.
But - instead of the repeated FROM OPENJSON queries - you will need repeated EXISTS() predicates...
A hacky solution might be this:
SELECT Id
,JsonData
,REPLACE(REPLACE(REPLACE(JsonData,'{"categories":[','",'),']}',',"'),'","',',')
FROM #TestTable
This will return all nested array values in one string, separated by a comma. You can query this using a LIKE pattern... You could return this as computed column though...

Call to_json on multiple columns using Postgres

Say I have the following table schema in Postgres:
CREATE TABLE users (id text, email text, phone_number text);
And I, for whatever reason, want to select the email and the phone number as JSON:
SELECT to_json(users.email, users.phone_number) AS user FROM users WHERE id=usr_123;
I get an error that looks like this:
function to_json(text, text) does not exist
No function matches the given name and argument types. You might need to add explicit type casts.
But this works just fine:
SELECT to_json(users.*) AS user FROM users WHERE id=usr_123;
How can I select just a few columns (not all of them) using a to_json call in Postgres?
The fine manual says that to_json's signature is:
to_json(anyelement)
so you're supposed say to_json(one_single_value). When you say:
to_json(users.email, users.phone_number)
you're trying to call to_json with two values and there is no such to_json function. When you say:
to_json(user.*)
you're actually calling to_json with a single ROW argument so it works just fine.
You can use a derived table or CTE as klin suggests or you can build the ROWs by hand:
select to_json(row(users.email, users.phone_number)) ...
The problem with this is that that ROW won't have any column names so your JSON will use useless keys like "f1" and "f2". To get around that you need to cast the ROW to something that does have names. One way to get some names is to create a custom type:
create type email_and_phone_number as (
email text,
phone_number text
)
and then cast your ROW to that type:
select to_json(row(users.email, users.phone_number)::email_and_phone_number) ...
You could also use a temporary table instead of a custom type:
create temporary table email_and_phone_number (
email text,
phone_number text
)
and then use the same cast as with a custom type.
If you're building this specific JSON format a lot then a custom type would make sense. If this is a one-off then a temporary table would make sense, the temporary table will automatically disappear when the session ends so there's nothing to clean up. Of course, a derived table or CTE might also make sense depending on the query and what tools you're using to interface with your database.
Use a subquery, e.g.:
select to_json(sub)
from (
select email, phone_number
from users
where id = 'usr_123'
) sub;
or with query:
with cte as (
select email, phone_number
from users
where id = 'usr_123')
select to_json(cte)
from cte;
If you just want to remove one specific field from the result there is an elegant solution:
SELECT to_json(users)::jsonb - 'id' AS user FROM users WHERE id=usr_123;

MySQL select all from list where value not in table

I cannot create a virtual table for this. Basically what I have, is a list of values:
'Succinylcholine','Thiamine','Trandate','Tridol Drip'
I want to know which of those values is not present in table1 and display them. Is this possible? I have tried using left joins and creating a variable with the list which I can compare to the table, but it returns the wrong results.
This is one of the things I have tried:
SET #list="'Amiodarone','Ammonia Inhalents','Aspirin';
SELECT #list FROM table1 where #list not in (
SELECT Description
FROM table1
);
With only narrow exceptions, you need to have data in table form to be able to obtain those data in your result set. This is the essential problem that all attempts at a solution to this problem run into, given that you cannot create a temporary table. If indeed you can provide the input in any form or format (per your comment), then you can provide it in the form of a subquery:
(
SELECT 'Amiodarone' AS description
UNION ALL
SELECT 'Ammonia Inhalents'
UNION ALL
SELECT 'Aspirin'
)
(Note that that exercises the biggest of the exceptions I noted: you can select scalars directly, without a base table. If you like, you can express that explicitly -- in MySQL and Oracle, at least -- by selecting FROM DUAL.)
In that case, this should work for you:
SELECT
a.description
FROM
(
SELECT 'Amiodarone' AS description
UNION ALL
SELECT 'Ammonia Inhalents'
UNION ALL
SELECT 'Aspirin'
) a
LEFT JOIN table1
ON a.description = table1.description
WHERE table1.description IS NULL
That won't work. the variable's contents will be treated as a monolithic string - one solid block of letters, not 3 separate comma-separated values. The query will be parsed/executed as:
SELECT ... WHERE "'Amio.....rin'" IN (x,y,z,...)
^--------------^--- string
Plus, since you're just doing a sub-select on the very same table, there's no point in this kind of a construct. You could try mysql find_in_set() function:
SELECT #list
FROM table1
WHERE FIND_IN_SET(Description, #list) <> ''

I need some help getting MySql to output some results using a subquery

I'm storing a list of numbers inside a table as a varchar(255) and want to use this list in another query's "IN() clause.
Here's what I mean:
Table Data:
CREATE TABLE IF NOT EXISTS `session_data` (
`visible_portf_ids` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `session_data` (`visible_portf_ids`) VALUES
('45,44,658,659,661,45,44,658,659,661')
I want to run a query like this to return a list of portfolio's "QUERY #1":
SELECT portfolio_hierarchy_id, account_id, name, leaf_node_portf_id
FROM portfolio_hierarchy
WHERE account_id = 1
AND leaf_node_portf_id IN
(
(SELECT visible_portf_ids
FROM session_data
WHERE username = 'ronedog')
)
ORDER BY name ASC
The result of the query above returns only 1 row, when there are a total of 3 that should have been returned.
If I run the subquery alone like this:
(SELECT visible_portf_ids
FROM session_data
WHERE username = 'ronedog')
it will return a list like this:
45,44,658,659,661,45,44,658,659,661
But, when I run Query #1 above, only one row of data, which is associated with the "visible_portf_ids" of "45" is returned.
If I replace the subquery with hard coded values like this:
SELECT portfolio_hierarchy_id, account_id, name, leaf_node_portf_id
FROM portfolio_hierarchy
WHERE account_id = 1
AND leaf_node_portf_id IN (45,44,658,659,661,45,44,658,659,661)
ORDER BY name ASC
then I get all 3 rows I'm expecting.
I'm guessing that MySql is returning the list as a string because its stored as a varchar() and so it stops processing after the first "visible_portf_ids" is found, which is "45", but I'm not really sure.
Anyone got any ideas how I can fix this?
Thanks in advance.
You should think about restructuring your tables storing each value in a new row, instead of concatenating them.
Until then, you can use the FIND_IN_SET() function:
AND FIND_IN_SET(leaf_node_portf_id,
(SELECT visible_portf_ids
FROM session_data
WHERE username = 'ronedog'
LIMIT 1)
) > 0
Unfortunately MySQL does not have a function to split a delimited string. Your IN argument is a single string with the result of your subquery. The reason it works when you hard-code it is that MySQL is parsing the values.
I suggest that you redesign your data base to store the visible ports list as separate rows in a separate table. Then you can retrieve them and use them in subqueries like you tried.