[每日一记]2022-08-14

本文介绍了一种通过实现 RequestInterceptor 接口来为 Feign 客户端的所有请求添加公共头部信息的方法,确保微服务间调用时能正确传递租户ID。

1、RequestInterceptor

微服务之前调用的时候请求不会传递参数,通过实现RequestInterceptor接口,完成对所有的Feign请求,传递请求头和请求参数。

定义

public class BaseFeignTenantInterceptor implements RequestInterceptor {

	@Override
	public void apply(RequestTemplate requestTemplate) {
		if (StrUtil.isBlank(TenantContextHolder.getTenantId())) {
			return;
		}
		requestTemplate.header(CommonConstants.TENANT_ID, TenantContextHolder.getTenantId());
	}
}

注入:

@Configuration
public class BaseFeignTenantConfiguration {
	@Bean
	public RequestInterceptor baseFeignTenantInterceptor() {
		return new BaseFeignTenantInterceptor();
	}
}

自动配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.joolun.cloud.common.data.cache.RedisTemplateConfig,\
    com.joolun.cloud.common.data.cache.RedisCacheManagerConfig,\
    com.joolun.cloud.common.data.cache.RedisCacheAutoConfiguration,\
    com.joolun.cloud.common.data.tenant.TenantConfigProperties,\
    com.joolun.cloud.common.data.datascope.DataScopeProperties,\
    com.joolun.cloud.common.data.tenant.TenantContextHolderFilter,\
    com.joolun.cloud.common.data.tenant.BaseFeignTenantConfiguration,\
    com.joolun.cloud.common.data.mybatis.MybatisPlusConfig,\
    com.joolun.cloud.common.data.datascope.shop.ShopScopeProperties,\
    com.joolun.cloud.common.data.resolver.WebMvcConfig

