我們真的需要鏈式查詢嗎?

2023-07-22 18:01:02

多年後,面對Quicksql的時候,我將會想起剛參加工作面對mongodb複雜聚合查詢的那個遙遠下午。

那是一種淡淡的憂傷,這麼多年來,這種憂傷僅次於Neo4j那獨樹一幟的語法給我帶來的驚()喜()。

warning!
warning!
warning!






以下是一個基於java api的管道聚合查詢,滿屏溢位的憂傷,逆流成河。

List<AggregationOperation> operations = new ArrayList<>();

       Criteria criteriaGroup = new Criteria();
       criteriaGroup.andOperator(
               Criteria.where("_id").is(userQuery.getId()),
               Criteria.where("deleteFlag").is("N"));
       MatchOperation matchGroup = new MatchOperation(criteriaGroup);

       UnwindOperation unwindUser = new UnwindOperation(Fields.field("userIds"), true);

       Field fromEntityType = Fields.field("user");
       Field localFieldEntityType = Fields.field("userIds");
       Field foreignFieldEntityType = Fields.field("_id");
       Field asEntityType = Fields.field("user");
       LookupOperation lookUpUser = new LookupOperation(fromEntityType, localFieldEntityType,
               foreignFieldEntityType, asEntityType);

       Fields projectUserFields = Fields.fields("user");
       ProjectionOperation projectGroup = new ProjectionOperation(projectUserFields);
       projectGroup.andExclude("_id");

       AggregationExpression userAggregationExpression = ArrayOperators.ArrayElemAt.arrayOf("user").elementAt(0);
       ReplaceRootOperation replaceRootUser = new ReplaceRootOperation(userAggregationExpression);

       SetOperation set = new SetOperation("deleteFlag", "");

       Criteria criteriaUser = new Criteria();
       criteriaUser.orOperator(
               Criteria.where("userName").regex(".*" + userQuery.getKeyword() + ".*"),
               Criteria.where("userDes").regex(".*" + userQuery.getKeyword() + ".*"));
       MatchOperation matchUser = new MatchOperation(criteriaUser);

       List<AggregationOperation> dataOperations = new ArrayList<>();
       SkipOperation skipOperation = new SkipOperation((userQuery.getPage() - 1) * userQuery.getSize());
       LimitOperation limitOperation = new LimitOperation(userQuery.getSize());
       dataOperations.add(skipOperation);
       dataOperations.add(limitOperation);

       CountOperation countOperation = new CountOperation("total");

       FacetOperation facet = new FacetOperation().and(countOperation).as("metadata")
               .and(dataOperations.toArray(new AggregationOperation[dataOperations.size()])).as("records");

       AggregationExpression totalArray = ArrayOperators.ArrayElemAt.arrayOf("metadata").elementAt(0);
       ObjectOperators.MergeObjects om = ObjectOperators.valueOf(totalArray).mergeWith(ROOT);
       ReplaceRootOperation replaceRootTotal = new ReplaceRootOperation(om);

       operations.add(matchGroup);
       operations.add(unwindUser);
       operations.add(lookUpUser);
       operations.add(projectGroup);
       operations.add(replaceRootUser);
       operations.add(set);
       operations.add(matchUser);
       operations.add(facet);
       operations.add(replaceRootTotal);

       Aggregation aggregation = Aggregation.newAggregation(operations);
       AggregationResults<UserPageVO> results = mongoTemplate.aggregate(aggregation, "user_group",
               UserPageVO.class);
       UserPageVO vo = results.getUniqueMappedResult();   

或者ElasticSearch 的一個不算複雜的常規聚合查詢

SearchRequestBuilder searchRequestBuilder=getClient(deviceIndexName,esclient);
BoolQueryBuilder query = QueryBuilders.boolQuery();
		query.filter(QueryBuilders.rangeQuery("time").gte(startTimeMills).lte(endTimeMills));
query.should(QueryBuilders.termsQuery("deviceId", deviceIdLst));
 
TermsBuilder deviceAgg = AggregationBuilders.terms("deviceAgg").field("deviceId").size(1000);
Map<String, Object> aggs = new HashMap<>();
String aggStr = "{\"rated\":{\"top_hits\":{\"sort\":[{\"ip\":{\"order\":\"desc\"}},{\"time\":{\"order\":\"asc\"}}],\"size\":1}}}";
aggs = JSON.parseObject(aggStr, new TypeReference<Map<String, Object>>(){});
deviceAgg.subAggregation(aggs);
CardinalityBuilder deviceIdCard = AggregationBuilders.cardinality("deviceCard").field("deviceId").precisionThreshold(100);
 
