记一次复杂sql涉及到的sql语法

news/2025/2/27 21:07:32/文章来源:https://www.cnblogs.com/caozz/p/18563952/sql

这是一个报表的sql查询,查询销售员的业绩分析.

一、表说明

  • customer:客户表,客户与销售人员(通过 salesman_id)关联。
  • sale_contract :销售合同表
  • user:用户表(销售人员表),包含字段如 real_name(姓名)、post_id(职位ID)、dept_id(部门ID)。
  • post:销售人员的职位信息。
  • dept:销售人员所属的部门。
  • region:区域(省份)信息。
  • customer_settlement 和 customer_settlement_detail:包含结算明细数据,用于计算销售业绩和其他财务指标。

二、函数介绍

  • GROUP_CONCAT(): 是 MySQL 提供的一个聚合函数,它用于将多个行中的值连接成一个单一的字符串。连接的值可以由指定的分隔符分隔,默认情况下是逗号(,)。适用场景:比如每个客户可能有多个销售员,那么联合查询后,对客户进行分组,就能得到该客户的所有销售员
  • FORMAT(): 是一个用于将数值格式化为具有特定小数位数的字符串函数。并且会自动转为千分位表示. FORMAT(value, 2) 将数字 value 格式化为一个字符串,保留小数点后两位。即便 value 是整数,返回的结果也会包含小数点后两位(例如 10000 会格式化为 10,000.00)
  • IFNULL(): 是 MySQL 中的一个条件函数,它用于检查一个表达式是否为 NULL,如果是 NULL,则返回第二个参数的值。否则,返回第一个参数的值。在一些需要计算的时候将NULL改为0比较方便.
  • COALESCE():是一个 SQL 函数,它接受多个参数,返回第一个非 NULL 的值。 如果所有参数都是 NULL,则返回 NULL。如果 no_complete_settle_amount 为 NULL,则 COALESCE(noCompleteSettle.no_complete_settle_amount, 0) 会返回 0
  • CONCAT() 是 MySQL 中的一个字符串连接函数,用于将多个字符串连接成一个新的字符串。
  • SUM():是 SQL 中的一个聚合函数,用于计算一列数字的总和。在这个查询中,它用于计算所有发货金额的总和。
  • COUNT(): 是一个聚合函数,用于计算某列或某个表的记录数。这里,COUNT(*) 用于计算与每个销售人员相关联的客户数(即每个销售人员负责的客户的总数)。
  • FIND_IN_SET(): 是 MySQL 中的一个字符串函数,用于查找一个字符串在一个由逗号分隔的字符串列表中的位置。如果该字符串在列表中存在,FIND_IN_SET() 返回其位置(从 1 开始);如果该字符串不在列表中,则返回 0。如果提供的字符串为 NULL,则返回 NULL。
  • if(): IF 函数用于根据条件判断返回两个值中的一个。用法:IF(条件, 条件成立时的值, 条件不成立时的值)

三、sql片段分析

3.1 区域子查询

该子查询相对简单,不过因为查询的区域是销售员负责的客户的地区,所以当一个销售员对应多个客户,区域就可能是多个值,所以使用了GROUP_CONCAT进行拼接,使用DISTINCT去重

LEFT JOIN (SELECT uu.id,GROUP_CONCAT(DISTINCT ur.province_name) province_nameFROM  customer uLEFT JOIN user uu ON FIND_IN_SET(uu.id,u.salesman_id)LEFT JOIN region ur ON u.province_code = ur.province_code AND ur.region_level  = 1GROUP BY uu.id
) area ON area.id = uu.id

3.2 客户数子查询

这个子查询也不难,是为了查询每个销售员在指定时间范围,负责多少个有效客户.思路是查询当前销售员签订了多少有效或失效(当前失效,合同有效区与查询时间区间有重叠)合同,然后对合同对应的客户进行去重求个数

