How to append data to a JSON object during a select? - sql

I'm in the progress of migrating data from an old system. I have some boolean columns, which can be True, False or Null. Each column has a certain meaning. In the new database, I use a JSON Data object. What I would like to achieve is this:
OldTable:
ValueA, ValueB, ValueC, ValueD
Null , True , False , True
I create now Json data using a select on my old table and FOR JSON to get the Json object. The object should look something like this:
{
myObject: {
data: "valueB, ValueD"
}
}
My problem is the appending the values to the same "data" field.
So right now I can only do it with single values like this:
SELECT CASE WHEN ValueA == True THEN 'ValueA' END 'myObject.data',
CASE WHEN ValueB == True THEN 'ValueB' END 'myObject.data'
FROM myLegacyTable FOR JSON Path
This would obviously overwrite, what ever is in myObject.data, whenever both values are true. JSON_MODIFY seems not to be an option, as I'm not working on an existing Json object, but creating a new one. Maybe someone else has an idea?

You can just build your json string with a CASE WHEN. If you want to handle a variable number of columns you'll probably need dynamic TSQL.
This is a simple static version that should do the job:
declare #tmp table(ValueA bit, ValueB bit, ValueC bit, ValueD bit)
insert into #tmp values (null, 1 ,0 , 1)
SELECT JSON_QUERY('{"data":"' +
stuff(
case when ValueA = 1 then ',ValueA' else '' end
+ case when ValueB = 1 then ',ValueB' else '' end
+ case when ValueC = 1 then ',ValueC' else '' end
+ case when ValueD = 1 then ',ValueD' else '' end
,1,1,'')
+ '"}') as myObject
FROM #tmp FOR JSON Path, without_array_wrapper
Result:

I don't know how your source data is really stored but here is an example of what you need to do if this was all in SQL Server
data.property is not seperate items so don't split it into columns. It's just a single concatenated string. So concatenate everything up and remove the trailing comma.
DECLARE #MyTable TABLE (
RowID INT,
ValueA VARCHAR(5),
ValueB VARCHAR(5),
ValueC VARCHAR(5),
ValueD VARCHAR(5)
)
INSERT INTO #MyTable (RowID,ValueA,ValueB,ValueC,ValueD) VALUES
(1,NULL,'True','False','True'),
(2,'False',NULL,'True','True'),
(3,NULL,'False',NULL,NULL)
SELECT
ISNULL(
RTRIM(REVERSE(STUFF(REVERSE
(
CASE WHEN ValueA='True' THEN 'ValueA, ' ELSE '' END +
CASE WHEN ValueB='True' THEN 'ValueB, ' ELSE '' END +
CASE WHEN ValueC='True' THEN 'ValueC, ' ELSE '' END +
CASE WHEN ValueD='True' THEN 'ValueD, ' ELSE '' END
), 2, 1, ''))),'') AS 'myObject.data'
FROM #MyTable
FOR JSON Path
The huge construct required to remove trailing , is from here:
Remove the last character in a string in T-SQL?
Result:
[
{
"myObject": {
"data": "ValueB, ValueD"
}
},
{
"myObject": {
"data": "ValueC, ValueD"
}
},
{
"myObject": {
"data": ""
}
}
]

Related

Query key values in a json column

