我想用同一个表生成的值更新我的表。
我的目标是使用像_NS和_WP这样的proftxt搜索所有行,并使用相同的ao,总结它们, 将该值除以该ao的_H,_ G,_L-Elements的数量,并将该值添加到该ao的_H,_G和_L对象中。
ao可能只有_NS和_WP行。比例程应跳过这个ao。
示例:
我的数据如下:
an, ao, proftxt, value, year
101 , 1, 'e_NSe', 5, 2006
102 , 1, 'e_Ha', 1, 2006
103 , 1, 'w_NSr', 4, 2006
104 , 2, 'w_NSr', 2, 2006
105 , 2, 'x_H05r', 4, 2006
106 , 2, 'w_Gr', 2, 2006
107 , 2, 'a_WPr', 4, 2006
108 , 3, 'a_WPr', 4, 2006
我的数据应该是:
an, ao, proftxt, value, year
102 , 1, 'e_Ha', 10 2006
103 , 2, 'x_H05r', 7, 2006
103 , 2, 'w_Gr', 5, 2006
108 , 3, 'a_WPr', 4, 2006
我的例程适用于少量的testdata。
更新功能在成功13个小时后,在真实数据库上工作时结束 但它只编辑了210000行中的5000行。
DECLARE @ENDYEAR INT
DECLARE @AO BIGINT
DECLARE @YEAR INT
DECLARE @ELEMENTS INT
--Parameter festlegen
SET @YEAR = 2006
SET @ENDYEAR = 2013 --Endyear+1
SET @AO = 2
WHILE(@YEAR<@ENDYEAR)
BEGIN
WHILE (@AO >1) --Do as long as Cursor is inside table
BEGIN
SET @AO = (SELECT TOP 1 ao FROM tbl_slp -- Search ao with _WP _NS
WHERE (proftxt LIKE '%[_]WP%'
OR proftxt LIKE '%[_]NS%')
AND year = @YEAR
AND ao > @AO );
SET @ELEMENTS = (SELECT COUNT(proftxt) --Count Number of _H, _G, _L elements
FROM tbl_SLP
WHERE ao = @AO AND year = @YEAR AND
(proftxt LIKE '%[_]H%' OR proftxt = NULL
OR proftxt LIKE '%[_]G%'
OR proftxt LIKE '%[_]L%'))
IF (@ELEMENTS != 0)
BEGIN
UPDATE tbl_SLP --Update _H, _G, _L rows
SET value = value + (SELECT SUM(CONVERT(float, value))
FROM tbl_SLP
WHERE (proftxt LIKE '%[_]WP%'
OR proftxt LIKE '%[_]NS%')
AND year = @YEAR
AND ao = @AO)
/@ELEMENTS
WHERE ao = @AO AND year = @YEAR
DELETE FROM tbl_SLP --delete_WP _NS rows
WHERE ao= @AO
AND year = @YEAR
AND (proftxt LIKE '%[_]WP%' OR proftxt LIKE '%[_]NS%')
END
SET @AO = @AO +1
END
SET @YEAR = @YEAR +1
END
我知道这个例程非常慢,但我该怎么办?
答案 0 :(得分:5)
SQL是为基于集合的操作而设计的,而不是像例程那样的程序化控制流式逻辑。这是一种基于集合的方式,我猜测它会比程序方式快得多:
SET XACT_ABORT ON
SET NOCOUNT ON
BEGIN TRANSACTION
-- Create a temp table with each ao-year's sums and counts (sums of _NS and _WP record values and counts of _H, _G, and _L records)
SELECT T.ao, T.year, SUM(T.value) AS SumVals, (SELECT COUNT(*) FROM tbl_slp A WHERE A.ao = T.ao AND A.year = T.year AND (A.proftxt = NULL OR A.proftxt LIKE '%[_]H%' OR A.proftxt LIKE '%[_]G%' OR A.proftxt LIKE '%[_]L%')) AS CountOther
INTO #temp1
FROM tbl_slp T
WHERE (T.proftxt LIKE '%[_]WP%' OR T.proftxt LIKE '%[_]NS%')
GROUP BY T.ao, T.year
-- Add "sum/count" for each ao-year to the _H, _G, and _L records for that year
UPDATE A
SET value = value + CONVERT(FLOAT, T.SumVals) / T.CountOther
FROM tbl_slp A
INNER JOIN #temp1 T ON A.ao = T.ao AND A.year = T.year
WHERE (A.proftxt = NULL OR A.proftxt LIKE '%[_]H%' OR A.proftxt LIKE '%[_]G%' OR A.proftxt LIKE '%[_]L%')
-- Now that we've distributed the _WP and _NS values, delete those records
DELETE A
FROM tbl_slp A
INNER JOIN #temp1 T ON A.ao = T.ao AND A.year = T.year
WHERE (A.proftxt LIKE '%[_]WP%' OR A.proftxt LIKE '%[_]NS%')
AND T.CountOther > 0
COMMIT TRANSACTION
对于您提供的样本集,这会产生完全相同的结果(an
列除外,我认为这是一个错字)。
完全公开,这样的样本集需要比例程更长(17毫秒与3毫秒相比),但它应该可以更好地扩展到大数据。我把它放在一个事务中是为了正确但我不确定你的确切用例是什么,所以这可能是我的方式的一个缺点,因为它会锁定页面(并可能会升级到整个表格)整个时间。但是,您的例行程序没有任何交易,这可能会导致数据不良,因此如果您坚持下去,请确保将每个更新 - 删除对放在自己的交易中。
此外,如果您在proftxt
上没有索引,请添加一个!这将为两种解决方案带来巨大的变化。
答案 1 :(得分:1)
首先,我看到了一些与NULL相关的问题。例如,你的内部循环显然等待@AO在完成之前变为NULL:
WHILE (@AO >1)
当你将@AO设置为不存在的东西时会有效,但很难读,并且你可能想要编写更明确的逻辑。
接下来,这种情况总是错误的:
OR proftxt = NULL
NULL值不等于它自己。要测试这种情况,你必须写:
OR proftxt IS NULL
此外,您的COUNT(proftxt)将省略任何NULL值。尝试运行以下示例查询。它返回1,同时返回消息“警告:聚合或其他SET操作消除了空值”。
SELECT COUNT(fieldname) FROM (SELECT 1 AS fieldname UNION SELECT NULL AS fieldname) AS tablename
最后,索引proftxt列不会解决性能问题,因为带有前导通配符的LIKE条件无法使用索引。您可以将像电话簿这样的索引视为按姓氏按字母顺序排列。如果您正在寻找LastName LIKE'%mann',索引将无法帮助您。您仍然需要阅读电话簿中的每个条目,以查找以“mann”结尾的所有姓氏。在数据库术语中,这称为“表扫描”,并且速度很慢。
我会添加一个新列,您可以将其命名为proftxttype。
UPDATE tbl_SLP
SET proftxttype = 1
WHERE proftxt LIKE '%[_]WP%'
OR proftxt LIKE '%[_]NS%'
UPDATE tbl_SLP
SET proftxttype = 2
WHERE proftxt LIKE '%[_]H%'
OR proftxt LIKE '%[_]G%'
OR proftxt LIKE '%[_]L%'
OR proftxt IS NULL
然后索引此列:
CREATE NONCLUSTERED INDEX [IX_PROFTXTTYPE] ON [dbo].[TBL_SLP] (PROFTXTTYPE ASC) ON [PRIMARY]
现在根据proftxttype重写您的更新。当然,无论何时插入或更新proftxt,您还必须更新proftxttype。这是不可避免的,但SQL Server将负责保持索引最新,因此您不必担心索引。
我知道这听起来像是很多工作,但问题的核心在于,每次想要使用前导通配符查找proftxt值时,都会扫描整个表。
答案 2 :(得分:1)
我结合了两个(真的很有用!)的答案。正如关键修正告诉我的那样,我添加了一个coloum proftype来设置表中的索引:
ALTER TABLE
ADD proftype CHAR(1)
GO
UPDATE tbl_SLPverrechnetWPNSP
SET proftype = 'W'
WHERE proftxt LIKE '%[_]WP%'
UPDATE tbl_SLP
SET proftype = 'N'
WHERE proftxt LIKE '%[_]NS%'
UPDATE tbl_SLP
SET proftype = 'H'
WHERE proftxt LIKE '%[_]H%'
OR proftxt IS NULL
UPDATE tbl_SLP
SET proftype = 'G'
WHERE proftxt LIKE '%[_]G%'
UPDATE tbl_SLP
SET proftype = 'L'
WHERE proftxt LIKE '%[_]L%'
--set index on proftype
CREATE NONCLUSTERED INDEX [IX_PROFTYPE] ON [dbo].[tbl_SLP] (proftype ASC) ON [PRIMARY]
GO
接下来,我使用bob中的代码编辑我的表。
SET XACT_ABORT ON
SET NOCOUNT ON
BEGIN TRANSACTION
-- Create a temp table with each ao-year's sums and counts (sums of N and W record values and counts of H, G, and L records)
SELECT T.ao, T.year, SUM(CONVERT(float, T.value)) AS SumVals, (SELECT COUNT(*)
FROM tbl_slp A
WHERE A.ao = T.ao
AND A.year = T.year
AND (A.proftype ='G' OR A.proftype = 'H' OR A.proftype = 'L' ))
AS CountOther
INTO #temp1
FROM tbl_slp T
WHERE (T.proftype = 'W' OR T.proftype = 'N')
GROUP BY T.ao, T.year
-- Add "sum/count" for each ao-year to the H, G, and L records for that year
UPDATE A
SET value = value + CONVERT(FLOAT, T.SumVals) / T.CountOther
FROM tbl_slp A
INNER JOIN #temp1 T ON A.ao = T.ao AND A.year = T.year
WHERE (A.proftype = 'H' OR A.proftype = 'G' OR A.proftype LIKE 'L')
-- Now that we've distributed the W and N values, delete those records
DELETE A
FROM tbl_slp A
INNER JOIN #temp1 T ON A.ao = T.ao AND A.year = T.year
WHERE (A.proftype = 'W' OR A.proftype = 'N')
AND T.CountOther > 0
DROP TABLE #temp1
COMMIT TRANSACTION
非常感谢你的帮助!例程只运行了3.5分钟!!!