一對(duì)多分頁的SQL到底應(yīng)該怎么寫?
前言
MySQL一對(duì)多的數(shù)據(jù)分頁是非常常見的需求,比如我們要查詢商品和商品的圖片信息。但是很多人會(huì)在這里遇到分頁的誤區(qū),得到不正確的結(jié)果。今天就來分析并解決這個(gè)問題。
2. 問題分析
我們先創(chuàng)建一個(gè)簡單商品表和對(duì)應(yīng)的商品圖片關(guān)系表,它們之間是一對(duì)多的關(guān)系:
然后我分別寫入了一些商品和這些商品對(duì)應(yīng)的圖片,通過下面的左連接查詢可以看出它們之間具有明顯的一對(duì)多關(guān)系:
- SELECT P.PRODUCT_ID, P.PROD_NAME, PI.IMAGE_URL
- FROM PRODUCT_INFO P
- LEFT JOIN PRODUCT_IMAGE PI
- ON P.PRODUCT_ID = PI.PRODUCT_ID
按照傳統(tǒng)的思維我們的分頁語句會(huì)這么寫:
- <resultMap id="ProductDTO" type="cn.felord.mybatis.entity.ProductDTO">
- <id property="productId" column="product_id"/>
- <result property="prodName" column="prod_name"/>
- <collection property="imageUrls" ofType="string">
- <result column="image_url"/>
- </collection>
- </resultMap>
- <select id="page" resultMap="ProductDTO">
- SELECT P.PRODUCT_ID, P.PROD_NAME,PI.IMAGE_URL
- FROM PRODUCT_INFO P
- LEFT JOIN PRODUCT_IMAGE PI
- ON P.PRODUCT_ID = PI.PRODUCT_ID
- LIMIT #{current},#{size}
- </select>
當(dāng)我按照預(yù)想傳入了(0,2)想拿到前兩個(gè)產(chǎn)品的數(shù)據(jù),結(jié)果并不是我期望的:
- 2020-06-21 23:35:54.515 DEBUG 10980 --- [main] c.f.m.mappers.ProductInfoMapper.page : ==> Preparing: SELECT P.PRODUCT_ID, P.PROD_NAME,PI.IMAGE_URL FROM PRODUCT_INFO P LEFT JOIN PRODUCT_IMAGE PI ON P.PRODUCT_ID = PI.PRODUCT_ID limit ?,?
- 2020-06-21 23:35:54.541 DEBUG 10980 --- [main] c.f.m.mappers.ProductInfoMapper.page : ==> Parameters: 0(Long), 2(Long)
- 2020-06-21 23:35:54.565 DEBUG 10980 --- [main] c.f.m.mappers.ProductInfoMapper.page : <== Total: 2
- page = [ProductDTO{productId=1, prodName='杯子', imageUrls=[http://asset.felord.cn/cup1.png, http://asset.felord.cn/cup2.png]}]
我期望的兩條數(shù)據(jù)是杯子和筆記本,但是結(jié)果卻只有一條。原來當(dāng)一對(duì)多映射時(shí)結(jié)果集會(huì)按照多的一側(cè)進(jìn)行輸出(期望4條數(shù)據(jù),實(shí)際上會(huì)有7條),而前兩條展示的只會(huì)是杯子的數(shù)據(jù)(如上圖),合并后就只有一條結(jié)果了,這樣分頁就對(duì)不上了。那么如何才能達(dá)到我們期望的分頁效果呢?
3. 正確的方式
正確的思路是應(yīng)該先對(duì)主表進(jìn)行分頁,再關(guān)聯(lián)從表進(jìn)行查詢。
拋開框架,我們的SQL應(yīng)該先對(duì)產(chǎn)品表進(jìn)行分頁查詢?nèi)缓笤僮箨P(guān)聯(lián)圖片表進(jìn)行查詢:
- SELECT P.PRODUCT_ID, P.PROD_NAME, PI.IMAGE_URL
- FROM (SELECT PRODUCT_ID, PROD_NAME
- FROM PRODUCT_INFO
- LIMIT #{current},#{size}) P
- LEFT JOIN PRODUCT_IMAGE PI
- ON P.PRODUCT_ID = PI.PRODUCT_ID
這種寫法的好處就是通用性強(qiáng)一些。但是MyBatis提供了一個(gè)相對(duì)優(yōu)雅的路子,思路依然是開頭所說的思路。只不過我們需要改造上面的Mybatis XML配置:
- <resultMap id="ProductDTO" type="cn.felord.mybatis.entity.ProductDTO">
- <id property="productId" column="product_id"/>
- <result property="prodName" column="prod_name"/>
- <!-- 利用 collection 標(biāo)簽提供的 select 特性 和 column -->
- <collection property="imageUrls" ofType="string" select="selectImagesByProductId" column="product_id"/>
- </resultMap>
- <!-- 先查詢主表的分頁數(shù)據(jù) -->
- <select id="page" resultMap="ProductDTO">
- SELECT PRODUCT_ID, PROD_NAME
- FROM PRODUCT_INFO
- LIMIT #{current},#{size}
- </select>
- <!--根據(jù)productId 查詢對(duì)應(yīng)的圖片-->
- <select id="selectImagesByProductId" resultType="string">
- SELECT IMAGE_URL
- FROM PRODUCT_IMAGE
- WHERE PRODUCT_ID = #{productId}
- </select>
4. 總結(jié)
大部分情況下分頁是很容易的,但是一對(duì)多還是有一些小小的陷阱的。一旦我們了解了其中的機(jī)制,也并不難解決。當(dāng)然如果你有更好的解決方案可以留言討論,集思廣益。多多關(guān)注:碼農(nóng)小胖哥,獲取更多開發(fā)技巧。