我正在寻找一种方法来编写以下程序而不使用CURSOR或只是为了找到性能更好的查询。
CREATE TABLE #OrderTransaction (OrderTransactionId int, ProductId int, Quantity int);
CREATE TABLE #Product (ProductId int, MediaTypeId int);
CREATE TABLE #OrderDelivery (OrderTransactionId int, MediaTypeId int);
INSERT INTO #Product (ProductId, MediaTypeId) VALUES (1,1);
INSERT INTO #Product (ProductId, MediaTypeId) VALUES (2,2);
INSERT INTO #OrderTransaction(OrderTransactionId, ProductId, Quantity) VALUES (1,1,1);
INSERT INTO #OrderTransaction(OrderTransactionId, ProductId, Quantity) VALUES (2,2,6);
DECLARE @OrderTransactionId int, @MediaTypeId int, @Quantity int;
DECLARE ordertran CURSOR FAST_FORWARD FOR
SELECT OT.OrderTransactionId, P.MediaTypeId, OT.Quantity
FROM #OrderTransaction OT WITH (NOLOCK)
INNER JOIN #Product P WITH (NOLOCK)
ON OT.ProductId = P.ProductId
OPEN ordertran;
FETCH NEXT FROM ordertran INTO @OrderTransactionId, @MediaTypeId, @Quantity;
WHILE @@FETCH_STATUS = 0
BEGIN
WHILE @Quantity > 0
BEGIN
INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId])
VALUES (@OrderTransactionId, @MediaTypeId)
SELECT @Quantity = @Quantity - 1;
END
FETCH NEXT FROM ordertran INTO @OrderTransactionId, @MediaTypeId, @Quantity;
END
CLOSE ordertran;
DEALLOCATE ordertran;
SELECT * FROM #OrderTransaction
SELECT * FROM #Product
SELECT * FROM #OrderDelivery
DROP TABLE #OrderTransaction;
DROP TABLE #Product;
DROP TABLE #OrderDelivery;
答案 0 :(得分:3)
从Number表开始,该表足以处理最大订单金额:
CREATE TABLE Numbers (
Num int NOT NULL PRIMARY KEY CLUSTERED
)
-- SQL 2000 version
INSERT Numbers VALUES (1)
SET NOCOUNT ON
GO
INSERT Numbers (Num) SELECT Num + (SELECT Max(Num) FROM Numbers) FROM Numbers
GO 15
-- SQL 2005 and up version
WITH
L0 AS (SELECT c = 1 UNION ALL SELECT 1),
L1 AS (SELECT c = 1 FROM L0 A, L0 B),
L2 AS (SELECT c = 1 FROM L1 A, L1 B),
L3 AS (SELECT c = 1 FROM L2 A, L2 B),
L4 AS (SELECT c = 1 FROM L3 A, L3 B),
L5 AS (SELECT c = 1 FROM L4 A, L4 B),
N AS (SELECT Num = ROW_NUMBER() OVER (ORDER BY c) FROM L5)
INSERT Numbers(Num)
SELECT Num FROM N
WHERE Num <= 32768;
然后,在INSERT语句之后立即:
INSERT #OrderDelivery (OrderTransactionId, MediaTypeId)
SELECT
OT.OrderTransactionId,
P.MediaTypeId
FROM
#OrderTransaction OT
INNER JOIN #Product P ON OT.ProductId = P.ProductId
INNER JOIN Numbers N ON N.Num BETWEEN 1 AND OT.Quantity
应该这样做!
如果由于某种原因你有疑虑在你的数据库中放置一个永久的Numbers表(我不明白,因为它是一个很好的工具),那么你可以简单地加入CTE而不是表本身。在SQL 2000中,您可以创建临时表并使用循环,但我会强烈反对这一点。
强烈建议使用Numbers表。没有人担心未来的某些变化会破坏它(整数的集合不会很快改变)。有些人使用带有一百万个数字的Numbers表,只有大约4MB的存储空间。
回答Numbers表的批评:如果数据库设计使用数字表,那么该表不需要更改。它就像数据库中的任何其他表一样,可以依赖它。你不必太担心Orders表的查询失败,因为某些表可能不存在,所以我不明白为什么对于另一个需要和依赖的表有任何类似的担忧。
<强>更新强>
在撰写此答案后的那段时间里,我了解到master.dbo.spt_values
表格中有number
列。当使用where type='P'
查询时,在SQL 2000中得到0 - 255,在SQL 2005及以上得到0 - 8191。 (还有一些可能有用的low
和high
列。)如果有必要,即使在SQL 2000中,也可以非常快速地将此表交叉连接到自身。 / p>
答案 1 :(得分:1)
诀窍是引入一个值表(在下面的示例中命名为MyTableOfIntegers),其中包含1和(至少)某个值之间的所有整数值(在手边的情况下) ,这将是OrderTransaction表中可能的最大数量值。)
INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId])
SELECT OT.OrderTransactionId, P.MediaTypeId
FROM #OrderTransaction OT WITH (NOLOCK)
INNER JOIN #Product P WITH (NOLOCK)
ON OT.ProductId = P.ProductId
JOIN MyTableOfIntegers I ON I.Num <= OT.Quantity
--WHERE some optional conditions
基本上MyTableOfIntegers上的额外JOIN产生与OT.Quantity一样多的重复行,这似乎是游标的目的:在OrderDelivery表中插入那么多重复的行。
我没有用临时表和所有来检查剩下的逻辑(我假设这些是临时表,用于检查逻辑而不是正确处理过程的一部分),但它似乎是上面是仅以声明方式表达所需逻辑所需的构造类型,没有任何游标甚至任何循环。
答案 2 :(得分:1)
以前的答案略有不同,避免使用永久数字表(虽然我不确定为什么人们如此害怕这个结构),并允许你构建一个包含完全集合的运行时CTE您需要执行正确数量的插入数字(通过检查最高数量)。我在初始CTE中注释掉了CROSS JOIN,但如果任何给定订单的数量超过sys.columns中的行数,您可以使用它。希望这是不太可能的情况。请注意,这适用于SQL Server 2005及更高版本...告诉我们您要定位的特定版本总是有用的。
DECLARE @numsNeeded INT;
SELECT @numsNeeded = MAX(Quantity) FROM #OrderTransaction;
WITH n AS
(
SELECT TOP (@numsNeeded) i = ROW_NUMBER()
OVER (ORDER BY c.[object_id])
FROM sys.columns AS c --CROSS JOIN sys.columns AS c2
)
INSERT #OrderDelivery
(
OrderTransactionID,
MediaTypeID
)
SELECT t.OrderTransactionID, p.MediaTypeID
FROM #OrderTransaction AS t
INNER JOIN #Product AS p
ON t.ProductID = p.ProductID
INNER JOIN n
ON n.i <= t.Quantity;
答案 3 :(得分:0)
INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId])
SELECT OT.OrderTransactionId, P.MediaTypeId,
FROM #OrderTransaction OT
INNER JOIN #Product P
ON OT.ProductId = P.ProductId
WHERE OT.Quantity > 0
我觉得我在这里误读了这个逻辑,但这不是公平的吗?
答案 4 :(得分:0)
这仍然使用循环,但它已经摆脱了光标。如果没有创建一个数字表来加入,我认为这是最好的答案。
DECLARE @Count AS INTEGER
SET @Count = 1
WHILE (1 = 1)
BEGIN
INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId])
SELECT OT.OrderTransactionId, P.MediaTypeId, OT.Quantity
FROM #OrderTransaction OT WITH (NOLOCK)
INNER JOIN #Product P WITH (NOLOCK)
ON OT.ProductId = P.ProductId
WHERE OT.Quantity > @Count
IF @@ROWCOUNT = 0
BREAK
SET @COUNT = @COUNT + 1
END