I have a table "jobs" with one of the columns called "check_list" ( varchar(max) that has JSON values, an example value would be
{
"items":[
{
"name":"machine 1",
"state":"",
"comment":"",
"isReleaseToProductionCheck":true,
"mnachine_id":10
},
{
"name":"machine 2",
"state":"",
"comment":"",
"isReleaseToProductionCheck":true,
"machine_id":12
}
]
}
Now how would I write a SQL query to only return the rows where the column "check_list" has items[machine_id] = 12
In the end after some trial and error this was the solution that worked for me. I had to add the ISJSON check because some of the older data was invalid
WITH jobs (id, workorder, selectedMachine) AS(
SELECT
[id],
[workorder],
(
select
*
from
openjson(check_list, '$.items') with (machine_id int '$.machine_id')
where
machine_id = 12
) as selectedMachine
FROM
engineering_job_schedule
WHERE
ISJSON(check_list) > 0
)
Select
*
from
jobs
where
selectedMachine = 12

Float with Null case condition

In the below code, IN ELSE, what if I don't want to pass 'NOTEQUAL' and pass a NULL Value of Float
select from table as A {
A.objek,
max(case when A.atinn = '0000010530' then fltp_to_dec( A.atflv as abap.dec(5,3) else <what to enter here for null or no values > end ) as DDC
}
group by A.objek
If you need NULL then use NULL
select from table as A {
A.objek,
max(case when A.atinn = '0000010530'
then fltp_to_dec( A.atflv as abap.dec(5,3)
else NULL end ) as DDC
}
group by A.objek
or nothing as suggested by Larnu
select from table as A {
A.objek,
max(case when A.atinn = '0000010530'
then fltp_to_dec( A.atflv as abap.dec(5,3)
end ) as DDC
}
group by A.objek

If statements with SQL and returning string values

I'm using SQL and I need to return one string if the value of a field is 5 and another string if the value of the field is 4. I have something like this right now:
SELECT * FROM tablename WHERE value1=4 OR value1=5;
In PHP for example it might be like this (but I can't use PHP for my application):
if ($value1 == 4) {
$value1 = 'free';
} elseif ($value1 == 5) {
$value1 = 'not free';
} elseif...etc.
Anyone know how to accomplish what I want with SQL only?
Then you would do something like:
select (case when value1 = 4 then 'free' else 'not free' end) as newval
from tablename
where value1 in (4, 5);

Update multiple rows in same query using PostgreSQL

I'm looking to update multiple rows in PostgreSQL in one statement. Is there a way to do something like the following?
UPDATE table
SET
column_a = 1 where column_b = '123',
column_a = 2 where column_b = '345'
You can also use update ... from syntax and use a mapping table. If you want to update more than one column, it's much more generalizable:
update test as t set
column_a = c.column_a
from (values
('123', 1),
('345', 2)
) as c(column_b, column_a)
where c.column_b = t.column_b;
You can add as many columns as you like:
update test as t set
column_a = c.column_a,
column_c = c.column_c
from (values
('123', 1, '---'),
('345', 2, '+++')
) as c(column_b, column_a, column_c)
where c.column_b = t.column_b;
sql fiddle demo
Based on the solution of #Roman, you can set multiple values:
update users as u set -- postgres FTW
email = u2.email,
first_name = u2.first_name,
last_name = u2.last_name
from (values
(1, 'hollis#weimann.biz', 'Hollis', 'Connell'),
(2, 'robert#duncan.info', 'Robert', 'Duncan')
) as u2(id, email, first_name, last_name)
where u2.id = u.id;
Yes, you can:
UPDATE foobar SET column_a = CASE
WHEN column_b = '123' THEN 1
WHEN column_b = '345' THEN 2
END
WHERE column_b IN ('123','345')
And working proof: http://sqlfiddle.com/#!2/97c7ea/1
For updating multiple rows in a single query, you can try this
UPDATE table_name
SET
column_1 = CASE WHEN any_column = value and any_column = value THEN column_1_value end,
column_2 = CASE WHEN any_column = value and any_column = value THEN column_2_value end,
column_3 = CASE WHEN any_column = value and any_column = value THEN column_3_value end,
.
.
.
column_n = CASE WHEN any_column = value and any_column = value THEN column_n_value end
if you don't need additional condition then remove and part of this query
Let's say you have an array of IDs and equivalent array of statuses - here is an example how to do this with a static SQL (a sql query that doesn't change due to different values) of the arrays :
drop table if exists results_dummy;
create table results_dummy (id int, status text, created_at timestamp default now(), updated_at timestamp default now());
-- populate table with dummy rows
insert into results_dummy
(id, status)
select unnest(array[1,2,3,4,5]::int[]) as id, unnest(array['a','b','c','d','e']::text[]) as status;
select * from results_dummy;
-- THE update of multiple rows with/by different values
update results_dummy as rd
set status=new.status, updated_at=now()
from (select unnest(array[1,2,5]::int[]) as id,unnest(array['a`','b`','e`']::text[]) as status) as new
where rd.id=new.id;
select * from results_dummy;
-- in code using **IDs** as first bind variable and **statuses** as the second bind variable:
update results_dummy as rd
set status=new.status, updated_at=now()
from (select unnest(:1::int[]) as id,unnest(:2::text[]) as status) as new
where rd.id=new.id;
Came across similar scenario and the CASE expression was useful to me.
UPDATE reports SET is_default =
case
when report_id = 123 then true
when report_id != 123 then false
end
WHERE account_id = 321;
Reports - is a table here, account_id is same for the report_ids mentioned above. The above query will set 1 record (the one which matches the condition) to true and all the non-matching ones to false.
The answer provided by #zero323 works great on Postgre 12. In case, someone has multiple values for column_b (referred in OP's question)
UPDATE conupdate SET orientation_status = CASE
when id in (66934, 39) then 66
when id in (66938, 49) then 77
END
WHERE id IN (66934, 39, 66938, 49)
In the above query, id is analogous to column_b; orientation_status is analogous to column_a of the question.
In addition to other answers, comments and documentation, the datatype cast can be placed on usage. This allows an easier copypasting:
update test as t set
column_a = c.column_a::number
from (values
('123', 1),
('345', 2)
) as c(column_b, column_a)
where t.column_b = c.column_b::text;
#Roman thank you for the solution, for anyone using node, I made this utility method to pump out a query string to update n columns with n records.
Sadly it only handles n records with the same columns so the recordRows param is pretty strict.
const payload = {
rows: [
{
id: 1,
ext_id: 3
},
{
id: 2,
ext_id: 3
},
{
id: 3,
ext_id: 3
} ,
{
id: 4,
ext_id: 3
}
]
};
var result = updateMultiple('t', payload);
console.log(result);
/*
qstring returned is:
UPDATE t AS t SET id = c.id, ext_id = c.ext_id FROM (VALUES (1,3),(2,3),(3,3),(4,3)) AS c(id,ext_id) WHERE c.id = t.id
*/
function updateMultiple(table, recordRows){
var valueSets = new Array();
var cSet = new Set();
var columns = new Array();
for (const [key, value] of Object.entries(recordRows.rows)) {
var groupArray = new Array();
for ( const [key2, value2] of Object.entries(recordRows.rows[key])){
if(!cSet.has(key2)){
cSet.add(`${key2}`);
columns.push(key2);
}
groupArray.push(`${value2}`);
}
valueSets.push(`(${groupArray.toString()})`);
}
var valueSetsString = valueSets.join();
var setMappings = new String();
for(var i = 0; i < columns.length; i++){
var fieldSet = columns[i];
setMappings += `${fieldSet} = c.${fieldSet}`;
if(i < columns.length -1){
setMappings += ', ';
}
}
var qstring = `UPDATE ${table} AS t SET ${setMappings} FROM (VALUES ${valueSetsString}) AS c(${columns}) WHERE c.id = t.id`;
return qstring;
}
I don't think the accepted answer is entirely correct. It is order dependent. Here is an example that will not work correctly with an approach from the answer.
create table xxx (
id varchar(64),
is_enabled boolean
);
insert into xxx (id, is_enabled) values ('1',true);
insert into xxx (id, is_enabled) values ('2',true);
insert into xxx (id, is_enabled) values ('3',true);
UPDATE public.xxx AS pns
SET is_enabled = u.is_enabled
FROM (
VALUES
(
'3',
false
,
'1',
true
,
'2',
false
)
) AS u(id, is_enabled)
WHERE u.id = pns.id;
select * from xxx;
So the question still stands, is there a way to do it in an order independent way?
---- after trying a few things this seems to be order independent
UPDATE public.xxx AS pns
SET is_enabled = u.is_enabled
FROM (
SELECT '3' as id, false as is_enabled UNION
SELECT '1' as id, true as is_enabled UNION
SELECT '2' as id, false as is_enabled
) as u
WHERE u.id = pns.id;

Output a comma separated list in T-SQL

I have a table with phone numbers in it. Instead of spitting out a single row for each number I want to return a comma separated list of phone numbers. What's the easiest way to do this in sql? A while loop?
Some of those answers are overly complicated with coalesce and more complex XML queries. I use this all the time:
select #Phones=(
Select PhoneColumn+','
From TableName
For XML Path(''))
-- Remove trailing comma if necessary
select #Phones=left(#Phones,len(#Phones)-1)
You could create a UDF that would do something like this
CREATE FUNCTION dbo.GetBirthdays(#UserId INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #combined VARCHAR(MAX)
SELECT #combined = COALESCE(#combined + ', ' + colName + ', colName)
FROM YourTable
WHERE UserId = #UserId
ORDER BY ColName
END
Basically this just pulls all of the values into a simple list.
FWIW I created a SQL CLR Aggregate function. Works like a champ!
[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined,
Name = "JoinStrings",
IsInvariantToNulls=true,
IsInvariantToDuplicates=false,
IsInvariantToOrder=false,
MaxByteSize=8000)] public struct
JoinStrings : IBinarySerialize {
public string Result;
public void Init()
{
Result = "";
}
public void Accumulate(SqlString value)
{
if (value.IsNull)
return;
Result += value.Value + ",";
}
public void Merge(JoinStrings Group)
{
Result += Group.Result;
}
public SqlString Terminate()
{
return new SqlString(Result.ToString().Trim(new
char[] { ',' }));
}
public void Read(System.IO.BinaryReader r)
{
Result = r.ReadString();
}
public void Write(System.IO.BinaryWriter w)
{
w.Write(Result.ToString());
} }
I can then use it like this:
SELECT dbo.JoinStrings(Phone) FROM Phones Where UserID = XXX
See my answer from this question. There are a couple of other ways to do it listed in that question also. COALESCE or for xml path should do the trick though.
Edit (added my answer from the previous question):
CREATE FUNCTION [dbo].[fn_MyFunction]()RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #str NVARCHAR(MAX)
DECLARE #Delimiter CHAR(2)
SET #Delimiter = ', '
SELECT #str = COALESCE(#str + #Delimiter,'') + AColumn
FROM dbo.myTable
RETURN RTRIM(LTRIM(#str))
END
Assuming you have a Customers table which has a unique ID and another table named PhoneNumbers with multiple phone numbers for each customer sharing the Customer ID field as a Foreign Key this would work using a correlated sub-Query
Select C.ID, C.FirstName, C.LastName,
(select (STUFF(( SELECT ', ' + PhoneNumber from PhoneNumbers P where P.CID = C.ID
FOR XML PATH('')), 1, 2, ''))) as PhoneNumbers
from Customers C
Select Unique ID, Replace(Rtrim(Ltrim(Case when [Phone_Number1] is not null Then [Phone_Number1]+' ' Else '' End +
Case when [Phone_Number2] is not null Then [Phone_Number2]+' ' Else '' End +
Case when [Phone_Number3] is not null Then [Phone_Number3]+' ' Else '' End)),' ',', ') as Phone_numbers
From MYTable
Hope this is what you are looking for and I dont know if this will help you so far after the question.