Accessing list element by index in myBatis-mapping - mybatis

I have the following mapping in myBatis.
<update id="updatePersons" parameterType="Map">
begin
<foreach item="fname" collection="fnames" index="index" separator=";">
update person set age = #{ages}[#{index}] where fname = #{fname}
</foreach>;
end;
</update>
It should update the age of all the persons whose first name matches the one passed as argument.
And the corresponding call in Java:
Map<String, List<String>> map = new HashMap<>();
List<String> fnames = new ArrayList<>();
List<Integer> ages = new ArrayList<>();
map.put("fnames", fnames);
map.put("ages", ages);
session.update("person.updatePersons", map);
The size of collections fnames and ages is the same. I have some difficulty to access elements of ages by index in myBatis-mapping. I have tried brackets as can be seen in the first snippet. I also tried #{ages}.get(#index) but nothing worked. Is it possible at all?

#{} is a placeholder (i.e. ?) in PreparedStatement, so the expression #{ages}[#index}] is translated to ?[?] which is not a valid SQL syntax.
The correct syntax would be...
<update id="updatePersons">
begin
<foreach item="fname" collection="fnames" index="index" separator=";">
update person set age = #{ages[${index}]} where fname = #{fname}
</foreach>;
end;
</update>
See the FAQ entry for the difference between #{} and ${}.
Although this may work given that the driver supports the syntax, it basically is a single big PreparedStatement with many placeholders and is not very efficient especially when there are many items in the lists.
If that is the case, you should consider using batch update. See this answer for the details.

Related

How to use dynamic regex to match value in Postgres

SUMMARY: I've two tables I want to derive info out of: family_values (family_name, item_regex) and product_ids (product_id) to be able to update the property family_name in a third.
Here the plan is to grab a json array from the small family_values table and use the column value item_regex to do a test match against the product_id for every row in product_ids.
MORE DETAILS: Importing static data from CSV to table of orders. But, in evaluating cost of goods and market value I'm needing to continuously determine family from a prefix regex (item_regex from family_values) match on the product_id.
On the client this looks like this:
const families = {
FOOBAR: 'Big Ogre',
FOOBA: 'Wood Elf',
FOO: 'Valkyrie'
};
// And to find family, and subsequently COGs and Market Value:
const findFamily = product_id => Object.keys(families).find(f => new RegExp('^' + f).test(product_id));
This is a huge hit for the client so I made a family_values table in PG to include a representative: family_name, item_regex, cogs, market_value.
Then, the product_ids has a list of only the products the app cares about (out of millions). This is actually used with an insert trigger 'on before' to ignore any CSV entries that aren't in the product_ids view. So, I guess after that the product_ids view could be taken out of the equation because the orders, after inserting readonly data, has its own matching product_id. It does NOT have family_name, so I still have the issue of determining that client-side.
PSUEDO CODE: update family column of orders with family_name from family_values regex match against orders.product_id
OR update the product_ids table with a new family column and use that with the existing on insert trigger (used to left pad zeros and normalize data right now). Now I'm thinking this may be just an update as suggested, but not real good with regex in PG. I'm a PG novice.
PROBLEM: But, I'm having a hangup in doing what I thought would be like a JS Array Find operation. The family_values have been sorted on the item_regex so that the most strict match would be on top, and therefor found first.
For example, with sorting we have:
family_values_array = [
{"family_name": "Big Ogre", "item_regex": "FOOBAR"},
{"family_name": "Wood Elf", "item_regex": "FOOBA"},
{"family_name": "Valkyrie", "item_regex": "FOO"}]
So, that the comparison of product_id of ^FOOBA would yield family "Wood Elf".
SOLUTION:
The solution I finally came about using was simply using concat to write out the front-anchored regex. It was so simple in the end. The key line I was missing is:
select * into family_value_row from iol.family_values
where lvl3_id = product_row.lvl3_id and product_row.product_id
like concat(item_regex, '%') limit 1;
Whole function:
create or replace function iol.populate_families () returns void as $$
declare
product_row record;
family_value_row record;
begin
for product_row in
select product_id, lvl3_id from iol.products
loop
-- family_name is what we want after finding the BEST match fr a product_id against item_regex
select * into family_value_row from iol.family_values
where lvl3_id = product_row.lvl3_id and product_row.product_id like concat(item_regex, '%') limit 1;
-- update family_name and value columns
update iol.products set
family_name = family_value_row.family_name,
cog_cents = family_value_row.cog_cents,
market_value_cents = family_value_row.market_value_cents
where product_id = product_row.product_id;
end loop;
end;
$$
LANGUAGE plpgsql;
Use concat as updated above:
select * into family_value_row from iol.family_values
where lvl3_id = product_row.lvl3_id and product_row.product_id
like concat(item_regex, '%') limit 1;

