i have got a couchbase n1ql update query:
UPDATE `store` document USE KEYS "test"
SET document = ARRAY_APPEND(ARRAY_REMOVE(document, "c"), "c")
this update will remove all occurences of the given "c" from the document and is then appending it to the end.
now i also want to limit the resulting array to a specific length, so the result never exeeds something like 50 array elements or so
since array slicing wont give a propper result, when the length of the array is less than the given slice, there is some more logic required.
i have tried the following:
UPDATE `store` document USE KEYS "test"
LET temp = ARRAY_APPEND(ARRAY_REMOVE(document, "c"), "c")
SET document = temp[-least(ARRAY_LENGTH(temp), 50):]
which fails, because "LET" is not part of the UPDATE syntax
is there any way to achieve limiting the stored array in size by using a single update statement?
EDIT:
with the selected answer and an additional case for new documents, i came up with this final solution:
MERGE INTO `store` document
USING (SELECT NULL) s ON KEY "test_x"
WHEN MATCHED THEN
UPDATE SET document = ARRAY_REVERSE(
ARRAY v FOR i:v IN ARRAY_REVERSE(
ARRAY_APPEND(ARRAY_REMOVE(document, "i"), "i")
) WHEN i < 3 END
)
WHEN NOT MATCHED THEN
INSERT ["i"]
RETURNING *
You can limit the length of an array using an indexed ARRAY operator, like this:
select array v for i:v in ["a", "b", "c", "d"] when i < 2 end
Result:
[
{
"$1": [
"a",
"b"
]
}
]
You can also use subquery expressions that are more flexible (like remove based on logical fields of objects) etc. If you need 50 elements give 49 because you are adding one more at the end.
UPDATE `store` AS document
USE KEYS "test"
SET document = ARRAY_APPEND((SELECT RAW d1 FROM document AS d1 WHERE d1 != "c" OFFSET ARRAY_LENGTH(document) - 49 LIMIT 49),"c");
https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/subqueries.html
Related
I am trying to create a simple filtering of records with bit operations by using this manual: https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html
I have four properties that are defined and based on certain content:
Filter 1 (field1) gets the value 1 (binary 1)
Filter 2 (field2) gets the value 2 (binary 10)
Filter 3 (field3) gets the value 4 (binary 100)
Filter 4 (field4) gets the value 8 (binary 1000)
I set the values with an update:
UPDATE table SET filter = 1 where field1 = "a";
UPDATE table SET filter = filter|2 where field2 = "b";
UPDATE table SET filter = filter|4 where field3 = "c";
UPDATE table SET filter = filter|8 where field4 = "d";
Now the column is filled for the different properties. I now have values between 0 (no property applies) and 15 (all properties apply).
How do I manage to use them now? If I want to use e.g. the filters 1,2 and 4, I get with:
select * from table where filter = 1|2|8;
I get the value "11". But actually, "15" should also match, since all four properties are applied here.
I had no success with this, too:
select * from table where filter & (1|2|8);
Can someone help me? Or am I completely wrong?
Try WHERE (filter & (1|2|8)) = (1|2|8).
But please be aware that this bitmasking approach can't exploit indexes, so it will scale up poorly to megarow tables.
This is my table:
CREATE TABLE orders(
id integer primary key,
order_uid text unique,
created_at text,
updated_at text,
created_by text,
updated_by text,
client text,
phone text,
device text,
items json,
comments text
)
'items' is a list of dictionaries - valid json.
This is what 'items' looks like:
[
{
"am_pm": "AM",
"brand_": "EEE",
"quantity": 8,
"code": "1-936331-67-5",
"delivery_date": "2020-04-19",
"supplier": "XXX",
"part_uid": "645039eb-82f4-4eed-b5f9-115b09679c66",
"name": "WWWWWW",
"price": 657,
"status": "Not delivered"
},
{
"am_pm": "AM",
"brand_": "DDDDDDD",
...
},
...
]
This is what I'm running (in 'execute sql' tab in sqlitebrowser V. 3.11.2, SQLite version 3.31.1), and it looks like it returns the desired results, however not reflected in the actual table, it doesn't update it:
select json_set(value, "$.am_pm", "Tequilla") from orders, json_each(orders.items, '$')
where orders.id=2 and json_extract(value, '$.part_uid') = '35f81391-392b-4d5d-94b4-a5639bba8591'
I also ran
update orders
set items = (select json_set(orders.items, '$.am_pm', "Tequilla") from orders, json_each(orders.items, '$'))
where orders.id=2
With the result being - it deleted the list of dicts and replaced it with a single dict, with the 'am_pm' field updated.
What is the correct sql statement, so I can update a single (or several) object/s in 'items'?
After much fiddling and posting on Sqlite forums as well, the optimal solution seems to be:
update orders
set items = (select json_set(items, fullkey||'.brand_', 'Teq')
from orders, json_each(items, '$')
where json_extract(value, '$.supplier') = 'XXX' and orders.id = 1)
where orders.id = 1
This will only update a single item in the json array, even if multiple items meet the criteria.
It would be helpful if someone more experienced in Sqlite could come up with a solution of updating multiple elements in a json array at once.
I have json column inside my PostgreSQL table that looks something similar to this:
{"example--4--":"test 1","another example--6--":"test 2","final example--e35b172a-af71-4207-91be-d1dc357fe8f3--Equipment":"ticked"}
{"example--4--":"test 4","another example--6--":"test 5","final example--e35b172a-af71-4207-91be-d1dc357fe8f3--Equipment":"ticked"}
Each key contains a map which is separated by --. The prefix is unique, ie: "example", "another example" and "final example".
I need to query on the unique prefix and so far, nothing I'm trying is even close.
select some_table.json_column from some_table
left join lateral (select array(select * from json_object_keys(some_table.json_column) as keys) k on true
where (select SPLIT_PART(k::text, '--', 1) as part_name) = 'example'
and some_table.json_column->>k = 'test 1'
The above is resulting in the following error (last line):
operator does not exist: json -> record
My expected output would be any records where "example--4--":"test 1" is present (in my above example, the only result would be)
{"example--4--":"test 1","another example--6--":"test 2","final example--e35b172a-af71-4207-91be-d1dc357fe8f3--Equipment":"ticked"}
Any help appreciated. After debugging around for a while, I can see the main issue resolves in the implicit cast to ::text. k seems to be a "record" of the keys that I need to loop and split to compare, currently, I'm casting a record to text which is causing the issue.
One way to do it, is to use an EXIST condition together with jsonb_each_text()
select *
from the_table
where exists (select *
from jsonb_each_text(data) as x(key,value)
where x.key like 'example%'
and x.value = 'test 1')
If your column isn't a jsonb (which it should be), you need to use json_each_text() instead
Another option is to use a JSON path expression:
select *
from the_table
where data #? '$.keyvalue() ? (#.key like_regex "^example" && #.value == "test 1")'
If I have a table with a column named json_stuff, and I have two rows with
{ "things": "stuff" } and { "more_things": "more_stuff" }
in their json_stuff column, what query can I make across the table to receive [ things, more_things ] as a result?
Use this:
select jsonb_object_keys(json_stuff) from table;
(Or just json_object_keys if you're using just json.)
The PostgreSQL json documentation is quite good. Take a look.
And as it is stated in the documentation, the function only gets the outer most keys. So if the data is a nested json structure, the function will not return any of the deeper keys.
WITH t(json_stuff) AS ( VALUES
('{"things": "stuff"}'::JSON),
('{"more_things": "more_stuff"}'::JSON)
)
SELECT array_agg(stuff.key) result
FROM t, json_each(t.json_stuff) stuff;
Here is the example if you want to get the key list of each object:
select array_agg(json_keys),id from (
select json_object_keys(json_stuff) as json_keys,id from table) a group by a.id
Here id is the identifier or unique value of each row. If the row cannot be distinguished by identifier, maybe it's better to try PL/pgSQL.
Here's a solution that implements the same semantics as MySQL's JSON_KEYS(), which...:
is NULL safe (i.e. when the array is empty, it produces [], not NULL, or an empty result set)
produces a JSON array, which is what I would have expected from how the question was phrased.
SELECT
o,
(
SELECT coalesce(json_agg(j), json_build_array())
FROM json_object_keys(o) AS j (j)
)
FROM (
VALUES ('{}'::json), ('{"a":1}'::json), ('{"a":1,"b":2}'::json)
) AS t (o)
Replace json by jsonb if needed.
Producing:
|o |coalesce |
|-------------|----------|
|{} |[] |
|{"a":1} |["a"] |
|{"a":1,"b":2}|["a", "b"]|
Insert json_column and table
select distinct(tableProps.props) from (
select jsonb_object_keys(<json_column>) as props from <table>
) as tableProps
I wanted to get the amount of keys from a JSONB structure, so I'm doing something like this:
select into cur some_jsonb from mytable where foo = 'bar';
select into keys array_length(array_agg(k), 1) from jsonb_object_keys(cur) as k;
I feel it is a little bit wrong, but it works. It's unfortunate that we can't get an array directly from the json_object_keys() function. That would save us some code.
The query:
$consulta = "UPDATE `list`
SET `pos` = $pos
WHERE `id_item` IN (SELECT id_item
FROM lists
WHERE pos = '$item'
ORDER BY pos DESC
LIMIT 1)
AND id_usuario = '$us'
AND id_list = '$id_pl'";
The thing is, this query is inside a foreach, and it wants to update the order of the items in a list. Before I had it like this:
$consulta = "UPDATE `list`
SET `pos` = $pos
WHERE `$pos` = '$item'
AND id_usuario = '$us'
AND id_list = '$id_pl'";
But when I update pos 2 -> 1, and then 1 -> 2, the result is two times 2 and no 1...
Is there a solution for this query?
Renumbering the items in a list is tricky. When you renumber the items in the list using multiple separate SQL statements, it is even trickier.
Your inner sub-select statement also is not properly constrained. You need an extra condition such as:
AND id_list = '$id_pl'
There are probably many ways to do this, but the one that may be simplest follows. I'm assuming that:
the unshown foreach loop generates $pos values in the desired sequence (1, 2, ...)
the value of $id_pl is constant for the loop
the foreach loop gives values for $us and $item for each iteration
the combination of $id_pl, $us, and $item uniquely identifies a row in the list table
there aren't more than 100 pos values to worry about
you are able to use an explicit transaction around the statement sequence
The suggested solution has two stages:
Allocate 100 + pos to each row to place it in its new position
Subtract 100 from each pos
This technique avoids any complicated issues about whether rows that have had there position adjusted are reread by the same query.
Inside the loop:
foreach ...
...$pos, $item, $us...
UPDATE list
SET pos = $pos + 100
WHERE id_item = '$item'
AND id_usuario = '$us'
AND id_list = '$id_pl'
AND pos < 100
end foreach
UPDATE list
SET pos = pos - 100
WHERE id__list = '$id_pl';
If you don't know the size of the lists, you could assign negative pos values in the loop and convert to positive after the loop, or any of a number of other equivalent mappings. The key is to update the table so that the new pos numbers in the loop are disjoint from the old numbers, and then adjust the new values after the loop.
Alternative techniques create a temporary table that maps the old numbers to the new and then executes a single UPDATE statement that changes the old pos value to the new for all rows in a single operation. This is probably more efficient, especially if the mapping table can be generated as a query, but that depends on whether the renumbering is algorithmic. The technique shown, albeit somewhat clumsy, can be made to work for arbitrary renumberings.