How to query from "any"/"map" data type on Tarantool? - sql

Following example from this answer. If I created map without index, how to query the inner value of the map?
box.schema.create_space('x', {format = {[1] = {'id', 'unsigned'}, [2] = {'obj', 'map'}}})
box.space.x:create_index('pk', {parts = {[1] = {field = 1, type = 'unsigned'}}})
box.space.x:insert({2, {text = 'second', timestamp = 123}}
box.execute [[ SELECT * FROM "x" ]]
-- [2, {'timestamp': 123, 'text': 'second'}]
How to fetch timestamp or text column directly from SQL without creating index?
Tried these but didn't work:
SELECT "obj.text" FROM "x"
SELECT "obj"."text" FROM "x"
SELECT "obj"["text"] FROM "x"
SELECT "obj"->"text" FROM "x"

You can register a Lua function to call it from SQL. The first example from our SQL + Lua manual shows exactly what you asked.
A bit simplified version of the example to explain the idea:
box.schema.func.create('GETFIELD', {
language = 'LUA',
returns = 'any',
body = [[
function(msgpack_value, field)
return require('msgpack').decode(msgpack_value)[field]
end]],
is_sandboxed = false,
param_list = {'string', 'string'},
exports = {'SQL'},
is_deterministic = true
})
After registration of the function you can call it from SQL:
tarantool> \set language sql
tarantool> select getfield("obj", 'text') from "x"
---
- metadata:
- name: COLUMN_1
type: any
rows:
- ['second']
...
tarantool> select getfield("obj", 'timestamp') from "x"
---
- metadata:
- name: COLUMN_1
type: any
rows:
- [123]
...
Differences from the example in the manual:
No hack with the global variable, but no dot syntax ('foo.bar.baz').
Exported only to SQL.
The return type is 'any': so it can be used for, say, the numeric 'timestamp' field. Downside: 'any' is reported in the result set metainformation.
(The idea suggested by Nikita Pettik, my teammate.)

Related

SQL query with json column

I have a table individual customer with a column employmentDetails as json field.
The task is to get a customer that has empty locality field
[{
"employmentStatus": "E",
"communicationInfo": {
"addresses": [
{
"id": "1",
"houseName": "1",
"locality": null
}
]
}}]
I tried several variants with casting etc with no success, please help me to understand how to query from json object fields. My attempts below that should return some values but returned nothing.
SELECT * FROM crm."IndividualCustomer" AS ic
WHERE (ic."employmentDetails" -> 'employmentStatus')::text = 'E';
SELECT * FROM crm."IndividualCustomer" AS ic
WHERE ic."employmentDetails" -> 'communicationInfo' -> 'adresses[0]' ->> 'locality' = null;
SELECT * FROM crm."IndividualCustomer" AS ic
WHERE ic."employmentDetails" -> 'communicationInfo' -> 'adresses' ->> 'locality' = null;
The task is to get a customer that has empty locality field
You can use a JSON path expression:
SELECT *
FROM crm."IndividualCustomer" AS ic
WHERE ic."employmentDetails" ## '$[*].communicationInfo.addresses[*].locality == null'
This requires Postgres 12 or later. If you have an older version, you can use the contains operator #>:
WHERE ic."employmentDetails" #> '[{"communicationInfo": {"addresses": [{"locality": null}]}}]'
This assumes that "employmentDetails" is defined as jsonb (which it should be). If it's not, you need to cast it: "employmentDetails"::jsonb.
The condition: (ic."employmentDetails" -> 'employmentStatus')::text = 'E' doesn't work, because -> returns a jsonb (or json) value which can't really be cast to a proper text value (the double quotes are kept if you do so).
You need to use the ->> operator which returns a text value directly: (ic."employmentDetails" ->> 'employmentStatus') = 'E'
However, the top level object is an array, so you would need to pick the e.g. the first array element:
(ic."employmentDetails" -> 0 ->> 'employmentStatus') = 'E'
Note the -> to return the first array element as a proper jsonb value, then the use of ->> on that value.
This can also be done using a JSON path expression to search through all array elements:
WHERE ic."employmentDetails" ## '$[*].employmentStatus == "E"'`

Querying case-insensitive columns by SQL in Tarantool

We know that string Tarantool indices can be made case-insensitive by specifying the collation option: collation = "unicode_ci". E.g.:
t = box.schema.create_space("test")
t:format({{name = "id", type = "number"}, {name = "col1", type = "string"}})
t:create_index('primary')
t:create_index("col1_idx", {parts = {{field = "col1", type = "string", collation = "unicode_ci"}}})
t:insert{1, "aaa"}
t:insert{2, "bbb"}
t:insert{3, "ccc"}
Now we can do a case-insensitive query:
tarantool> t.index.col1_idx:select("AAA")
---
- - [1, 'aaa']
...
But how to do it using SQL? This doesn't work:
tarantool> box.execute("select * from \"test\" where \"col1\" = 'AAA'")
---
- metadata:
- name: id
type: number
- name: col1
type: string
rows: []
...
Neither does this:
tarantool> box.execute("select * from \"test\" indexed by \"col1_idx\" where \"col1\" = 'AAA'")
---
- metadata:
- name: id
type: number
- name: col1
type: string
rows: []
...
There's a dirty trick with a poor performance (full scan). We don't want it, do we?
tarantool> box.execute("select * from \"test\" indexed by \"col1_idx\" where upper(\"col1\") = 'AAA'")
---
- metadata:
- name: id
type: number
- name: col1
type: string
rows:
- [1, 'aaa']
...
At last, we have one more workaround:
tarantool> box.execute("select * from \"test\" where \"col1\" = 'AAA' collate \"unicode_ci\"")
---
- metadata:
- name: id
type: number
- name: col1
type: string
rows:
- [1, 'aaa']
...
But the question is - does it use the index? Without an index it also works...
One can check query plan to figure out whether particular index is used or not. To get query plan simply add 'EXPLAIN QUERY PLAN ' prefix to the original query. For instance:
tarantool> box.execute("explain query plan select * from \"test\" where \"col1\" = 'AAA' collate \"unicode_ci\"")
---
- metadata:
- name: selectid
type: integer
- name: order
type: integer
- name: from
type: integer
- name: detail
type: text
rows:
- [0, 0, 0, 'SEARCH TABLE test USING COVERING INDEX col1_idx (col1=?) (~1 row)']
...
So the answer is 'yes', index is used in this case.
As for another example:
box.execute("select * from \"test\" indexed by \"col1_idx\" where \"col1\" = 'AAA'")
Unfortunately collation in this comparison is binary, since index's collation is ignored. In SQL only column's collations are considered to be used during comparison. This limitation will be resolved as soon as corresponding issue is closed.

How to write a CASE clause with another column as a condition using knex.js

So my code is like one below:
.select('id','units',knex.raw('case when units > 0 then cost else 0 end'))
but it gives me error like this one
hint: "No operator matches the given name and argument type(s). You might need to add explicit type casts."
Any idea how I should right my code so I can use another column as an condition for different to column ?
I don't get the same error you do:
CASE types integer and character varying cannot be matched
but regardless, the issue is that you're trying to compare apples and oranges. Postgres is quite strict on column types, so attempting to put an integer 0 and a string (value of cost) in the same column does not result in an implicit cast.
Turning your output into a string does the trick:
.select(
"id",
"units",
db.raw("CASE WHEN units > 0 THEN cost ELSE '0' END AS cost")
)
Sample output:
[
{ id: 1, units: null, cost: '0' },
{ id: 2, units: 1.2, cost: '2.99' },
{ id: 3, units: 0.9, cost: '4.50' },
{ id: 4, units: 5, cost: '1.23' },
{ id: 5, units: 0, cost: '0' }
]

How to match a string value from the database with an integer value from the response

My feature calls Java to query the database and then compares the results with the response. The results from the Java call returns all values as string. But in the response some of values are integer. So the test fails with the reason: actual value is not a string. I have tried to convert the results to json but that didn't work. If I print out the results, it shows all keys and values are enclosed in double quotes, but there are no double quotes in the error message. I found a similar question in the forum and it was suggested to set the field in the response to '#ignore'. But I want to verify all the fields. How do I get this to work?
Scenario: Get an script by id
* def results = db.getRows("select * from ScriptVersion where id=4 order by version")
Given path '4'
When method get
Then status 200
And match response.version == results
[main] ERROR com.intuit.karate - assertion failed: path: $.version[0], actual: {id=4, version=1, created=2016-06-23T10:49:51.9630000-05:00, updated=2016-06-23T10:49:51.9630000-05:00, message=Initial Version, author=ocadm, hash=0023ad00455962eee4ef1db16a58ce41}, expected: {created=2016-06-23T10:49:51.9630000-05:00, author=ocadm, id=4, message=Initial Version, version=1, updated=2016-06-23T10:49:51.9630000-05:00, hash=0023ad00455962eee4ef1db16a58ce41}, reason: [path: $.version[0].id, actual: 4, expected: '4', reason: actual value is not a string]
Just convert the fields you need to the right data type before the match:
* def results = [{ id: '1', foo: 'bar' }, { id: '2', foo: 'baz' }]
* def fun = function(x){ x.id = ~~x.id; return x }
* def results = karate.map(results, fun)
* match results == [{ id: 1, foo: 'bar' }, { id: 2, foo: 'baz' }]

Node.js - sqlstring alternative which allows named named replacements

The sqlstring node module allows creating of queries using an ordered array. So if I have a template query like:
sqlstring.format('Select * from users where id = ?', ['my_id'])
It will become:
Select * from users where id = 'my_id'
However here I need to remember the order of the question marks, so if the same thing is being in multiple places it becomes a hassle. Is there an alternative which allows me to do the following:
sqlstring.format('Select :id + :foo as bar from users where id = :id', {id: 1, foo: 3})
Which would become:
Select 1 + 3 as bar from users where id = 1
I know knex query builder does this, but I don't want install the entirety of knex just for the query builder.
You can use mysql2 package, that support that format:
Named placeholders
You can use named placeholders for parameters by setting
namedPlaceholders config value or query/execute time option. Named
placeholders are converted to unnamed ? on the client (mysql protocol
does not support named parameters). If you reference parameter
multiple times under the same name it is sent to server multiple
times.
connection.config.namedPlaceholders = true;
connection.execute('select :x + :y as z', {x: 1, y: 2}, function (err, rows) {
// statement prepared as "select ? + ? as z" and executed with [1,2] values
// rows returned: [ { z: 3 } ]
});
connection.execute('select :x + :x as z', {x: 1}, function (err, rows) {
// select ? + ? as z, execute with [1, 1]
});
connection.query('select :x + :x as z', {x: 1}, function (err, rows) {
// query select 1 + 1 as z
});