如何解决在不删除表/列的情况下将现有列更改为计算和持久化
我想在不删除表/列的情况下将 sql Server 中的现有列更改为计算和持久化。
我有一个自动递增的 ID
和另一列 ReportID
,它使用此计算进行格式化:
ReportID = 'RPT'+{FiscalYear}+{0s}+{Auto Incremented ID}
例如:
- 对于 ID = 1,2,....,10,11,...,100,101
- ReportID 将是
RPT2122000001
、RPT2122000002
、...、RPT2122000010
、RPT2122000011
、...、RPT2122000101
、RPT2122000102
以前,我们在“插入后”触发器中执行此操作 - 计算值并更新行。但是通过这样做,当负载很高并且报告由不同用户并行生成时,一些 ReportID 会重复。
因此,为了解决这个问题,我想将现有列更改为带有 'RPT'+{FiscalYear}+{0s}+{Auto Incremented ID}
的计算列,但问题是我希望现有数据保持不变。因为如果现在运行计算,所有上一年的数据都会被修改为当前财年,这是错误的。
当我通过在 Management Studio 中设置计算值直接尝试时,它也在内部运行 drop 并在后台添加回来。
我看到了很多答案,但都不够令人满意。
我尝试使用计算值创建一个新列,然后尝试重命名,这也是不允许的。
编辑 1
错误:
Computed column 'ReportID' in table 'Tmp_wp_tra_report_creation' cannot be persisted because the column is non-deterministic
编辑 2
财务年度计算:
(select (case when ((select top 1 a.Pc_FromDate from wp_pc_calendar a where a.Pc_FromDate>=getdate())<=getdate()) then (select top 1 b.Pc_FinYear from wp_pc_calendar b where b.Pc_FromDate>=getdate() order by b.Pc_FromDate asc ) else (case when ((select top 1 c.Pc_FromDate from wp_pc_calendar c where c.Pc_FromDate<=getdate() )<=getdate()) then (select top 1 d.Pc_FinYear from wp_pc_calendar d where d.Pc_FromDate<=getdate() order by d.Pc_FromDate desc ) else 'No Records' end) end) as finyear)
另外,有没有不创建新列的方法?
解决方法
我想通过以下方式解决这个问题:
- 重命名原始列
- 创建新的计算列,该列使用原始列并在原始列没有值(为空)时故障转移到计算。
喜欢:
-- === Setting up some data for testing
drop table if exists test_reports
create table test_reports (
id int identity(1,1),fiscal_year int,report_id varchar(15)
)
insert test_reports (fiscal_year,report_id) values (2122,'RPT2122000001')
insert test_reports (fiscal_year,'RPT2122000002')
insert test_reports (fiscal_year,'RPT2122000010')
insert test_reports (fiscal_year,'RPT2122000011')
insert test_reports (fiscal_year,'RPT2122000101')
insert test_reports (fiscal_year,'RPT2122000102')
-- === Rename the existing column to something else because it will start to
-- === contain nulls and the nulls may break existing code.
exec sp_rename 'test_reports.report_id','original_report_id'
-- === Add our new,computed column that checks the original column
-- === for a value and uses the original value,if available. Otherwise,-- === the computed column is an actual computation.
alter table test_reports add report_id as (
coalesce(original_report_id,'RPT' + convert(varchar,fiscal_year) + right('000000' + convert(varchar,id),6)))
insert test_reports(fiscal_year) values (2123)
select * from test_reports
,
在我的例子中
试试这个,
drop table if exists test_reports
create table test_reports (
id int identity(1,col1 varchar(10),report_id varchar(15)
)
insert test_reports (col1,report_id) values ('çol1','RPT2122000001')
insert test_reports (col1,'RPT2122000002')
insert test_reports (col1,'RPT2122000010')
insert test_reports (col1,'RPT2122000011')
insert test_reports (col1,'RPT2122000101')
insert test_reports (col1,'RPT2122000102')
第一步
,重命名旧列
sp_rename 'test_reports.report_id','Oldreport_id','COLUMN';
第 2 步:获取最大自动递增 id 列值。
第 3 步:
ALTER TABLE test_reports
ADD report_id AS CASE
WHEN id > 6
THEN 'RPT' + CAST(YEAR(GETDATE()) AS VARCHAR) + CAST(id AS VARCHAR)
ELSE Oldreport_id
END;
第 4 步尝试新插入
insert test_reports (col1) values ('çol1')
select * from test_reports
您可以使用自己的会计年度逻辑,它会起作用。
,由于您正在计算财政年度,您可以将财政年度计算放在一个标量函数中并在您的计算列中使用它:
- 用于会计年度计算的函数:
CREATE FUNCTION fiscalyear(@currentdate AS datetime)
RETURNS int
AS BEGIN
@fiscalyear int = <yourlogic here >
RETURN @fiscalyear
end
但是,在您的会计年度计算中,您应该使其依赖于输入参数日期(而不是 getdate()),并且在您的计算列中,您为该行传递诸如 reportdate 或 insertdate 之类的内容。
因为如果您的计算基于 getdate() ,那么明年同一行的计算列的值将更改。这不是你想要的。
同样使用函数会被认为是非确定性的,并且计算列不能被持久化。
然后跟进其他人所说的:
- 重命名旧列:
exec sp_rename 'test_reports.report_id','original_report_id'
- 添加另一列以保存报告日期
alter table tablename
add reportdate datetime not null default (getdate())
您可以为其设置默认值,无需修改插入/更新
- 添加新的计算列:
alter table test_reports add report_id as (
coalesce(original_report_id,dbo.fiscalyear(reportdatecolumn) + right('000000' + convert(varchar,6)))
,
我很久以前就遇到过与您类似的问题,以下解决方案对我有用。
您可能会遇到这个问题,因为在批量插入的情况下,它只会使用上次更新的值更新列的值(对于所有行),因此您会得到重复的值。
考虑到您现有的结构 - 插入触发器后,其中一种方法是继续使用触发器并尝试遍历 INSERTED 表
IF (OBJECT_ID('tempdb..#T') IS NOT NULL)
BEGIN
DROP TABLE #T
END
CREATE TABLE #T (
[Serial] INT IDENTITY(1,[ID] INT,[ReportID] VARCHAR(100) -- data type you are using
)
INSERT INTO #T (
[ID],[ReportID]
)
SELECT [ID],[ReportID]
FROM INSERTED
DECLARE @LoopCounter INT,@MaxId INT,@ID INT,@ReportID VARCHAR(100)
SELECT @LoopCounter = MIN([Serial]),@MaxId = MAX([Serial])
FROM #T
WHILE (
@LoopCounter IS NOT NULL
AND @LoopCounter <= @MaxId
)
BEGIN
SELECT @ID = [ID],@ReportID = [ReportID]
FROM #T
WHERE [Serial] = @LoopCounter
/* write your logic here,you can use @ID to identify your record
ReportID = 'RPT'+{FiscalYear}+{0s}+{Auto Incremented ID}
*/
SELECT @LoopCounter = min([Serial])
FROM #T
WHERE [Serial] > @LoopCounter
END
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。