MySQL 5.7 - Increment a number in JSON - mysql

Is it possible to mass increment a specific JSON attribute?
For example, my JSON column is called metadata and looks like this:
{"counter": 0, ...}
Can I update multiple rows so that the counter increments by 1 with a single query?
Edit:
Since this is getting flagged for too broad, let me give an exact example.
Data on table x, the only column on it is metadata and it has 3 rows:
{"counter": 0}, {"counter": 1}, {"counter": 0, "something": "somethign"}
Desired result is one query that would update those 3 to this:
{"counter": 1}, {"counter": 2}, {"counter": 1, "something": "somethign"}
It goes without saying that I'm not an expert with MySQL and I didn't manage to get together a query that would work for me from the MySQL documentation. Help would be appreciated.

You could use JSON_EXTRACT along with JSON_SET:
UPDATE table SET col = JSON_SET(col, '$.counter', JSON_EXTRACT(col, '$.counter') + 1)

Related

Use GROUP_CONCAT query in Rails

I need the ids of a table but #pluck is not fast enough because there are too many records.
The thing is that I would like to get them in a string directly from mysql instead of get any Array or ActiveRecord::Relation
[1, 2, 3] => "1,2,3"
There is no group_concat in Rails, so I just asked via sql. Example:
sql = User.select("GROUP_CONCAT(users.id)").to_sql
ActiveRecord::Base.connection.exec_query(sql)
The thing is that I don't know why but it does not return all the ids of the table, but just some of them.
Any idea why is not returning all of them or how can I achieve it in a different way?
Apparently the result is truncated to the maximum length that is given by the group_concat_max_len.
Maybe you could increase that value. Follow this answer to get more information:
https://stackoverflow.com/a/5545904/8195530
You could just call it like this
sql = "select GROUP_CONCAT(id) from users"
data = ActiveRecord::Base.connection.exec_query(sql)
# #<ActiveRecord::Result:0x0000560661a2b7d8 #columns=["ids"], #rows=[["41,40,38,42,39,43,45,44"]], #hash_rows=nil, #column_types={}>
then you can get the data as ids with
ids = data['ids'].split(',').map(&:to_i)
# [41, 40, 38, 42, 39, 43, 45, 44]

Constructing nested json arrays in T-SQL

