3

我正在使用示例数据创建一个数据库。每次我运行存储过程为我的示例数据库生成一些新数据时,我都想根据表 A(“产品”)中的所有行清除并重新填充表 B(“项目”)。

如果表 A 包含主键值为 1、2、3、4 和 5 的行,我希望表 B 具有表 A 的外键,并将随机数的行插入表 B 中的每个表 A 行。(对于任何给定的“产品”,我们基本上是在货架上放上随机数量的“项目”。)

我正在使用此答案中的代码来生成数字列表。我加入此函数的结果以创建要插入的行:

WITH cte AS
(
   SELECT
       ROW_NUMBER() OVER (ORDER BY (select 0)) AS i
   FROM
      sys.columns c1 CROSS JOIN sys.columns c2 CROSS JOIN sys.columns c3
)
SELECT i
FROM cte
WHERE 
    i BETWEEN @p_Min AND @p_Max AND
    i % @p_Increment = 0

随机数在视图中生成(以绕过函数的限制),如下所示:

-- Mock.NewGuid view
SELECT id = ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)))

还有一个返回随机数的函数:

-- Mock.GetRandomInt(min, max) function definition
DECLARE @random int;
SELECT @random = Id % (@MaxValue - @MinValue + 1) FROM Mock.NewGuid;
RETURN @random + @MinValue;

但是,当您查看并执行此代码时...

WITH Products AS
(
    SELECT ProductId, ItemCount = Mock.GetRandomInt(1,5)
    FROM Product.Product
)
SELECT A = Products.ProductId, B = i
FROM Products
JOIN (SELECT i FROM Mock.GetIntList(1,5,1)) Temp ON
    i < Products.ItemCount
ORDER BY ProductId, i

...这会返回一些不一致的结果!

A,B
1,1
1,2
1,3
2,1
2,2
3,2 <-- where is 1?
3,3
4,1
5,3 <-- where is 1, 2?
6,1

我希望,对于每个产品 ID,JOIN 会产生 1-5 行。但是,似乎值被跳过了!对于更大的数据集,这一点更加明显。我最初试图在 Item 中为每个 Product 行生成 20-50 行,但这导致每个产品只有 30-40 行。

问题:知道为什么会这样吗?每个产品都应该插入随机数量的行(1 到 5 之间),并且 B 值应该是连续的!相反,缺少一些数字!

如果我将数字存储在我创建的表中然后加入该表,或者如果我使用递归 CTE,也会发生此问题。

我正在使用 SQL Server 2008R2,但我相信我在我的 2012 数据库上也看到了同样的问题。兼容级别分别为 2008 和 2012。

4

2 回答 2

1

这是一个有趣的问题。我已经多次处理过这个问题。我确信有一种方法可以不使用游标。但为什么不呢。只要@RandomMaxRecords 不会变得很大或者您有大量的产品记录,这是一个廉价的内存问题。如果 Items 表中的数据没有意义,那么我建议截断内存表中的任何我为#Item 定义哈希表的地方。显然,您将从 Product 表中提取,而不是我为测试创建的哈希。

这是一篇很棒的文章,详细描述了我是如何得出我的解决方案的。 少于点博客

代码

--This is your product table with 5 random products
IF OBJECT_ID('tempdb..#Product') IS NOT NULL DROP TABLE #Product
CREATE TABLE #Product
(
    ProductID INT PRIMARY KEY IDENTITY(1,1),
    ProductName VARCHAR(25),
    ProductDescription VARCHAR(max)
)
INSERT INTO #Product (ProductName,ProductDescription) VALUES ('Product Name 1','Product Description 1'),
                                                             ('Product Name 2','Product Description 2'),
                                                             ('Product Name 3','Product Description 3'),
                                                             ('Product Name 4','Product Description 4'),
                                                             ('Product Name 5','Product Description 5')

--This is your item table.  This would probably just be a truncate statement so that your table is reset for the new values to go in
IF OBJECT_ID ('tempdb..#Item') IS NOT NULL DROP TABLE #Item
CREATE TABLE #Item
(
    ItemID INT PRIMARY KEY IDENTITY(1,1),
    FK_ProductID INT NOT NULL,
    ItemName VARCHAR(25),
    ItemDescription VARCHAR(max)
)

--Declare a bunch of variables for the cursor and insert into the item table process
DECLARE @ProductID INT
DECLARE @ProductName VARCHAR(25)
DECLARE @ProductDescription VARCHAR(max)
DECLARE @RandomItemCount INT
DECLARE @RowEnumerator INT
DECLARE @RandomMaxRecords INT = 10

--We declare a cursor to iterate over the records in product and generate random amounts of items
DECLARE ItemCursor CURSOR
FOR SELECT * FROM #Product

OPEN ItemCursor

FETCH NEXT FROM ItemCursor INTO @ProductID, @ProductName, @ProductDescription
WHILE (@@FETCH_STATUS <> -1)
BEGIN

    --Get the Random Number into the variable.  And we only want 1 or more records.  Mod division will produce a 0.
    SELECT @RandomItemCount = ABS(CHECKSUM(NewID())) % @RandomMaxRecords
    SELECT @RandomItemCount = CASE @RandomItemCount WHEN 0 THEN 1 ELSE @RandomItemCount END

    --Iterate on the RowEnumerator to the RandomItemCount and insert item rows
    SET @RowEnumerator = 1
    WHILE (@RowEnumerator <= @RandomItemCount)
    BEGIN
        INSERT INTO #Item (FK_ProductID,ItemName,ItemDescription)
        SELECT @ProductID, REPLACE(@ProductName,'Product','Item'),REPLACE(@ProductDescription,'Product','Item')

        SELECT @RowEnumerator = @RowEnumerator + 1
    END
    FETCH NEXT FROM ItemCursor INTO @ProductID, @ProductName, @ProductDescription
END

CLOSE ItemCursor
DEALLOCATE ItemCursor
GO

--Look at the result
SELECT 
    * 
FROM 
    #Product AS P
    RIGHT JOIN #Item AS I ON (P.ProductID = I.FK_ProductID)

--Cleanup
DROP TABLE #Product
DROP TABLE #Item
于 2013-06-14T20:42:17.770 回答
0

看起来LEFT OUTER JOINGetIntList (而不是INNER JOIN)解决了我遇到的问题。

于 2013-09-13T14:38:14.253 回答