Sql示例说明如何分组后求中间值--【叶子】

本文介绍了一种SQL查询技巧,用于按指定字段分组并计算每组价格的中间值,通过使用窗口函数和子查询实现复杂的数据处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里所谓的分组后求中间值是个什么概念呢?

我举个例子来说明一下:

假设我们现在有下面这样一个表:

type name price

----------- ---- ------------

2 A 5.4

2 A 3.7

2 B 4.3

1 B 4.7

2 B 6.7

2 B 9.8

3 a 3.0

3 a 4.0

3 a 10.0

3 a 20.0

我们要得到什么样的效果呢?

typename进行分组后求中间值:

是不是就是这样呢:

select [type],name,AVG(price) from @table

group by type,name

/*

type name

----------- ---- ---------------------------------------

2 A 4.550000

3 a 9.250000

1 B 4.700000

2 B 6.933333

*/

不是的,我所说的中间值和平均数还不太一样,平均数就是均值,但是中间值是说分组如果有奇数个就是中间那个,如果有偶数个就是中间两个值的均值。

例如上面数据中3a分组后有341020四个数,那中间值就是410个均值。

例如上面按2B分组后有4.3 和6.7 及9.8三个值,那中间值就是6.7

我们如何能达到这样效果呢,写个简单的例子:

declare @table table ([type] int,name varchar(1),price numeric(3,1))

insert into @table

select 2,'A',5.4 union all

select 2,'A',3.7 union all

select 2,'B',4.3 union all

select 1,'B',4.7 union all

select 2,'B',6.7 union all

select 2,'B',9.8 union all

select 3,'a',3 union all

select 3,'a',4 union all

select 3,'a',10 union all

select 3,'a',20

--sql查询如下:

;with maco as

(

select

[type],name,price,

m_asc=row_number() over(partition by type,name order by price),

m_desc=row_number() over(partition by type,name order by price desc)

from @table

)

--select * from liang

select [type],name,avg(price) as price

from maco

where m_asc in(m_desc,m_desc+1,m_desc-1)

group by type,name

order by type,name

/*

type name price

----------- ---- -----------

1 B 4.700000

2 A 4.550000

2 B 6.700000

3 a 7.000000

*/