FileMaker calculated filtered list

I am currently getting a list of related field like so
List ( join_table::id_b;)'
and what i would like to do is filter that list by a second field in the same related table pseudo code as follows
List ( join_table::id_b;jointable:other="foo")
not really sure how to filter it down
The List() function will return a list of (non-empty) values from all related records.
To get a list filtered by a second field, you could do any one of the following:
Define a calculation field in the join table = If ( other = "foo" ; id_b ) and use this field in your List() function call instead of the id_b field;
Construct a relationship filtered by the other field;
Use the ExecuteSQL() function instead of List();
Write your own recursive custom function (requires the Advanced version to install).

AREL: writing complex update statements with from clause

I tried looking for an example of using Arel::UpdateManager to form an update statement with a from clause (as in UPDATE t SET t.itty = "b" FROM .... WHERE ...), couldn.t find any. The way I've seen it, Arel::UpdateManager sets the main engine on initialization and allows to set the various fields and values to update. Is there actually a way to do this?
Another aside would be to find out how to express Postgres posix regex matching into ARel, but this might be impossible by now.
As far as I see the current version of arel gem is not support FROM keyword for the sql query. You can generate a query using the SET, and WHERE keywords only, like:
UPDATE t SET t.itty = "b" WHERE ...
and the code, which copies a value from field2 to field1 for the units table, will be like:
relation = Unit.all
um = Arel::UpdateManager.new(relation.engine)
um.table(relation.table)
um.ast.wheres = relation.wheres.to_a
um.set(Arel::Nodes::SqlLiteral.new('field1 = "field2"'))
ActiveRecord::Base.connection.execute(um.to_sql)
Exactly you can use the additional method to update a relation. So we create the Arel's UpdateManager, assigning to it the table, where clause, and values to set. Values shell be passed to the method as an argument. Then we need to add FROM keyword to the generated SQL request, we add it only if we have access to external table of the specified one by the UPDATE clause itself. And at the last we executes the query. So we get:
def update_relation!(relation, values)
um = Arel::UpdateManager.new(relation.engine)
um.table(relation.table)
um.ast.wheres = relation.wheres.to_a
um.set(values)
sql = um.to_sql
# appends FROM field to the query if needed
m = sql.match(/WHERE/)
tables = relation.arel.source.to_a.select {|v| v.class == Arel::Table }.map(&:name).uniq
tables.shift
sql.insert(m.begin(0), "FROM #{tables.join(",")} ") if m && !tables.empty?
# executes the query
ActiveRecord::Base.connection.execute(sql)
end
The you can issue the the relation update as:
values = Arel::Nodes::SqlLiteral.new('field1 = "field2", field2 = NULL')
relation = Unit.not_rejected.where(Unit.arel_table[:field2].not_eq(nil))
update_relation!(relation, values)

PostgreSQL, using xml