Given the table:
C1 C2 C3
----------------
1 'v1' 1.1
2 'v2' 2.2
3 'v3' 3.3
Is there any "easy" way to return JSON in this format:
{
"columns": [ "C1", "C2", "C3" ],
"rows": [
[ 1, "v1", 1.1 ],
[ 2, "v2", 2.2 ],
[ 3, "v3", 3.3 ]
]
}
To generate an array with single values from a table there is a neat trick like this:
SELECT JSON_QUERY(REPLACE(REPLACE(
(
SELECT id
FROM table a
WHERE pk in (1,2)
FOR JSON PATH
), '{"id":',''),'}','')) 'ids'
Which generates
"ids": [1,2]
But to construct the nested array above the replacing gets really tedious, anyone know a good way to achieve this?
Well, you ask for an easy way but the following will not be easy :-)
The tricky part is to know which values need to be qouted and which can remain naked.
This needs generic type-analysis to find, which values are strings.
The only way I know to get on meta data (besides building dynamic sql using meta views like INFORMATIONSCHEMA.COLUMNS) is XML together with an AUTO-schema.
This XML is very near to your needs actually. There is a list of columns at the beginning, followed by a list of rows. But it is not JSON of course...
Try this out:
--This is a mockup table with the values you provided.
DECLARE #mockup TABLE(C1 INT,C2 VARCHAR(100),C3 DECIMAL(4,2));
INSERT INTO #mockup VALUES
(1,'v1',1.1)
,(2,'v2',2.2)
,(3,'v3',3.3);
--Now we create an XML out of this
DECLARE #xml XML =
(
SELECT *
FROM #mockup t
FOR XML RAW,XMLSCHEMA,TYPE
);
--Check the XML's content with SELECT #xml to see how it is looking internally
--Now the real query can start:
SELECT '{"columns":[' +
STUFF(#xml.query('declare namespace xsd="http://www.w3.org/2001/XMLSchema";
for $col in /xsd:schema/xsd:element//xsd:attribute
return
<x>,{concat("""",xs:string($col/#name),"""")}</x>
').value('.','nvarchar(max)'),1,1,'') +
'],"rows":[' +
STUFF(
(
SELECT
',[' + STUFF(b.query(' declare namespace xsd="http://www.w3.org/2001/XMLSchema";
for $attr in ./#*
return
<x>,{if(/xsd:schema/xsd:element//xsd:attribute[#name=local-name($attr)]//xsd:restriction/#base="sqltypes:varchar") then
concat("""",$attr,"""")
else
xs:string($attr)
}
</x>
').value('.','nvarchar(max)'),1,1,'') + ']'
FROM #xml.nodes('/*:row') B(b)
FOR XML PATH(''),TYPE
).value('.','nvarchar(max)'),1,1,'') +
']}';
The result
{"columns":["C1","C2","C3"],"rows":[[3,"v3",3.30],[1,"v1",1.10],[2,"v2",2.20]]}
Some explanation:
The first part will use XQuery to find all columns (xsd:attribute within XML-schema) and create the array of column names.
The second part will againt use XQuery in order to run through all rows and write their column values in a concatenated string. Each value can refer to its type within the schema. Whenever this type is sqltypes:varchar the value will be quoted. All other values remain naked.
This will not solve each and any case generically...
To be honest, this was more for my own curiosity :-) Wanted to find out, how one can solve this.
Quite probably the best answer is: Use another tool. SQL-Server is not the best choice here ;-)

Couchbase : How to maintain arrays without duplicate elements?

We have a Couchbase store which has the Customer data.
Each customer has exactly one document in this bucket.
Daily transactions will result in making updates to this customer data.
Sample document. Let's focus on the purchased_product_ids array.
{
"customer_id" : 1000
"purchased_product_ids" : [1, 2, 3, 4, 5 ]
# in reality this is a big array - hundreds of elements
...
... many other elements ...
...
}
Existing purchased_product_ids :
[1, 2, 3, 4, 5]
products purchased today :
[1, 2, 3, 6] // 6 is a new entry, others existing already
Expected result after the update:
[1, 2, 3, 4, 5, 6]
I am using Subdocument API to avoid large data transfer between server and clients.
Option1 "arrayAppend" :
customerBucket.mutateIn(customerKey)
.arrayAppend("purchased_product_ids", JsonObject for [1,2,3,6] )
.execute();
It results in duplicate elements.
"purchased_product_ids" : [1, 2, 3, 4, 5, 1, 2, 3, 6]
Option2 "arrayAddUnique" :
customerBucket.mutateIn(customerKey)
.arrayAddUnqiue("purchased_product_ids", 1 )
.arrayAddUnqiue("purchased_product_ids", 2 )
.arrayAddUnqiue("purchased_product_ids", 3 )
.arrayAddUnqiue("purchased_product_ids", 6 )
.execute();
It throws exception for most of the times,
because those elements already existing.
Is there any better way to do this update ?
You could use N1QL, and the ARRAY_APPEND() and ARRAY_DISTINCT() functions.
UPDATE customer USE KEYS "foo"
SET purchased_product_ids = ARRAY_DISTINCT(ARRAY_APPEND(purchased_product_ids, 9))
Presumably this would be a prepared statement and the key itself and the new value would be supplied as parameters.
Also, if you want to add multiple elements to the array at once, ARRAY_CONCAT() would be a better choice. More here:
https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/arrayfun.html
Do you need purchased_product_ids to be ordered? If not you can convert it to a map, e.g.
{
"customer_id" : 1000
"purchased_product_ids" : {1: {}, 3: {}, 5: {}, 2: {}, 4: {}}
}
and then write to that map with subdoc, knowing you won't be conflicting (assuming product IDs are unique):
customerBucket.mutateIn(customerKey)
.upsert("purchased_product_ids.1", JsonObject.create()) // already exists
.upsert("purchased_product_ids.6", JsonObject.create()) // new product
.execute();
which will result in:
{
"customer_id" : 1000
"purchased_product_ids" : {1: {}, 3: {}, 6: {}, 5: {}, 2: {}, 4: {}}
}
(I've used JsonObject.create() as a placeholder here in case you need to associate additional information for each customer-order paid, but you could equally just write null. If you do need purchased_product_ids to be ordered, you can write the timestamp of the order, e.g. 1: {date: <TIMESTAMP>}, and then order it in code when you fetch.)

Using N1QL with document keys

I'm fairly new to couchbase and have tried to find the answer to a particular query I'm trying to create with not much success so far.
I've debated between using a view or N1QL for this particular case and settled with N1QL but haven't managed to get it to work so maybe a view is better after all.
Basically I have the document key (Group_1) for the following document:
Group_1
{
"cbType": "group",
"ID": 1,
"Name": "Group Atlas 3",
"StoreList": [
2,
4,
6
]
}
I also have 'store' documents, their keys are listed in this document's storelist. (Store_2, Store_4, Store_6 and they have a storeID value that is 2, 4 and 6) I basically want to obtain all 3 documents listed.
What I do have that works is I obtain this document with its id by doing:
var result = CouchbaseManager.Bucket.Get<dynamic>(couchbaseKey);
mygroup = JsonConvert.DeserializeObject<Group> (result.ToString());
I can then loop through it's storelist and obtain all it's stores in the same manner, but i don't need anything else from the group, all i want are the stores and would have prefered to do this in a single operation.
Does anyone know how to do a N1QL directly unto a specified document value?
Something like (and this is total imaginary non working code I'm just trying to clearly illustrate what I'm trying to get at):
SELECT * FROM mycouchbase WHERE documentkey IN
Group_1.StoreList
Thanks
UPDATE:
So Nic's solution does not work;
This is the closest I get to what I need atm:
SELECT b from DataBoard c USE KEYS ["Group_X"] UNNEST c.StoreList b;
"results":[{"b":2},{"b":4},{"b":6}]
Which returns the list of IDs of the Stores I want for any given group (Group_X) - I haven't found a way to get the full Stores instead of just the ID in the same statement yet.
Once I have, I'll post the full solution as well as all the speed bumps I've encountered in the process.
I apologize if I have a misunderstanding of your question, but I'm going to give it my best shot. If I misunderstood, please let me know and we'll work from there.
Let's use the following scenario:
group_1
{
"cbType": "group",
"ID": 1,
"Name": "Group Atlas 3",
"StoreList": [
2,
4,
6
]
}
store_2
{
"cbType": "store",
"ID": 2,
"name": "some store name"
}
store_4
{
"cbType": "store",
"ID": 4,
"name": "another store name"
}
store_6
{
"cbType": "store",
"ID": 6,
"name": "last store name"
}
Now lets say you wan't to query the stores from a particular group (group_1), but include no other information about the group. You essentially want to use N1QL's UNNEST and JOIN operators.
This might leave you with a query like so:
SELECT
stores.name
FROM `bucket-name-here` AS groups
UNNEST groups.StoreList AS groupstore
JOIN `bucket-name-here` AS stores ON KEYS ("store_" || groupstore.ID)
WHERE
META(groups).id = 'group_1';
A few assumptions are made in this. Both your documents exist in the same bucket and you only want to select from group_1. Of course you could use a LIKE and switch the group id to a percent wildcard.
Let me know if something doesn't make sense.
Best,
Try this query:
select Name
from buketname a join bucketname b ON KEYS a.StoreList
where Name="Group Atlas 3"
Based on your update, you can do the following:
SELECT b, s
FROM DataBoard c USE KEYS ["Group_X"]
UNNEST c.StoreList b
JOIN store_bucket s ON KEYS "Store_" || TO_STRING(b);
I have a similar requirement and I got what I needed with a query like this:
SELECT store
FROM `bucket-name-here` group
JOIN `bucket-name-here` store ON KEYS group.StoreList
WHERE group.cbType = 'group'
AND group.ID = 1

ArangoDB custom sort order

I would like to sort by a field in a specific order, lets say 2,4,1,5,3.
In MySQL I could use ORDER BY FIELD(id,2,4,1,5,3).
Is there anything equivalent for ArangoDB?
I think it should be possible to use the POSITION AQL function, which can return the position of an element inside an array
FOR i IN [ 1, 2, 3, 4, 5 ] /* what to iterate over */
SORT POSITION([ 2, 4, 1, 5, 3 ], i, true) /* order to be returned */
RETURN i
This will return:
[ 2, 4, 1, 5, 3 ]
Update: my original answer included the CONTAINS AQL function, however, it should be POSITION!
Unfortunately, there is no direct equivalent for that, at the moment.
However, there are ways to accomplish that by yourself.
1) By constructing an AQL query:
The query would run through your sort value array and query the DB for every defined value. Each of those results would then be added to the final output array.
Mind you, that this does have a performance penalty, because there is one query for every value. If you are defining only a few ones, I guess it will be tolerable, but if you have to define for example tens or hundreds, it will lead to n+1 queries (where n is the number of custom sorted values).
The "+1" is the last query, which should get the result of all the other values, which are not defined in your custom sort array and also append these to your output array.
That would look like the following snippet, which you can copy into your AQL Editor and run it.
Notes for the snippet:
I am first creating an array, which would represent the collection we
would query.
Then I am setting the defined sort values.
After that, the actual AQL statement does its job.
Also, note the FLATTEN function at the outer RETURN statement, which is required, because in the first loop we are getting result arrays for each defined sort value. These have all to be flatten down to the same level in order to be processed as a unique result set (instead of many encapsulated small ones).
/* Define a dummy collection-array to work with */
LET a = [
{
"_id": "a/384072353674",
"_key": "384072353674",
"_rev": "384073795466",
"sort": 2
},
{
"_id": "a/384075040650",
"_key": "384075040650",
"_rev": "384075827082",
"sort": 3
},
{
"_id": "a/384077137802",
"_key": "384077137802",
"_rev": "384078579594",
"sort": 4
},
{
"_id": "a/384067504010",
"_key": "384067504010",
"_rev": "384069732234",
"sort": 1
},
{
"_id": "a/384079497098",
"_key": "384079497098",
"_rev": "384081004426",
"sort": 5
}
]
/* Define the custom sort values */
LET cSort = [5,3,1]
/* Gather the results of each defined sort value query into definedSortResults */
LET definedSortResults = (
FOR u in cSort
LET d = (
FOR docs IN `a`
FILTER docs.`sort` == u
RETURN docs
)
RETURN d
)
/* Append the the result of the last (all the non-defined sort values) query to the results of the definedSortResults into the output array */
LET output = (
APPEND (definedSortResults, (
FOR docs IN `a`
FILTER docs.`sort` NOT IN cSort
RETURN docs
)
)
)
/* Finally FLATTEN and RETURN the output variable */
RETURN FLATTEN(output)
2) A different approach would be, to extend AQL with a function written in JavaScript, that would essentially do the same steps as above.
Of course, you could also open up a feature request on ArangoDB's GitHub Page, and maybe the nice folks at ArangoDB will consider it for inclusion. :)
Hope that helps