Okay, I am trying to generate sql for a Map<String,List<String>>, I am having a nested foreach statements to generate sql. This is my mybatis xml mapper file:
<select id="UserScaleResult.listAccordingToScaleBrief" resultType="java.util.Map">
select distinct uu.id as uid, uu.fullname, ucn.card_number,
uu.mobile_number
from
<include refid="UserScaleResult.baseCondition.from" />
<include refid="UserScaleResult.UserType.from"/>
<include refid="UserScaleResult.listAccordingToScale.from"/>
<include refid="UserScaleResult.baseConditions.where" />
<include refid="UserScaleResult.UserType.where" />
<include refid="UserScaleResult.listAccordingToScale.where" />
limit #{offset}, #{pageSize}
</select>
<sql id="UserScaleResult.listAccordingToScale.where">
<if test="#Ognl#isNotEmpty(type)">
and musr.type = #{type}
</if>
<if
test="#Ognl#isNotEmpty(createTimeBegin) and #Ognl#isNotEmpty(createTimeEnd)">
and musr.create_time between #{createTimeBegin} and
#{createTimeEnd};
</if>
<if test="#Ognl#isNotEmpty(missionId)">
and musr.mission_id = #{missionId}
</if>
<if test="#Ognl#isNotEmpty(scaleId)">
and musr.scale_id = #{scaleId}
</if>
<if test="#Ognl#isNotEmpty(sdl)">
and (
<foreach collection="sdl.entrySet()" item="item" open="(" separator=") or (" close=")">
tsrdl.dimension_id = #{item.key}
<if test="#Ognl#isNotEmpty(item.value)">
and
<foreach collection="item.value" item="iitem" open="(" separator=" or " close=")">
tsrdl.level_id = #{iitem}
</foreach>
</if>
</foreach>
)
</if>
</sql>
The log shows the #{iitem} are all null when it put it down to sql, although the number of parameters are correct:
2016-05-13 14:47:27,837 DEBUG [java.sql.Connection] - <==> Preparing: select count(*) from user_user uu left join user_user_detail uud on uu.id = uud.user_id left join user_card_number ucn on uu.card_number_id =ucn.id left join org_org oo on ucn.org_id = oo.id inner join (SELECT user_id, MAX(IF(extension_item_id=?,DATA,'') )AS ? FROM user_extension_item_data GROUP BY user_id) AS ud on uu.id = ud.user_id inner join mission_user_scale_result musr on uu.id = musr.user_id inner join test_scale ts on musr.scale_id = ts.id inner join test_scale_result_dimension_level tsrdl on tsrdl.result_id = musr.id and ud.3 = ? and ( ( tsrdl.dimension_id = ? and ( tsrdl.level_id = ? or tsrdl.level_id = ? ) ) or ( tsrdl.dimension_id = ? and ( tsrdl.level_id = ? or tsrdl.level_id = ? or tsrdl.level_id = ? ) ) ) >
2016-05-13 14:47:27,838 DEBUG [java.sql.PreparedStatement] - <==> Parameters: 3(String), 3(String), 保密(String), 42(String), null, null, 47(String), null, null, null>
EDIT:
Just did some experiment, when I substitute the '#' sign in #{iitem} with '$', the value appears and the sql gets parameters all good.
We need to use ${dataBaseTable} instead of #. The difference is that # is used for PreparedStatement substitution. $ is for direct String substitution.
Replace your #{iitem} with ${iitem}
....
<foreach collection="item.value" item="iitem" open="(" separator=" or " close=")">
tsrdl.level_id = ${iitem}
</foreach>
....
Related
I write a sql fragment, like this:
<sql id="day">
<choose>
<when test="${property} == 'day'">
substring(datachange_createtime, 1, 10)
</when>
<otherwise>
${property}
</otherwise>
</choose>
</sql>
and include this sql like this:
select * from my_table
<if test="groupBy != null">
group by
<foreach collection="groupBy" item="groupByAttr" separator=",">
<include refid="day">
<property name="property" value="groupByAttr"/>
</include>
</foreach>
</if>
But I got an error, mybatis didn't substitute property with a real parameter, but with groupByAttr. The result sql is like:
select * from my_table group by groupByAttr
In the otherwise clause, if I replace ${property} with #{${property}}, The result sql becomes:
select * from my_table group by ?
It is indeed correct, but for some reasons, I don't want a prepared sql.
I think the problem is the behavior of substitution in test and otherwise clauses is different.
Is the a bug, or just some mistakes I took? How to solve this problem?
Short answer:
You need to change the <include /> as follows.
<sql id="day">
<choose>
<when test="${property} == 'day'">
substring(datachange_createtime, 1, 10)
</when>
<otherwise>
\\${${property}}
</otherwise>
</choose>
</sql>
Long answer:
With your original code, the statement will look as follows after the <include /> substitution.
select * from my_table
<if test="groupBy != null">
group by
<foreach collection="groupBy" item="groupByAttr" separator=",">
<choose>
<when test="groupByAttr == 'day'">
substring(datachange_createtime, 1, 10)
</when>
<otherwise>
groupByAttr
</otherwise>
</choose>
</foreach>
</if>
As you can see, <when /> is OK, but <otherwise /> is not.
<otherwise /> should look like this:
<otherwise>
${groupByAttr}
</otherwise>
In the expression in the short answer \\${${property}}, the first two backslashes is the escape syntax which avoids the outer ${} from being treated as a variable.
Note that #{groupByAttr} does not work here because, in java.sql.PreparedStatement, a placeholder ? is not allowed for a table/column name.
Please see the wiki page for the details.
I am looking for a query where i can use either internal query returned value or direct SQL in when test= in Mybatis.
<foreach item="ID"
index="index"
collection="selectionIds"
separator=";">
UPDATE TABLE_1 SET
ACT_IND ='N', upd_by = 1234
WHERE SLCT_ID = #{ID}
AND rem= select REM from TABLE_1 where SLCT_ID=#{ID}
<choose>
<when test="rem == 3">
AND Bbsid=#{nsid}
</when>
<otherwise>
AND asid=#{asid}
</otherwise>
</choose>
</foreach>
From the above query, how can i get the rem value and use in when condition.
Thanks
I have a MyBatis query that looks like this:
<if test="userIdList != null and userIdList > 0">
AND galleries.id IN (
SELECT gallery_id
FROM gallery_users
WHERE gallery_id IN (
<foreach collection="userIdList" item="item" separator="," open="(" close=")">
#{item}
</foreach>
)
GROUP BY gallery_id HAVING COUNT(gallery_id) = ???
)
</if>
That part I'm stuck on is getting the collection size which will be dynamic. So how can I get the collection size so that I can properly fill in the '???' value?
You can invoke the Collection#size() method using OGNL expression. i.e.
GROUP BY gallery_id HAVING COUNT(gallery_id) = ${userIdList.size}
Note that #{userIdList.size} won't work here because the expression in #{} is parsed by MyBatis' internal expression parser and not by OGNL.
I need to a list of Pair to MyBatis query:
in Mapper:
List<Usage> getUsageByUserLanguagesAndDates(
#Param("userLanguagePairList") List<Pair<String, String>> userLanguageMap,
#Param("startDate") DateTime startDate,
#Param("endDate") DateTime endDate)
List> userLanguageMap looks like this:
List<Pair<String, String>> pairs = Lists.newArrayList;
pairs.add(new Pair("12345", "en-US"));
pairs.add(new Pair("23456", "en-GB"));
...
and the query looks like this:
SELECT
...
FROM
...
WHERE 1=1
AND
(
(l.userid = '12345' AND ll.language_code = 'en-US') OR
(l.userid = '23456' AND ll.language_code = 'en-GB')
)
;
I tried to write the query as:
AND (
<foreach item="userLanguagePair" index="index" collection="userLanguagePairList" open="" separator="," close="">
(
l.userid = #{userLanguagePair.first}
AND
ll.language_code = #{userLanguagePair.second}
)
OR
</foreach>
)
...
but there will be an extra OR at the end:
AND
(
(l.userid = '12345' AND ll.language_code = 'en-US') OR
(l.userid = '23456' AND ll.language_code = 'en-US') OR
)
how can i get rid of the last 'OR'?
From http://www.mybatis.org/mybatis-3/dynamic-sql.html:
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">,
so I'd say your code should be something like this:
...
WHERE 1=1 AND (
<foreach item="userLanguagePair" index="index" collection="userLanguagePairList" open="(" separator="OR" close=")">
l.userid = #{userLanguagePair.first}
AND
ll.language_code = #{userLanguagePair.second}
</foreach>
)
but right now I cannot check if this will generate the correct sentence.
I am trying myBatis recently and I have been facing a problem when forming dynamic sqls.
When using myBatis to form dynamic sql like below, it works well with all the fields except when I send value in email field.
No data is fetched when emailId is passed. I have a doubt if the # symbol in emailId column is having a problem?
<select id = "getAllWithFilter" parameterType="java.util.Map" resultMap="result">
SELECT * FROM EMPLOYEE_LOOKUP
<where>
<if test = "firstName != null">
FIRST_NAME LIKE #{firstName}
</if>
<if test = "phoneNo != null">
AND PHONE LIKE #{phoneNo}
</if>
<if test = "emailId != null">
AND EMAIL LIKE #{emailId}
</if>
<if test = "analystGroup != null">
AND GROUP LIKE #{empGroup}
</if>
<choose>
<when test = "activeFlag != null">
AND EXPIRATION_DATE IS NULL
</when>
<when test = "inactiveFlag != null">
AND EXPIRATION_DATE IS NOT NULL
</when>
</choose>
</where>
</select>
This is the DEBUG Logs in myBatis - Log4J.
DEBUG [http-bio-9081-exec-1] - ==> Preparing: SELECT * FROM EMPLOYEE_LOOKUP WHERE EMAIL LIKE ? AND GROUP LIKE ? AND EXPIRATION_DATE IS NULL
DEBUG [http-bio-9081-exec-1] - ==> Parameters: abc(String), F(String)
Database, contains the following values
EMAIL | GROUP
----------------------
abc#gsf.com | F
You have simply forgotten the % surrounding the parameter value:
AND EMAIL LIKE '%' + #{emailId} + '%'
or
AND EMAIL LIKE '%' || #{emailId} || '%'
depending on DB vendor.
Alternatively, you can add the % in the param value after escaping those that might be in the param value.
otherwise it behaves as =