SearchResponse response = searchRequestBuilder.setQuery(query).addAggregation(deviceIdCard).addAggregation(deviceAgg).setSize(1).execute().actionGet();
Terms deviceAggRes = response.getAggregations().get("deviceAgg");
List<Map<String, Object>> dataList = new ArrayList<>();
for (Terms.Bucket deviceBuck : deviceAggRes.getBuckets()){
	InternalTopHits rated = deviceBuck.getAggregations().get("rated");
	SearchHit[] searchHits = rated.getHits().hits();
	List<Map<String, Object>> hitDataList = Lambda.extract(searchHits, Lambda.on(SearchHit.class).getSource());
	dataList.addAll(hitDataList);
}

那時我想的是,我會java,我還會sql,但為什麼我不會es和mongodb資料庫查詢。

這個(java),再加上這個(sql),能不能站著把錢掙了?

這些複雜的API,我需要花時間去熟悉,好不容易用熟悉了常用的一些,然後,除非我一直用,不然過一個月,鐵定又給忘了。

ElasticSearch,mongodb不就是資料庫嗎?
甭管你造些什麼名詞出來忽悠我,關係型資料庫,非關係型資料庫,nosql,new sql,它不都是資料庫嗎?

這些年過去了,

好訊息是,mybatis本身已經支援mongodb,neo4j這些非傳統關係型資料庫了。
有些個人開發者或者公司也在這些非關係型資料庫的基礎上,開發包裝了一層通用的查詢語言,大大降低了學習曲線和上手難度。

壞訊息是,mybatis-plus好像又走上了monogodb-java-driver各種API點點點的復讀機老路了。

我能理解SQL是Structured,而es,mongo已經不是Structured的資料庫了,但面對傳統關係型資料庫,mybatis-plus為什麼要反其道而行之呢?

我需要去熟悉這些API,一個稍微複雜的查詢點點點出來一串鏈式的程式碼堆在那裡。

我並不覺得這是一件優雅的事情。

同樣的,在巨量資料領域裡,面對的資料庫挑戰並不比web方向更少。
相關的資料庫更加的繁多。

但是spark/flink保證了統一的操作方式。

比如,要實現一個經典的topN,使用flink DataStream API的方式,非常複雜,需要熟悉事件驅動和諸多API。

以前寫過demo,但沒找到,網上隨便找了一個,大家隨意感受一下程式碼量,以及上手難度。

https://blog.csdn.net/zhungcan/article/details/116271588

而使用table api 即flink sql呢?
剛好手邊有個以前寫的demo,不涉及業務。

一個簡單的全域性topN場景

 String sql = " select * from ( " +
                "select *, ROW_NUMBER() over ( partition by key order by price desc  ) as rownum " +
                "from test " +
                ") where rownum <= 3";

這種一般用得少,多作用於下面這種實時視窗上

 String sqlWindowTopN =
                "select * from (" +
                "  select *, " +
                "   ROW_NUMBER() over (partition by window_start, window_end order by total desc ) as rownum " +
                "     from (" +
                "       select key,window_start,window_end,count(key) as `count`,sum(price) total from table (" +
                "           HOP(TABLE test, DESCRIPTOR(`time`), interval '1' minute, interval '3' minutes)" +
                "        ) group by window_start, window_end, key" +
                "   )" +
                ") where rownum <= 3";

        st.executeSql(sqlWindowTopN).print();

哪怕你不是java程式設計師,哪怕你對flink乃至於巨量資料一竅不通,但只要你懂sql,你基本就能看懂上面程式碼的意圖,上手難度大大降低。

巨量資料的趨勢是最大程度對新手甚至外行友好。力求零成本上手。
資料是流動的,沒有流和批的區別,更沒有高低貴賤的鄙視鏈,只區分有界與無界。
資料從一個地方流到另一個地方,各種分析處理,都能通過sql來直接解決。

https://juejin.cn/post/7231567262448746551

不管巨量資料還是web方向,我覺得不管什麼資料庫,甭造些名詞來唬我,只要是存資料的地方,就應該用通用的查詢語言來操作,不要搞些生僻的API在那裡點點點,什麼都要點點點只會害了你。

本文無過激字眼,不引戰。彼之砒霜,吾之蜜糖。彼之蜜糖,吾之砒霜。理性交流。