Here is example table used for getting some basic operations with xml column in postgreSQL table.
DROP TABLE IF EXISTS temp1;
CREATE TABLE temp1(myindex serial PRIMARY KEY, description xml);
INSERT INTO temp1(description)
VALUES
('<?xml version="1.0" encoding="utf-8"?>
<setup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DATABASE>herdatabase</DATABASE>
<DBSERVER>127.0.0.1</DBSERVER>
<DBUSER>saly</DBUSER>
<DBPORT>5432</DBPORT>
</setup>'),
('<?xml version="1.0" encoding="utf-8"?>
<setup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DATABASE>mydatabase</DATABASE>
<DBSERVER>127.0.0.1</DBSERVER>
<DBUSER>john</DBUSER>
<DBPORT>4424</DBPORT>
</setup>');
I decided to use XML instead of hstore and JSON since I'm working in .NET where XML functions and serialization is well supported and I haven't much of such data so speed is not much important.
From here I try some basic queries to get data.
--1) That one work
SELECT xpath('/setup/DBPORT/text()', description) FROM temp1;
--2) That work but give two arrays with single value
-- How to get one array with 2 values like "{5432, 127.0.0.1}"
SELECT xpath('/setup/DBPORT/text()', description), xpath('/setup/DBSERVER/text()', description) FROM temp1;
--3) How to get description when condition is met?
-- Here I get ERROR: could not identify an equality operator for type xml
SELECT description FROM temp1 WHERE xpath('/setup/DBSERVER/text()', description) = '{127.0.0.1}';
--4) How to get all values when condition is met?
SELECT allvalues FROM temp1 WHERE xpath('/setup/DBUSER/text()', description) = 'john';
How to get working those queries which don't work?
2 - Use the XPath "or" operator, |, to select either a DBPORT or DBSERVER:
SELECT xpath('/setup/DBPORT/text()|/setup/DBSERVER/text()', description)
FROM temp1;
3 - The xpath() function returns an XML array which can be cast to a TEXT array for easier matching to other values:
SELECT description
FROM temp1
WHERE xpath('/setup/DBSERVER/text()', description)::TEXT[] = '{127.0.0.1}'::TEXT[];
4 - Similar to the previous, cast the XML array to a Text array to match to a value:
SELECT xpath('/setup/node()/text()', description)
FROM temp1
WHERE xpath('/setup/DBUSER/text()', description)::TEXT[] = '{john}'::TEXT[];
For the second, you have two arrays, so you can use array_cat():
SELECT array_cat(xpath('/setup/DBPORT/text()', description),
xpath('/setup/DBSERVER/text()', description))
FROM temp1;
For the third, you have one array of values (it's possible that your xpath matches multiple /setup/DBSERVER elements, thus the array type). This takes the first element from the array and casts to text so that you can compare to the string
SELECT description
FROM temp1
WHERE (xpath('/setup/DBSERVER/text()', description))[1]::text = '127.0.0.7';
Finally, you can use an xpath to generate an array of your elements, then unnest() them (so you get one row per element), then use another xpath to get at the element content. This gives the element content, but not the element name - I don't know the xpath to get the tag name off the top of my head.
SELECT xpath('/', unnest(xpath('/setup/*', description)))
FROM temp1
WHERE (xpath('/setup/DBUSER/text()', description))[1]::text = 'john';

How to generate a predicate array of OR statements

I am trying to use the Criteria API instead of constructing queries as JPQL Strings as the Criteria API seems much better suited for this. However, I am having a few problems understanding how to construct the two following statements.
SELECT e
FROM Subject e
WHERE e.company = :c1
OR e.company = :c2
OR e.company = :c3
In this case I need to iterate over an unknown number of values (c1, c2, c3 etc.) to match to the same attribute.
SELECT e
FROM Subject e
WHERE e.company
LIKE :c1
OR e.account
LIKE :c1
OR e.email
LIKE :c1
In this case I need to pass in a single value (c1) and have a 'LIKE' comparison done on a specific range of attributes.
My current pattern looks something like this:
// Criteria
CriteriaBuilder builder = subjectDAO.criteriaBuilder();
CriteriaQuery<Subject> query = builder.createQuery(Subject.class);
// Root
Root<Subject> subject = query.from(Subject.class);
List<Predicate> predicates = new ArrayList();
for (String property : map.keySet()) {
String value = (String) coreFilter.get(map);
predicates.add(????? This is where I come unstuck ?????);
}
// pass all the predicates into the query
query.where(predicates.toArray(new Predicate[predicates.size()]));
NB. I don't have any problems constructing the Query object or specifying the Root or Joins. I am just having problems with the specificity of the above queries. For the sake of clarity just assume that all the attributes are String and don't require any joins.
The expression CriteriaQuery<T> where(Predicate... restrictions), as you can see in the javadoc,
Modify the query to restrict the query result according to the conjunction of the specified restriction predicates.
So, it makes the conjunction of the predicates in the list, i.e. it concatenates them with AND expressions. In order to concatenate them with OR expression, simply use CriteriaBuilder#or(Predicate... restrictions)
query.where(builder.or(predicates.toArray(new Predicate[] {})));