在不删除 table/column 的情况下将现有列更改为计算和持久化
Change an Existing Column to Computed and Persisted without dropping the table/column
我想将 SQL 服务器中的现有列更改为计算和持久化而不删除 table/column。
我有一个自动递增的 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 中设置计算值时,它也在内部 运行 在后台删除并添加回来。
我看到了很多答案,但它们不够令人满意。
我尝试用计算值创建一个新列,然后尝试重命名,这也是不允许的。
编辑 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, report_id) values (2122, 'RPT2122000002')
insert test_reports (fiscal_year, report_id) values (2122, 'RPT2122000010')
insert test_reports (fiscal_year, report_id) values (2122, 'RPT2122000011')
insert test_reports (fiscal_year, report_id) values (2122, 'RPT2122000101')
insert test_reports (fiscal_year, report_id) values (2122, '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, 1),
col1 varchar(10),
report_id varchar(15)
)
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000001')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000002')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000010')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000011')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000101')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000102')
Step 1
,重命名旧列
sp_rename 'test_reports.report_id', 'Oldreport_id', 'COLUMN';
Step 2 : Get max auto incremented id column value.
Step 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;
Step 4 Try new insert
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())
您可以为其设置默认值,并且 inserty/update 无需修改
- 添加新的计算列:
alter table test_reports add report_id as (
coalesce(original_report_id,
'RPT' + convert(varchar,dbo.fiscalyear(reportdatecolumn) + right('000000' + convert(varchar, id), 6)))
我很久以前遇到过与您类似的问题,下面的解决方案对我有用。
您可能会遇到此问题,因为在批量插入的情况下,它只会使用上次更新的值更新列(对于所有行)的值,因此您会得到重复的值。
考虑到您现有的结构 - 在插入触发器之后,一种方法是继续使用触发器并尝试遍历 INSERTED table
IF (OBJECT_ID('tempdb..#T') IS NOT NULL)
BEGIN
DROP TABLE #T
END
CREATE TABLE #T (
[Serial] INT IDENTITY(1, 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
我想将 SQL 服务器中的现有列更改为计算和持久化而不删除 table/column。
我有一个自动递增的 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 中设置计算值时,它也在内部 运行 在后台删除并添加回来。
我看到了很多答案,但它们不够令人满意。
我尝试用计算值创建一个新列,然后尝试重命名,这也是不允许的。
编辑 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, report_id) values (2122, 'RPT2122000002')
insert test_reports (fiscal_year, report_id) values (2122, 'RPT2122000010')
insert test_reports (fiscal_year, report_id) values (2122, 'RPT2122000011')
insert test_reports (fiscal_year, report_id) values (2122, 'RPT2122000101')
insert test_reports (fiscal_year, report_id) values (2122, '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, 1),
col1 varchar(10),
report_id varchar(15)
)
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000001')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000002')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000010')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000011')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000101')
insert test_reports (col1,report_id) values ('çol1', 'RPT2122000102')
Step 1
,重命名旧列
sp_rename 'test_reports.report_id', 'Oldreport_id', 'COLUMN';
Step 2 : Get max auto incremented id column value.
Step 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;
Step 4 Try new insert
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())
您可以为其设置默认值,并且 inserty/update 无需修改
- 添加新的计算列:
alter table test_reports add report_id as (
coalesce(original_report_id,
'RPT' + convert(varchar,dbo.fiscalyear(reportdatecolumn) + right('000000' + convert(varchar, id), 6)))
我很久以前遇到过与您类似的问题,下面的解决方案对我有用。
您可能会遇到此问题,因为在批量插入的情况下,它只会使用上次更新的值更新列(对于所有行)的值,因此您会得到重复的值。
考虑到您现有的结构 - 在插入触发器之后,一种方法是继续使用触发器并尝试遍历 INSERTED table
IF (OBJECT_ID('tempdb..#T') IS NOT NULL)
BEGIN
DROP TABLE #T
END
CREATE TABLE #T (
[Serial] INT IDENTITY(1, 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