LEFT JOIN (SELECT uu.id,COUNT(DISTINCT usc.customer_id) countFROM  sale_contract  uscLEFT JOIN user uu ON FIND_IN_SET(uu.id,usc.signer)WHERE usc.is_deleted  = 0 AND usc.contract_status IN (4, 5)<if test="q.startDate != null and q.startDate != '' and q.endDate != null and q.endDate != ''">and usc.sign_date <![CDATA[ < ]]> #{q.endDate}and usc.validity_date <![CDATA[ > ]]>  #{q.startDate}</if>GROUP BY uu.id
) sale ON sale.id = uu.id

3.3 销售目标子查询

这个子查询关联销售计划,对计划数值进行累加

LEFT JOIN (SELECT c.salesman_id,SUM(c.sales_target) sales_targetFROM sale_plan aLEFT JOIN usale_plan_detail b on b.sale_plan_id = a.idLEFT JOIN sale_plan_detail_split c on c.sale_plan_detail_id = b.idWHERE a.is_deleted  = 0 AND a.execute_status = 1<if test="q.startDate != null and q.startDate != '' and q.endDate != null and q.endDate != ''">and a.start_plan_date <![CDATA[ <= ]]> #{query.endDate}and a.end_plan_date <![CDATA[ >= ]]> #{query.startDate}</if>GROUP BY c.salesman_id
) target ON target.salesman_id = uu.id

3.4 结算子查询