<think>好的,我现在需要帮用户修改他们的SQL查询,以满足只返回输入ID的叶子菜单信息,并且包含特定字段以及拼接路径的需。首先,我需要仔细分析用户提供的原始SQL语句,找出问题所在,然后根据他们的新需进行调整。 首先,用户提到的错误是SQL语法错误,特别是第7行附近的VARCHAR使用问题。但查看用户提供的原始SQL,并没有直接出现VARCHAR定义,可能之前的错误已经被修正,或者用户现在提供了正确的查询。不过,根据用户当前的请,他们希望修改现有的SQL,所以可能之前的错误已经解决,现在需要功能上的调整。 接下来,用户的需是让查询只返回输入ID的叶子菜单的信息。也就是说,输入的那个ID本身可能不是叶子节点,但需要找到它的所有子节点中最底层的叶子节点。或者,可能用户希望直接返回该ID对应的叶子节点,假设该ID本身就是叶子节点。需要明确叶子节点的定义,通常是没有子节点的节点。 然后,用户需要返回的字段包括id、parent_id、path、menu_title,以及从根菜单到叶子菜单的路径拼接。这里的menu_title可能是指meta_title,因为原始查询中使用了meta_title字段。需要确认字段名称是否正确。 原始查询使用了递归CTE(WITH RECURSIVE)来遍历菜单路径,从指定的ID开始,向上或向下查找父节点或子节点。当前的CTE是从指定ID开始,向上查找父节点,因为使用了INNER JOIN MenuPathCTE mp ON m.parent_id = mp.id。这会导致递归向上,因为每次递归都是查找当前节点的父节点。但是用户可能需要的是向下查找子节点,直到叶子节点,或者相反,需要明确递归的方向。 用户提到要返回输入ID的叶子菜单的信息,所以可能需要调整递归的方向,确保能够遍历到叶子节点。叶子节点是指没有子节点的节点,因此在递归过程中,需要找到那些没有子节点的节点。 此外,用户希望拼接从根到叶子的路径,而原始查询中的GROUP_CONCAT是按level升序排列,这可能是从根到当前节点的路径。但需要确认递归是否正确构建了从根到叶子的路径,或者是否是从叶子到根的路径。 现在,用户希望修改后的查询只返回叶子节点的信息,即输入ID对应的叶子节点。可能需要添加条件来过滤出叶子节点,例如在最后的WHERE子句中检查是否存在子节点,或者通过递归确保只选择到叶子节点。 另外,用户提到要拼接每层menu_title的第十一个字符到下一个双引号的部分。原始查询中使用了SUBSTRING(meta_title, 11)和SUBSTRING_INDEX来提这部分内容,这可能需要检查是否正确截了所需的字符串部分。 可能的步骤包括: 1. 确保递归CTE正确遍历从根到叶子节点的路径,或者从输入ID到其叶子节点。 2. 修改CTE的递归部分,确保正确连接到父节点或子节点。 3. 添加条件以确定叶子节点(即没有子节点的节点)。 4. 调整GROUP_CONCAT的顺序,确保路径是从根到叶子正确拼接。 5. 确保所有字段名称正确,如menu_title是否应为meta_title。 6. 处理可能的保留字问题,如level是否需要用反引号包裹。 7. 检查字符串截函数是否正确,特别是SUBSTRING和SUBSTRING_INDEX的参数是否正确。 需要验证递归CTE的逻辑是否正确。原始查询中,CTE的初始部分选择id为'03011201'的记录,然后递归部分通过INNER JOIN连接父节点,这意味着递归是向上查找父节点,而不是向下查找子节点。这可能与用户的需不符,因为如果用户想要从根到叶子的路径,可能需要从根节点开始向下递归到叶子节点,或者从当前节点向上到根,再拼接路径。需要明确路径的方向。 如果用户希望从根到叶子节点,而输入的ID是中间的某个节点,可能需要调整CTE的递归逻辑,先找到根节点,然后向下递归到叶子节点。或者,如果输入的ID本身就是叶子节点,那么只需要从该节点向上找到根节点,并拼接路径。需要进一步明确用户的需。 假设用户希望输入一个ID,找到该ID对应的叶子节点(即该节点本身没有子节点),并返回该叶子节点的信息以及从根到该叶子的路径。那么需要确保CTE能够正确构建从根到该叶子的路径,并且该叶子节点确实没有子节点。 可能需要修改递归CTE的起始条件,或者添加过滤条件来确保只选择叶子节点。例如,在递归的终止条件中检查是否存在子节点,或者在最后的查询中排除有子节点的记录。 此外,GROUP_CONCAT可能需要按正确的顺序拼接路径,可能需要按level升序排列,以确保根在前,叶子在后。 总结需要进行的修改步骤: 1. 确保递归CTE正确构建从根到叶子节点的路径。 2. 确定叶子节点的条件(例如,没有子节点)。 3. 调整字符串截逻辑,确保正确提第十一个字符到下一个双引号的内容。 4. 修改最终的SELECT语句,只返回叶子节点的信息,并拼接路径。 5. 检查字段别名是否正确,如menu_title是否应为meta_title的别名。 6. 处理可能的语法问题,如保留字level是否需要转义。 现在,根据用户的示例SQL,可能存在以下问题: - 在WITH RECURSIVE中,初始查询选择了id为'03011201'的记录,然后递归部分连接父节点,这意味着CTE会向上遍历父节点,直到根节点。因此,CTE实际上是从指定ID向上到根节点的路径。但用户可能需要的是从根到该ID的路径,或者从该ID到其叶子节点的路径。 如果用户希望获输入ID的叶子节点,即该ID下的最底层节点,可能需要递归向下查找子节点,直到没有更多子节点为止。因此,递归部分应该连接子节点,而不是父节点。需要将INNER JOIN的条件改为m.parent_id = mp.id,这样会查找当前节点的子节点,但这样会导致递归向下。但原SQL中的递归部分是INNER JOIN MenuPathCTE mp ON m.parent_id = mp.id,这实际上是查找当前节点的父节点,因此递归是向上的,这可能与预期相反。 因此,可能需要调整递归方向,以便向下查找子节点,找到叶子节点。或者,如果用户希望输入的ID本身就是叶子节点,则需要确保该ID没有子节点,并在查询中返回该节点的信息以及从根到它的路径。 另外,在最后的子查询中,使用了ROW_NUMBER() OVER(PARTITION BY id ORDER BY level DESC) as rn,然后选择rn=1,这可能用于选择每个id的最新(最深)层级记录。但用户现在希望只返回输入ID的叶子节点,可能需要调整这个逻辑。 可能的修改方向: - 修改递归CTE,使其从根节点开始,向下递归到叶子节点,或者从输入ID开始,向下递归到其子节点,直到叶子节点。 - 在最终的查询中过滤叶子节点,即那些在sys_menu表中没有子节点的记录(即不存在其他记录的parent_id等于该节点的id)。 - 调整路径拼接的顺序,确保从根到叶子。 另外,用户提到需要返回输入ID的叶子菜单的信息,可能需要明确输入ID是否是叶子节点,或者需要找到其下的所有叶子节点。根据用户的需,可能只需要返回该ID对应的节点本身的信息,假设它是叶子节点,或者需要找到该节点下的所有叶子节点。 假设用户希望输入一个ID,并返回该ID对应的叶子节点(即该节点本身是叶子),那么需要确保该节点没有子节点。或者,如果该ID有子节点,需要递归找到最底层的叶子节点。 可能需要调整CTE的递归部分,以向下查找子节点,直到叶子节点。例如,递归CTE的初始部分选择输入ID,然后递归部分查找所有子节点,直到没有更多子节点。这样,最终的CTE将包含从输入ID到其所有叶子节点的路径。然后,在最后的查询中,选择那些在sys_menu表中没有子节点的节点作为叶子节点。 但根据用户当前的SQL,CTE是向上查找父节点,这可能与需不符。因此,需要调整递归方向。 综上,可能需要将递归条件改为查找子节点,而不是父节点。即,将INNER JOIN MenuPathCTE mp ON m.parent_id = mp.id改为m.parent_id = mp.id,这实际上会查找mp的子节点。但这样递归会向下扩展,直到没有子节点为止。 但原SQL中的递归部分已经是这样,所以可能需要检查是否正确。例如,初始部分选择id='03011201',然后递归部分查找其子节点,直到叶子节点。但根据原始查询中的递归条件,可能实际是向上查找父节点,因为递归部分是从子节点到父节点。例如,m.parent_id = mp.id,意味着查找m的父节点是mp的id,即mp是父节点,m是子节点。因此,递归部分实际上是向上遍历父节点,而不是向下遍历子节点。 这可能与用户的需相反。如果用户希望从输入ID向下遍历到叶子节点,递归条件应该是mp.parent_id = m.id,这样每次递归都查找当前节点的子节点。但需要仔细分析递归CTE的结构。 例如,正确的向下递归CTE结构应该是: WITH RECURSIVE cte AS ( SELECT * FROM sys_menu WHERE id = 'start_id' UNION ALL SELECT m.* FROM sys_menu m JOIN cte ON m.parent_id = cte.id ) 这将从start_id开始,递归查找所有子节点。因此,递归条件是m.parent_id = cte.id,即查找cte节点的子节点。 但在用户提供的SQL中,递归部分是: INNER JOIN MenuPathCTE mp ON m.parent_id = mp.id 这意味着,m的parent_id等于mp的id,即mp是m的父节点,因此每次递归都在查找父节点,导致向上遍历。因此,CTE实际上是从输入ID向上遍历到根节点,而不是向下到叶子节点。这可能与用户的需不符。 因此,用户可能需要调整递归条件,以向下遍历子节点。这可能是导致无法获叶子节点的原因。 因此,修改递归部分的条件为m.parent_id = mp.id,即查找mp的子节点,这样递归会向下进行。但需要确认初始查询是否正确。 例如,初始查询选择id='03011201',然后递归部分查找所有m.parent_id等于该id的记录,即该id的子节点。然后递归继续查找这些子节点的子节点,直到没有更多子节点。这样,CTE将包含从输入ID开始的所有子节点路径,直到叶子节点。 但用户的需是返回输入ID的叶子菜单的信息,即输入ID本身可能不是叶子,而是需要找到其下的叶子节点。因此,需要确保CTE遍历到所有子节点,直到叶子节点,然后在最终查询中筛选出叶子节点。 然后,在最终查询中,需要确定哪些节点是叶子节点,即没有子节点的节点。可以通过LEFT JOIN sys_menu检查是否存在子节点: WHERE NOT EXISTS (SELECT 1 FROM sys_menu m2 WHERE m2.parent_id = tmp.id) 因此,综合以上分析,修改步骤可能包括: 1. 调整递归CTE的递归部分,使其向下遍历子节点。 2. 在最终查询中添加叶子节点的判断条件。 3. 确保路径拼接正确,从根到叶子。 但用户当前的递归CTE是向上遍历父节点,这可能与需相反。因此,需要调整递归方向。 此外,用户提到“从根菜单到叶子菜单中每层menu_title的第十一个字符到下一个”的拼接,因此路径需要从根开始,到叶子结束。如果递归是向上遍历父节点,那么路径的顺序会是叶子到根,需要反转。例如,在GROUP_CONCAT中使用ORDER BY level DESC,然后反转顺序,或者调整CTE中的level计算方式。 因此,可能需要调整递归的方向,或者调整路径拼接的顺序。 现在,根据用户的需,他们希望输入一个ID,返回该ID对应的叶子节点的信息,以及从根到该叶子的路径。假设输入ID是某个中间节点,其下存在叶子节点,或者输入ID本身就是叶子节点。 可能的解决方案: 1. 修改递归CTE,使其从输入ID开始,向下遍历所有子节点,直到叶子节点。 2. 在最终查询中,筛选出叶子节点(即没有子节点的节点)。 3. 拼接路径时,确保从根到叶子的顺序。 但根据用户当前的SQL,递归是向上遍历,因此可能需要调整递归条件。或者,用户可能希望从输入ID向上到根,然后拼接路径从根到输入ID,这可能与需不符。 需要进一步明确用户的需。如果用户希望从根到叶子节点的完整路径,而输入ID是叶子节点本身,那么递归向上到根,然后拼接路径即可。但需要确保路径顺序正确。 现在,回到用户提供的原始SQL,他们的CTE是从输入ID开始,递归向上到根节点。然后,在最终查询中,使用GROUP_CONCAT按level升序排列,这将从根到输入ID拼接路径。但用户现在希望返回输入ID的叶子节点的信息,即该ID本身是叶子,或者需要找到其下的叶子节点。 如果输入ID是叶子节点,那么递归向上到根,拼接路径即可。此时,修改原始SQL,确保在最终查询中只返回该叶子节点的信息,并拼接路径。 但用户可能需要输入ID不是叶子节点,而是希望找到其下的所有叶子节点,并返回这些叶子的信息及路径。 这可能需要不同的方法,例如: 1. 递归查找输入ID下的所有子节点,直到叶子节点。 2. 对每个叶子节点,递归查找其到输入ID的路径(或到根),然后拼接。 但这样可能需要更复杂的CTE结构。 可能用户当前的查询结构是用于获从输入ID到根的路径,而现在需要的是从根到输入ID(作为叶子)的路径,或者输入ID下的叶子节点的路径。 需要更多的信息,但根据用户当前的查询和需描述,假设输入ID是叶子节点,需要返回其信息及从根到它的路径。 在这种情况下,原始SQL已经可以获从输入ID到根的路径,但路径拼接顺序可能需要调整。例如,GROUP_CONCAT按level ASC排序,即从根到叶子,这可能正确。但需要确保递归CTE中的level计算正确。 在原始CTE中,初始查询的level是0,然后每次递归加1,当向上遍历父节点时,level实际上是深度从叶子到根递增。例如,输入ID是level 0,其父节点是level 1,依此类推,直到根节点是最高level。因此,按level ASC排序会将根节点(最高level)放在最后,导致路径顺序是叶子到根。这可能与用户期望的相反。 因此,需要调整level的计算方式,或者在GROUP_CONCAT时使用ORDER BY level DESC,以逆转顺序。 例如,在原始CTE中,初始查询的level是0,每次递归加1,当向上遍历父节点时,level表示距离叶子节点的层级数。因此,根节点会有最大的level值。在拼接路径时,按level DESC排序,可以将根节点放在前面,叶子节点放在后面,形成正确的路径顺序。 因此,修改GROUP_CONCAT的ORDER BY子句为DESC,可能解决路径顺序问题。 此外,用户需要确保只返回输入ID对应的叶子节点,即该节点本身是叶子。因此,在最终查询中,可能需要过滤掉存在子节点的记录。 或者,如果输入ID不是叶子,需要找到其下的所有叶子节点,并返回它们的路径。这种情况下,需要调整递归CTE以向下遍历子节点,直到叶子节点,并为每个叶子节点拼接从根到它的路径。 这可能需要更复杂的处理,例如,首先找到所有叶子节点,然后为每个叶子节点生成从根到它的路径。 但根据用户当前的查询结构,可能更适合处理输入ID作为叶子节点的情况。 现在,根据用户提供的修改后的需,需要调整原始查询以: 1. 只返回输入ID的叶子菜单的信息(可能输入ID本身是叶子)。 2. 包括id, parent_id, path, menu_title(可能是meta_title的别名)。 3. 拼接从根到叶子每层menu_title的第十一个字符到下一个双引号的字符串。 可能的修改步骤: 1. 确保递归CTE正确获从根到输入ID的路径。 2. 在最终查询中筛选出叶子节点(即输入ID本身,假设它是叶子)。 3. 调整路径拼接的顺序为根到叶子。 4. 确保字段别名正确,如menu_title。 在原始查询中,最终的SELECT语句已经使用了GROUP_CONCAT按level ASC排序,但可能因为level的计算方式导致顺序错误。如果递归向上遍历父节点,level从0递增,根节点的level最大,按ASC排序会先显示输入ID(level 0),然后是其父节点(level 1),依此类推,导致路径顺序是叶子到根。因此,需要改为ORDER BY level DESC。 此外,需要确保只返回输入ID对应的记录,假设它是叶子节点。或者在递归CTE中,确保遍历到叶子节点。 可能的修改后的SQL如下: ```sql WITH RECURSIVE MenuPathCTE AS ( SELECT id, parent_id, path, meta_title, 0 AS level, --第十一字符到下一个双引号的内容 CASE WHEN INSTR(SUBSTRING(meta_title, 11), '"') > 0 THEN SUBSTRING_INDEX(SUBSTRING(meta_title, 11), '"', 1) ELSE SUBSTRING(meta_title, 11) END AS title_part FROM sys_menu WHERE id = '03011201' -- 输入的目标ID UNION ALL -- 向上递归查询父级节点 SELECT m.id, m.parent_id, m.path, m.meta_title, mp.level + 1, -- 层级递增 CASE WHEN INSTR(SUBSTRING(m.meta_title, 11), '"') > 0 THEN SUBSTRING_INDEX(SUBSTRING(m.meta_title, 11), '"', 1) ELSE SUBSTRING(m.meta_title, 11) END FROM sys_menu m INNER JOIN MenuPathCTE mp ON m.id = mp.parent_id -- 关键修改:通过parent_id反向连接 ) SELECT base.id, base.parent_id, base.path, base.meta_title AS menu_title, -- 别名修正 -- 路径按从根到叶的顺序拼接 GROUP_CONCAT( path_order.title_part ORDER BY path_order.level DESC -- 反转层级顺序 SEPARATOR ' -> ' ) AS full_path FROM ( SELECT *, -- 标识每个节点的最终层级(最深层级) ROW_NUMBER() OVER( PARTITION BY id ORDER BY level DESC ) as rn FROM MenuPathCTE ) AS base -- 只保留叶子节点(假设输入ID是叶子节点) LEFT JOIN sys_menu leaf_check ON base.id = leaf_check.parent_id WHERE base.rn = 1 -- 最深层级 AND leaf_check.id IS NULL -- 确保没有子节点(叶子节点条件) GROUP BY base.id, base.parent_id, base.path, base.meta_title; ``` 修改说明: 1. **递归方向修正**: - 将递归连接条件改为`m.id = mp.parent_id`,实现从子
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值