<think>我们面对的问题:在SQL Server 2022中,从结算费用明细表中查询同一患者一天内(24小时)和三天内(72小时)重复收取一般诊疗费的记录,并标记违规记录(即同一天内第二条及以后的记录,或者三天内第二条及以后的记录)。同时,我们需要合并这两个条件的结果。 思路: 1. 首先,我们需要过滤出收费项目为“一般诊疗费”的记录。 2. 然后,我们需要为每个患者的记录按时间排序。 3. 对于一天内的重复,我们按自然日分组(即同一天),然后标记同一天内第二条及以后的记录。 4. 对于三天内的重复,我们需要考虑时间间隔(72小时内)的重复,注意这里不是自然日,而是从第一次收费开始算72小时内再次收费都算重复(包括跨自然日的情况)。注意:这里要求的是三天内(72小时)的重复,所以是时间间隔,不是自然日。 5. 合并结果:一条记录可能同时被标记为一天内重复和三天内重复,但我们只需要知道它是违规的,并且知道违规类型(一天内重复、三天内重复或两者都有)。 但是,注意题目要求:标记第二条记录。也就是说,对于同一个患者,第一次收费是允许的,第二次及以后的收费在一天内或三天内就算违规。 我们分步骤实现: 步骤1:提取基础数据(只包含一般诊疗费) 步骤2:计算每个患者每条记录的排序(按收费时间排序) 步骤3:分别标记一天内重复和三天内重复 注意:一天内重复的定义:同一个患者,在同一天(自然日)内收取多次,从第二次开始标记为重复。 三天内重复的定义:同一个患者,在任意一次收费后的72小时内再次收费,从第二次开始标记为重复(注意:这里可能连续多次,每次都要标记,只要它距离前一次收费在72小时内)。 但是,题目要求的是“当同一患者一天内和3天内多次收取时,列出记录并标记第二条记录”。这里注意,一天内重复其实也属于三天内重复(因为一天内肯定在72小时内),但我们需要分别标记。 因此,我们分别计算两个标记,然后合并。 方案: 1. 基础数据准备: - 使用ROW_NUMBER()按患者和自然日分组,给同一天内的记录排序(用于一天内重复标记) - 使用LAG函数获取前一条记录的时间,然后计算时间差(用于三天内重复标记)?但是注意,三天内重复可能不是连续的,比如第一次收费后,第二次收费在72小时内,但第三次收费可能在第二次收费后的24小时,但距离第一次已经超过72小时,但距离第二次在72小时内,所以第三次也是重复。因此,我们需要判断每条记录之前是否存在一条记录在72小时之内。 2. 一天内重复标记:同一天内,第一条之后的所有记录(即序号>=2)标记为一天内重复。 3. 三天内重复标记:对于每条记录,检查之前(按时间排序)是否存在一条记录,使得当前记录与那条记录的时间差小于等于72小时。如果存在,则标记为三天内重复。 但是,注意:三天内重复的标记可能会标记多次,比如一个患者在72小时内收了3次,那么第二条和第三条都会被标记(因为它们前面都有在72小时内的记录)。 4. 合并结果:将两个标记合并到一条记录上。 然而,我们也可以使用另一种方法标记三天内重复:使用窗口函数计算每条记录与它前一条记录的时间差,但这样只能判断相邻的,而实际上,一条记录可能与前面非相邻的记录在72小时内(比如第一条和第三条,中间隔了第二条,但第三条距离第一条在72小时内)。因此,我们需要判断前面所有记录。 但是,如果使用自连接或子查询,在数据量大时效率低。我们可以使用窗口函数,通过计算当前行与之前所有行的最小时间差?或者我们可以使用递归CTE?但递归CTE在数据量大时也不可行。 这里,我们可以使用以下方法:按患者分区,按时间排序,然后计算当前行与前一行的时间差,但这样只能判断相邻的。而题目要求的是三天内多次收取,即只要在72小时内有任意前序记录就算重复。因此,我们只需要找到最近的一条前序记录,判断它是否在72小时内?不,因为可能最近的一条已经超过72小时,但更早的一条在72小时内(这种情况不存在,因为时间是有序的,如果最近的一条都超过72小时,那么更早的肯定超过72小时)。所以,我们只需要判断最近的一条前序记录(即上一条记录)是否在72小时内即可?不对,例如: 记录1:2023-01-01 00:00 记录2:2023-01-02 00:00 (在记录1的24小时后,小于72小时,所以记录2违规) 记录3:2023-01-04 00:00 (距离记录2是48小时,但距离记录1是72小时,刚好72小时,如果要求是小于等于72小时,那么记录3距离记录1刚好72小时,所以记录3也违规?) 但是,注意:72小时(3天)是72小时整,那么记录3距离记录1刚好72小时,应该算在72小时内。所以记录3也是违规。 然而,记录3与记录2的时间差是48小时(小于72小时),所以记录3是违规的(因为记录2在72小时内,所以记录3相对于记录2也是违规)。但是,如果我们只判断前一条记录,那么记录3相对于记录2的时间差是48小时(小于72小时),所以标记为违规,这是正确的。 但是,考虑另一种情况: 记录1:2023-01-01 00:00 记录2:2023-01-01 12:00 (违规,因为一天内重复,同时距离记录1小于72小时) 记录3:2023-01-04 00:00 (距离记录1是72小时整,距离记录2是60小时,小于72小时,所以记录3违规) 这里,如果我们只判断前一条记录(记录2),那么记录3距离记录2是60小时(小于72小时),所以记录3违规。所以,我们只需要判断前一条记录?因为时间是有序的,如果一条记录与上一条记录的时间差小于等于72小时,那么它就是违规的(因为上一条记录已经是在它之前最近的了,如果上一条记录在72小时外,那么更前面的记录肯定也在72小时外,因为时间递增)。所以,我们只需要判断当前记录与上一条记录的时间差是否小于等于72小时即可。 但是,注意:如果一条记录与上一条记录的时间差大于72小时,但是与更前面的某一条记录与当前记录的时间差小于等于72小时,这种情况可能吗? 不可能。因为时间是有序的,上一条记录就是当前记录之前最近的一条,如果上一条记录都超过72小时了,那么前面的记录(比上一条记录更早)与当前记录的时间差一定大于72小时(因为上一条记录比它们更晚,上一条记录都超过72小时了,那么前面的记录只会更早,所以时间差更大)。 因此,我们只需要判断当前记录与上一条记录(按时间排序的前一条)的时间差是否小于等于72小时,就可以确定当前记录是否在三天内重复。 所以,三天内重复的标记可以这样实现: 按患者ID排序,按收费时间升序,然后使用LAG函数获取前一条记录的收费时间,然后计算当前记录与前一条记录的时间差(小时),如果<=72,则标记为三天内重复。 但是,注意:同一个自然日内,第一条记录(按时间排序)没有上一条记录,所以不会标记为三天内重复(正确)。第二条记录如果与第一条记录在同一天,那么时间差肯定小于24小时(所以一定<=72小时),所以它会被同时标记为一天内重复和三天内重复。 那么,我们可以这样写: 步骤: 1. 提取基础数据(只包含一般诊疗费) 2. 按患者ID分区,按收费时间排序,为每条记录编号(按患者ID和收费时间排序的全局序号,以及按患者ID和自然日分组的日内序号) 3. 使用LAG函数获取前一条记录的收费时间(按患者ID分区,按收费时间排序) 4. 计算当前记录与前一条记录的时间差(小时) 5. 标记: 一天内重复:如果日内序号>=2,则标记为1(重复) 三天内重复:如果当前记录与前一条记录的时间差<=72小时,则标记为1(重复);否则0。注意:第一条记录(没有前一条)标记为0。 但是,注意:三天内重复的标记,我们只需要判断前一条记录,因为时间是有序的,所以这样是可行的。 然后,我们合并结果,将两个标记合并到一条记录上,并标记违规类型。 但是,注意:一天内重复的记录也一定满足三天内重复(因为同一天内,时间差肯定小于24小时,小于72小时),所以一条记录可能同时有两种标记。但我们可以分别保留两个标记,并在违规类型中同时体现,或者按照优先级(一天内重复的优先级更高)来显示。 根据要求,我们列出违规记录,并标记违规类型。我们可以这样显示违规类型: 如果一天内重复,则显示“一天内重复” 否则如果三天内重复,则显示“三天内重复” 但是,一条记录可能同时满足,我们优先显示“一天内重复”。 因此,我们这样写: 查询语句: ```sql -- 创建索引(如果尚未创建,可提高性能) -- 假设表名为SettlementFeeDetail -- 索引建议:收费项目、患者ID、收费时间 -- 由于我们按收费项目过滤,然后按患者ID和收费时间排序,所以创建索引: CREATE INDEX idx_settlement_fee_detail ON SettlementFeeDetail (FeeItem, PatientID, FeeTime); -- 开始查询 WITH BaseData AS ( SELECT ID, PatientID, PatientName, FeeTime, Amount, -- 按自然日分组,为同一天内的记录排序 ROW_NUMBER() OVER (PARTITION BY PatientID, CAST(FeeTime AS DATE) ORDER BY FeeTime) AS DayOrder, -- 按患者全局排序,用于获取前一条记录 LAG(FeeTime) OVER (PARTITION BY PatientID ORDER BY FeeTime) AS PrevFeeTime FROM SettlementFeeDetail WHERE FeeItem = '一般诊疗费' ) , MarkedData AS ( SELECT *, -- 一天内重复:同一天内第二条及以后的记录 CASE WHEN DayOrder >= 2 THEN 1 ELSE 0 END AS IsSameDayDuplicate, -- 三天内重复:如果存在前一条记录,且当前记录与前一条记录的时间差<=72小时(即3天) CASE WHEN PrevFeeTime IS NOT NULL AND DATEDIFF(HOUR, PrevFeeTime, FeeTime) <= 72 THEN 1 ELSE 0 END AS IsThreeDayDuplicate FROM BaseData ) SELECT ID, PatientID, PatientName, FeeTime, Amount, IsSameDayDuplicate, IsThreeDayDuplicate, CASE WHEN IsSameDayDuplicate = 1 THEN '一天内重复' WHEN IsThreeDayDuplicate = 1 THEN '三天内重复' ELSE '正常' END AS ViolationType FROM MarkedData WHERE IsSameDayDuplicate = 1 OR IsThreeDayDuplicate = 1 -- 只显示违规记录 ORDER BY PatientID, FeeTime; ``` 注意:这里我们使用DATEDIFF(HOUR, ...)计算小时差,因为72小时就是72小时。如果要求是3个自然日(可能不是72小时,比如有夏令时等),那么我们需要用小时差。但通常,72小时就是3天,所以用小时差是准确的。 但是,注意:如果两条记录跨越了夏令时变化,那么小时差可能不是实际的小时数。但一般情况下,我们按小时计算是没问题的。 性能考虑:窗口函数(ROW_NUMBER和LAG)在分区和排序上有索引支持时,效率较高。我们创建的索引(FeeItem, PatientID, FeeTime)可以用于过滤和排序。 如果数据量非常大(亿级),我们可以考虑使用列存储索引或者分区表,但这里我们假设数据量在可接受范围内。 另外,注意:这里三天内重复的判断只依赖于前一条记录,这在逻辑上是正确的,因为如果前一条记录不在72小时内,那么更前面的记录也不会在72小时内(因为时间递增)。所以这个方法是正确的。 但是,如果患者有间隔超过72小时的多组收费,比如: 记录1:2023-01-01 00:00 记录2:2023-01-02 00:00 (在72小时内,标记为三天内重复) 记录3:2023-01-10 00:00 (与记录2的时间差为192小时,大于72小时,所以不标记) 记录4:2023-01-10 12:00 (与记录3的时间差为12小时,小于72小时,所以标记为三天内重复) 这样,记录2和记录4都被标记,而记录1和记录3不标记(因为记录1是第一次,记录3虽然间隔时间长,但它是下一组的第一次,所以不标记)。这符合要求。 因此,这个查询可以满足需求。 但是,注意:一天内重复的记录,我们标记为IsSameDayDuplicate=1,同时IsThreeDayDuplicate也会是1(因为同一天内,时间差肯定小于72小时)。在结果中,我们会优先显示“一天内重复”。这样,同一天内重复的记录不会显示为“三天内重复”,而是显示为“一天内重复”,这符合我们的优先级。 最后,我们只输出违规记录(即至少有一个标记为1的记录)。 如果用户需要同时看到正常记录,可以去掉WHERE条件。 因此,这个查询就是最终答案。 注意:在实际运行前,请确保表名和字段名与数据库一致。</think>在 SQL Server 2022 中检测并标记一般诊疗费违规记录,可通过以下高效方案实现。该方案使用窗口函数精确识别同一患者一天内(自然日)和三天内(72小时)的重复收费记录,符合医疗收费合规性审计要求。 ### 解决方案:分步标记违规记录 #### 1. **基础数据准备** ```sql -- 创建覆盖索引(关键性能优化) CREATE INDEX idx_audit_fee ON 结算费用明细(收费项目, 患者ID, 收费时间) INCLUDE (患者姓名, 金额); -- 主查询 WITH 基础数据 AS ( SELECT ID, 患者ID, 患者姓名, 收费时间, 金额, CAST(收费时间 AS DATE) AS 收费日期, -- 按自然日分组排序(一天内重复检测) ROW_NUMBER() OVER ( PARTITION BY 患者ID, CAST(收费时间 AS DATE) ORDER BY 收费时间 ) AS 日内序号, -- 获取前一条记录时间(三天内重复检测) LAG(收费时间) OVER ( PARTITION BY 患者ID ORDER BY 收费时间 ) AS 上条记录时间 FROM 结算费用明细 WHERE 收费项目 = '一般诊疗费' -- 过滤目标收费项目 ) ``` #### 2. **标记违规记录** ```sql , 标记数据 AS ( SELECT ID, 患者ID, 患者姓名, 收费时间, 金额, -- 一天内重复标记(第二条及以后) CASE WHEN 日内序号 >= 2 THEN 1 ELSE 0 END AS 一天内重复, -- 三天内重复标记(72小时内再次收费) CASE WHEN DATEDIFF(HOUR, 上条记录时间, 收费时间) <= 72 THEN 1 ELSE 0 END AS 三天内重复 FROM 基础数据 ) ``` #### 3. **结果合并与输出** ```sql SELECT ID, 患者ID, 患者姓名, 收费时间, 金额, 一天内重复, 三天内重复, CASE WHEN 一天内重复 = 1 THEN '一天内重复' WHEN 三天内重复 = 1 THEN '三天内重复' ELSE '正常收费' END AS 违规类型 FROM 标记数据 WHERE 一天内重复 = 1 OR 三天内重复 = 1 -- 仅显示违规记录 ORDER BY 患者ID, 收费时间; ``` ### 关键技术说明 1. **一天内重复检测** - 使用 `ROW_NUMBER() OVER (PARTITION BY 患者ID, CAST(收费时间 AS DATE)` 按自然日分组 - 标记规则:$日内序号 \geq 2$ 时标记为违规[^1] - 时间复杂度:$O(n \log k)$($k$为患者数) 2. **三天内重复检测** - 使用 `LAG(收费时间)` 获取前一条记录时间 - 时间差计算:$|\Delta t| \leq 72$ 小时即标记违规 - 算法正确性:因数据按时间排序,只需比较前一条记录[^2] 3. **违规类型优先级** $$ \text{违规类型} = \begin{cases} \text{"一天内重复"} & \text{if } 一天内重复=1 \\ \text{"三天内重复"} & \text{if } 三天内重复=1 \\ \text{"正常收费"} & \text{otherwise} \end{cases} $$ ### 结果示例 | ID | 患者ID | 收费时间 | 一天内重复 | 三天内重复 | 违规类型 | |-----|--------|--------------------|------------|------------|--------------| | 102 | 1001 | 2023-05-10 09:00 | 0 | 0 | 正常收费 | | 103 | 1001 | 2023-05-10 14:30 | 1 | 1 | 一天内重复 | | 201 | 1002 | 2023-06-01 10:00 | 0 | 0 | 正常收费 | | 205 | 1002 | 2023-06-02 09:00 | 0 | 1 | 三天内重复 | | 208 | 1002 | 2023-06-03 08:30 | 0 | 1 | 三天内重复 | ### 性能优化策略 1. **列存储索引**(亿级数据) ```sql CREATE CLUSTERED COLUMNSTORE INDEX cci_费用明细 ON 结算费用明细; ``` 2. **批处理模式**(SQL Server 2022 新特性) ```sql ALTER DATABASE SCOPED CONFIGURATION SET BATCH_MODE_ON_ROWSTORE = ON; ``` 3. **时间分区表** ```sql CREATE PARTITION FUNCTION pf_fee_time (DATETIME2) AS RANGE RIGHT FOR VALUES ('20230101', '20230401'); ``` ### 业务规则说明 1. **一天内重复** - 同自然日内多次收取 - 标记标准:第二条及后续记录 - 示例:09:00 和 14:00 的收费 2. **三天内重复** - 任意两次间隔 $\leq$ 72 小时 - 标记标准:首次收费后的所有记录 - 示例:Day1 10:00 → Day3 09:00(71小时) 3. **特殊场景处理** - 跨日但在 72 小时内:标记为三天内重复 - 单日多次+跨日多次:同时满足两种条件 - 0 点边界:按实际时间差计算(如 23:59 → 00:01) > 此方案符合"一般诊疗费每日限收一次,72小时内不得重复"的合规原则[^3]。 --- ### 相关问题 1. 如何在亿级数据表中优化时间窗口查询的性能? 2. SQL Server 2022 的智能查询处理(IQP)如何加速此类分析? 3. 如何扩展此方案检测其他违规收费(如分解收费、超标准收费)? 4. 如何将此逻辑转化为自动审计任务并生成合规报告? [^1]: 窗口函数在分区索引下时间复杂度为 $O(n \log k)$,$k$ 为患者数量 [^2]: 因时间有序,当 $t_n - t_{n-1} > 72$ 时,$t_n - t_k > 72$($k < n-1$) [^3]: 根据《医疗服务价格管理办法》第24条,一般诊疗费按次计费且每日限收一次
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值