该子查询查看账单时间在目标区间内的发货,退货与回款
settlement_type: (1-回款;2-发货;3-退货;4-调出;5-调入;
deliverNum 发货量=发货-调出+调入;发货金额也是类似的;

LEFT JOIN (SELECT uu.id,sum(if(csd.settlement_type=2,csd.prod_weight,0))  -  sum(if(csd.settlement_type=4,csd.prod_weight,0))  + sum(if(csd.settlement_type=5,csd.prod_weight,0)) deliverNum ,sum(if(csd.settlement_type=2,csd.prod_sub_total,0))  -  sum(if(csd.settlement_type=4,csd.prod_sub_total,0))  + sum(if(csd.settlement_type=5,csd.prod_sub_total,0)) deliverMoney ,sum(if(csd.settlement_type=3,csd.prod_weight,0)) returnNum,sum(if(csd.settlement_type=3,csd.prod_sub_total,0)) returnMoney,sum(if(csd.settlement_type=1,csd.received_sub_total_amount,0)) receiptFROM  customer_settlement_detail  csdLEFT JOIN user uu ON FIND_IN_SET(uu.id,csd.sale_men_id)WHERE csd.is_deleted  = 0<if test="query.startDate != null and query.startDate != '' and query.endDate != null and query.endDate != ''">and csd.detail_date <![CDATA[ <= ]]> #{query.endDate} and csd.detail_date <![CDATA[ >= ]]> #{query.startDate}</if>GROUP BY uu.id
) settle ON settle.id = uu.id

3.5 已结算单子查询

该子查询是为了查询销售员的期初余额。期初余额指的是时间区间的前一天的余额。
customer_settlement 表存的是待结算与已结算单据,待结算单据还未计算奖励,可以使用详情表与未结算的一起,所以已结算单独拿出来进行计算。
计算逻辑是本单业务员是谁算谁的,同时每一结算单有奖励,奖励按照本单业务员的业绩进行按比例分配。
所以先按照单据和业务员查询每一单每一个业务员的业绩。本子查询下面的子查询得到的是订单,销售员,当前单销售员业绩,当前单总业绩。

  • 当前单当前销售员业绩查询:
    settlement_type: (1-回款;2-发货;3-退货;4-调出;5-调入;
SUM(CASEWHEN detail.settlement_type = 1 THEN +received_sub_total_amountWHEN detail.settlement_type IN(2,5) THEN -prod_sub_totalWHEN detail.settlement_type IN(3,4) THEN +prod_sub_totalELSE 0END
) 
  • 当前单据总业绩:当前期减去上一期再减去本期奖励
    通过当前业务员业绩除以本单总业绩,再乘以奖励得到本单业务员奖励
LEFT JOIN (SELECT salemanId, sum(salemanPerformance) saleTotalPerformanceFROM(SELECTsettlement.id as settlementId,detail.sale_men_id as salemanId,(SUM(CASEWHEN detail.settlement_type = 1 THEN +received_sub_total_amountWHEN detail.settlement_type IN(2,5) THEN -prod_sub_totalWHEN detail.settlement_type IN(3,4) THEN +prod_sub_totalELSE 0END) / (current_period_amount - last_period_amount - discount_amount) * discount_amount+SUM(CASEWHEN detail.settlement_type = 1 THEN +received_sub_total_amountWHEN detail.settlement_type IN(2,5) THEN -prod_sub_totalWHEN detail.settlement_type IN(3,4) THEN +prod_sub_totalELSE 0END)) as salemanPerformanceFROM customer_settlement settlementLEFT JOIN customer_settlement_detail detailON settlement.id = detail.settlement_idWHERE detail.is_settle = 1 AND date_format(settlement_date,'%Y-%m-%d') <![CDATA[ <= ]]> date_format(#{query.yestodayDate},'%Y-%m-%d')GROUP BY settlement.id, detail.sale_men_id) salemanPerformanceGROUP BY salemanId
) completeSettle on completeSettle.salemanId = uu.id

3.6 未结算订单

该子查询比较简单,就是在已结算基础上,去除奖励的计算,这里就不再列举了。

3.7 预收款子查询

该子查询与区域子查询差不多,都是简单的双表关联。然后对预收款进行简单的SUM运算。

LEFT JOIN (SELECT uu.id,SUM(uro.received_amount) received_amountFROM  received_order  uroLEFT JOIN user uu ON FIND_IN_SET(uu.id,uro.salesman_id)WHERE uro.is_deleted  = 0 AND uro.pay_collection_type = 0 AND uro.received_status = 1<if test="q.startDate != null and q.startDate != '' and q.endDate != null and q.endDate != ''">and uro.received_date <![CDATA[ <= ]]> #{q.endDate}and uro.received_date <![CDATA[ >= ]]> #{q.startDate}</if>GROUP BY uu.id
) receive ON receive.id = uu.id

四、SQL

经过表的介绍,涉及到的函数的介绍,以及对于每一个子查询进行分析之后,这个看起来比较复杂的sql也相对没有那么难以理解了。将每一个子查询看作一个表,也就是一个以销售员为基础的的表,通过销售员ID进行关联销售员的区域,客户数,业绩,发货退货等信息。
当然,有一些放在内存中实现可能更容易理解与调试。这里只是为了通过这个熟悉一些sql的语法,函数等

SELECT uu.id,uu.real_name saleman,up.post_name post,ud.dept_name dept,area.province_name region,IFNULL(sale.count,0) customerNum,FORMAT(IFNULL(target.sales_target,0),2) salesTarget,FORMAT(IFNULL(settle.deliverNum,0),2) deliverNum,FORMAT(IFNULL(settle.deliverMoney,0),2) deliverMoney,FORMAT(IFNULL(settle.returnNum,0),2) returnNum,FORMAT(IFNULL(settle.returnMoney,0),2) returnMoney,IFNULL(settle.receipt,0) receipt,FORMAT(IFNULL(completeSettle.saleTotalPerformance,0) + IFNULL(noCompleteSettle.amount,0),2) AS startBalance,FORMAT(IFNULL(completeSettle.saleTotalPerformance,0) + IFNULL(noCompleteSettle.amount,0) + IFNULL(settle.receipt,0) - IFNULL(settle.deliverMoney,0) + IFNULL(settle.returnMoney,0),2) as preReceiptBalance,FORMAT(IFNULL(receive.received_amount,0),2) received_amount,FORMAT(IFNULL(settle.deliverNum,0) - IFNULL(settle.returnNum,0),2) completeNumFROM customer ucLEFT JOIN user uu ON FIND_IN_SET(uu.id,uc.salesman_id)LEFT JOIN post up ON uu.post_id = up.idLEFT JOIN dept ud ON uu.dept_id = ud.idLEFT JOIN (SELECT uu.id,GROUP_CONCAT(DISTINCT ur.province_name) province_nameFROM  customer uLEFT JOIN user uu ON FIND_IN_SET(uu.id,u.salesman_id)LEFT JOIN region ur ON u.province_code = ur.province_code AND ur.region_level  = 1GROUP BY uu.id) area ON area.id = uu.idLEFT JOIN (SELECT uu.id,COUNT(DISTINCT usc.customer_id) countFROM  sale_contract  uscLEFT JOIN user uu ON FIND_IN_SET(uu.id,usc.signer)WHERE usc.is_deleted  = 0 AND usc.contract_status IN (4, 5)<if test="query.startDate != null and query.startDate != '' and query.endDate != null and query.endDate != ''">and usc.sign_date <![CDATA[ < ]]> #{query.endDate}and usc.validity_date <![CDATA[ > ]]>  #{query.startDate}</if>GROUP BY uu.id) sale ON sale.id = uu.idLEFT JOIN (SELECT c.salesman_id,SUM(c.sales_target) sales_targetFROM sale_plan aLEFT JOIN sale_plan_detail b on b.sale_plan_id = a.idLEFT JOIN sale_plan_detail_split c on c.sale_plan_detail_id = b.idWHERE a.is_deleted  = 0 AND a.execute_status = 1<if test="query.startDate != null and query.startDate != '' and query.endDate != null and query.endDate != ''">and a.start_plan_date <![CDATA[ <= ]]> #{query.endDate}and a.end_plan_date <![CDATA[ >= ]]> #{query.startDate}</if>GROUP BY c.salesman_id) target ON target.salesman_id = uu.idLEFT JOIN (SELECT uu.id,sum(if(csd.settlement_type=2,csd.prod_weight,0))  -  sum(if(csd.settlement_type=4,csd.prod_weight,0))  + sum(if(csd.settlement_type=5,csd.prod_weight,0)) deliverNum ,sum(if(csd.settlement_type=2,csd.prod_sub_total,0))  -  sum(if(csd.settlement_type=4,csd.prod_sub_total,0))  + sum(if(csd.settlement_type=5,csd.prod_sub_total,0)) deliverMoney ,sum(if(csd.settlement_type=3,csd.prod_weight,0)) returnNum,sum(if(csd.settlement_type=3,csd.prod_sub_total,0)) returnMoney,sum(if(csd.settlement_type=1,csd.received_sub_total_amount,0)) receiptFROM  customer_settlement_detail  csdLEFT JOIN user uu ON FIND_IN_SET(uu.id,csd.sale_men_id)WHERE csd.is_deleted  = 0<if test="query.startDate != null and query.startDate != '' and query.endDate != null and query.endDate != ''">and csd.detail_date <![CDATA[ <= ]]> #{query.endDate}and csd.detail_date <![CDATA[ >= ]]> #{query.startDate}</if>GROUP BY uu.id) settle ON settle.id = uu.idLEFT JOIN (SELECT salemanId, sum(salemanPerformance) saleTotalPerformanceFROM(SELECTsettlement.id as settlementId,detail.sale_men_id as salemanId,(SUM(CASEWHEN detail.settlement_type = 1 THEN +received_sub_total_amountWHEN detail.settlement_type IN(2,5) THEN -prod_sub_totalWHEN detail.settlement_type IN(3,4) THEN +prod_sub_totalELSE 0END) / (current_period_amount - last_period_amount - discount_amount) * discount_amount+SUM(CASEWHEN detail.settlement_type = 1 THEN +received_sub_total_amountWHEN detail.settlement_type IN(2,5) THEN -prod_sub_totalWHEN detail.settlement_type IN(3,4) THEN +prod_sub_totalELSE 0END)) as salemanPerformanceFROM customer_settlement settlementLEFT JOIN customer_settlement_detail detailON settlement.id = detail.settlement_idWHERE detail.is_settle = 1 AND date_format(settlement_date,'%Y-%m-%d') <![CDATA[ <= ]]> date_format(#{query.yestodayDate},'%Y-%m-%d')GROUP BY settlement.id, detail.sale_men_id) salemanPerformanceGROUP BY salemanId) completeSettle on completeSettle.salemanId = uu.idLEFT JOIN (SELECTsale_men_id as salemanId,SUM(CASEWHEN detail.settlement_type = 1 THEN +received_sub_total_amountWHEN detail.settlement_type IN(2,5) THEN -prod_sub_totalWHEN detail.settlement_type IN(3,4) THEN +prod_sub_totalELSE 0END) as amountFROM customer_settlement_detail detailWHERE is_settle = 0 AND date_format(detail_date,'%Y-%m-%d') <![CDATA[ <= ]]> date_format(#{query.yestodayDate},'%Y-%m-%d')GROUP BY sale_men_id) noCompleteSettle on noCompleteSettle.salemanId = uu.idLEFT JOIN (SELECT uu.id,SUM(uro.received_amount) received_amountFROM  received_order  uroLEFT JOIN user uu ON FIND_IN_SET(uu.id,uro.salesman_id)WHERE uro.is_deleted  = 0 AND uro.pay_collection_type = 0 AND uro.received_status = 1<if test="query.startDate != null and query.startDate != '' and query.endDate != null and query.endDate != ''">and uro.received_date <![CDATA[ <= ]]> #{query.endDate}and uro.received_date <![CDATA[ >= ]]> #{query.startDate}</if>GROUP BY uu.id) receive ON receive.id = uu.id<where><if test="query.deptQuery !=null and query.deptQuery != ''">ud.dept_name like concat('%',#{query.deptQuery},'%')</if><if test="query.regionQuery !=null and query.regionQuery != ''">and area.province_name like concat('%',#{query.regionQuery},'%')</if><if test="query.postQuery !=null and query.postQuery != ''">and up.post_name like concat('%',#{query.postQuery},'%')</if><if test="query.salemanQuery !=null and query.salemanQuery != ''">and uu.real_name like concat('%',#{query.salemanQuery},'%')</if></where>GROUP BY uu.id,uu.real_name,up.post_name,ud.dept_name,area.province_name,sale.count,target.sales_target,settle.deliverNum,settle.deliverMoney,settle.returnNum,settle.returnMoney,settle.receipt,receive.received_amount,completeSettle.saleTotalPerformance,noCompleteSettle.amountORDER BY completeNum  DESC
欢迎大家留言,以便于后面的人更快解决问题!另外亦欢迎大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/839525.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java大作业5-6次总结

第一次迭代总体来说不难分析。它每个控制器和受控制器都是独立的,设计完之后就可以像拼积木那样来完成项目。感觉是封装思想的经典例题。 在进行类的设计时,由于题目中描述的类的实现采用的是元器件拼音首字母,因此为了方便记忆,我也使用了对应的拼音来定义类。这在实际使用…

一文梳理获取本地IP和远程IP的各种方式,附Python代码实例

本地 IP 是指设备在本地网络(如家庭、办公室局域网)中被分配的IP地址,用于在该局域网内设备之间的通信和识别,一般由路由器分配。远程 IP 则是互联网上其它网络或服务器的 IP 地址,用于设备与外部网络通信,如访问网站时的服务器 IP。 获取本地IP地址 1. 系统设置查看其中…

程序设计实验4

任务1 task1_1.cpp1 #include <iostream>2 3 using std::cout;4 using std::endl;5 6 // 类A的定义7 class A {8 public:9 A(int x0, int y0); 10 void display() const; 11 12 private: 13 int x, y; 14 }; 15 16 A::A(int x0, int y0): x{x0}, y{y0} { 1…

2024 Notepad++最新版官网中文版与安装教程

前言 Notepad (记事本)是一个简单的文本编辑器,预装在所有版本的 Microsoft Windows 操作系统中。它的主要功能是创建、编辑和存储纯文本文件,通常以 .txt 格式保存。Notepad 的设计旨在提供一个轻量级的文本处理工具,适合快速编辑和查看文本内容。 基于 Notepad,Notepad…

宝塔面板MySQL无法启动的解决办法

如果你也使用宝塔面板,并且恰好遇到 MySQL 无法启动的情况,那么我猜你大概率是使用了宝塔面板中 MySQL 管理中的“性能调整”功能,因为子凡我已经在这里栽跟头好几次了,今天就简单的给大家分享一下解决办法。宝塔面板 MySQL 数据库无法启动或重启失败的主要问题就是由于 qu…

Computer Vision

Computer Vision https://zhuanlan.zhihu.com/p/444208711.1 概念 计算机视觉(Computer Vision)是研究计算机如何像人类视觉系统 一样,从数字图像或视频中理解其高层内涵的一门学科,简言之就是研究如何让计算机看懂世界,由于它包括对数字图像或视频进行预处理、特征提取、特…

学习笔记(四十二):自定义组件@BuilderParam装饰器

概述: 该装饰器用于声明任意UI描述的一个元素,类似slot占位符。使用示例: 1、初始化@BuilderParam装饰的方法// 自定义组件 @Component export struct CommonView{@Builder customBuilder() {}; // 当前组件@BuilderParam customBuilderParam: () => void = this.customB…

Python创建虚拟环境报错:Error: Command......

目录环境说明问题描述原因分析解决方法 环境说明系统# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.4 LTS Release: 22.04 Codename: jammyPython版本# python3 --version Python 3.13.0问题描述 使用 ve…

使用Redis来实现在线人数的查看

使用Redis来实现在线人数的查看在学习程序员老罗的easylive项目当中,遇到了一个对我现阶段来说很有意思的功能,那就是实现在线人数查看的功能,我第一次接触到这个功能是在学习WebServlet的监听器的时候.那时候是使用Listener监听器的Session事件和Context的生命周期来实现这个功…

PDF 转 HTML API 数据接口

PDF 转 HTML API 数据接口 文件处理 / PDF 高效 PDF 转 HTML 工具 生成HTML站点 / 可永久存储。1. 产品功能高效处理大文件; 支持多语言字符识别; 支持 formdata 格式 PDF 文件流传参; 输出文件永久 CDN 存储; 输出可直接访问的 HTML 站点; 全接口支持 HTTPS(TLS v1.0 / …

圆形直流电(DC)接口和供电特点

、 l定义:圆形接口是一种具有圆形外形的电源接口,通常由插头和插座两部分组成,用于设备之间的电能传输。l特点:结构简单:由金属或塑料制成的外壳包裹内部的引脚或接触子,形状紧凑,易于安装和连接。耐用性强:插头和插座常采用耐磨损的材料制成,能够经受长期插拔和重复使…

抖音自动识别视频标题描述进行点赞评论脚本2020.11.22

抖音自动识别视频标题描述进行点赞评论脚本2020.11.22该 python 脚本可自动实现,针对抖音网页版,识别短视频的标题和描述内容,符合预设题材,自动点赞、评论、关注、收藏。 已绕过检测,可以稳定运行。 可用于广告宣传,刷粉丝等情景。需要的联系抖音账号:太阳不晒晚睡不困…