保证随机插入

时间:2018-11-01 13:28:48

标签: sql sql-server

我正在尝试预生成一些字母数字字符串,并将结果插入表中。字符串的长度为5。例如:a5r67。基本上,我想为客户生成一些可读的字符串,以便他们可以访问他们的订单,例如 www.example.com/order/a5r67。现在,我有一条选择语句:

;WITH 
    cte1 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
    cte2 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
    cte3 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
    cte4 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
    cte5 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t))
INSERT INTO ProductHandles(ID, Used)
SELECT cte1.t + cte2.t + cte3.t + cte4.t + cte5.t, 0
FROM cte1
CROSS JOIN cte2
CROSS JOIN cte3
CROSS JOIN cte4
CROSS JOIN cte5

现在的问题是,我需要编写这样的东西才能从表中获取值:

SELECT TOP 1 ID 
FROM ProductHandles
WHERE Used = 0

我将在Used列上具有索引,因此它会很快。问题在于它带有订单:

00000
00001
00002
...

我知道我可以NEWID()来订购,但这会慢很多。我知道除非我们指定Order By子句,否则无法保证订购。需要的是相反的。我需要保证混乱,但是不需要每次客户创建订单时都按NEWID()进行订购。

我将像这样使用它:

WITH cte as (
                SELECT TOP 1 * FROM ProductHandles WHERE Used = 0
                --I don't want to order by newid() here as it will be slow
            )
UPDATE cte 
SET Used = 1
OUTPUT INSERTED.ID

3 个答案:

答案 0 :(得分:8)

如果您向表中添加一个身份列,并在插入记录时使用order by newid()(虽然速度很慢,但根据我的理解这是一次性的事情,但是您可以使用{{ 1}}上的order by列中,以将记录插入表的顺序选择它们。

来自Microsoft文档中的Limitations and Restrictions part of the INSERT page

  

使用SELECT和ORDER BY填充行的INSERT查询保证了如何计算标识值,但不能保证插入行的顺序。

这意味着通过这样做,您可以有效地使identity列以相同的随机顺序对identity语句中选择的行进行排序。

此外,无需重复相同的cte 5次-您已经在重复交叉申请:

insert...select

然后,cte可以具有order by子句,该子句可以保证与填充该表的select语句返回的行相同的随机顺序:

CREATE TABLE ProductHandles(sort int identity(1,1), ID char(5), used bit)


;WITH 
    cte AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t))        
INSERT INTO ProductHandles(ID, Used)
SELECT a.t + b.t + c.t + d.t + e.t, 0
FROM cte a
CROSS JOIN cte b
CROSS JOIN cte c
CROSS JOIN cte d
CROSS JOIN cte e
ORDER BY NEWID()

You can see a live demo on rextester.(只有数字,因为它花费的时间太长了)

答案 1 :(得分:2)

这是一个略有不同的选择... 与其尝试在一个会议中尝试生成所有可能的值,不如一次仅生成一百万个或两个,并在它们用完时生成更多。 使用这种方法,您可以大大减少初始创建时间,并且无需维护庞大的值表,而其中的大部分表将永远不会使用。

CREATE TABLE dbo.ProductHandles (
    rid INT NOT NULL
        CONSTRAINT pk_ProductHandles 
        PRIMARY KEY CLUSTERED,
    ID_Value CHAR(5) NOT NULL
        CONSTRAINT uq_ProductHandles_IDValue 
        UNIQUE WITH (IGNORE_DUP_KEY = ON),      -- prevents the insertion of duplicate values w/o generating any errors.
    Used BIT NOT NULL
        CONSTRAINT df_ProductHandles_Used 
        DEFAULT (0)
    );

-- Create a filtered index to help facilitate fast searches
-- of unused values.
CREATE NONCLUSTERED INDEX ixf_ProductHandles_Used_rid    
    ON dbo.ProductHandles (Used, rid)
    INCLUDE(ID_Value)
WHERE Used = 0;

--==========================================================

WHILE 1 = 1     -- The while loop will attempt to insert new rows, in 1M blocks, until required minimum of unused values are available.
BEGIN 
    IF (SELECT COUNT(*) FROM dbo.ProductHandles ph WHERE ph.Used = 0) > 1000000     -- the minimum num of unused ID's you want to keep on hand.
    BEGIN
        BREAK;
    END;
    ELSE 
    BEGIN
        WITH 
            cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)), 
            cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
            cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b),
            cte_Tally (n) AS (
                SELECT TOP (1000000)    -- Sets the "block size" of each insert attempt.
                    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
                FROM
                    cte_n3 a CROSS JOIN cte_n3 b
                )
        INSERT dbo.ProductHandles (rid, ID_Value, Used)
        SELECT 
            t.n + ISNULL((SELECT MAX(ph.rid) FROM dbo.ProductHandles ph), 0),
            CONCAT(ISNULL(c1.char_1, n1.num_1), ISNULL(c2.char_2, n2.num_2), ISNULL(c3.char_3, n3.num_3), ISNULL(c4.char_4, n4.num_4), ISNULL(c5.char_5, n5.num_5)),
            0
        FROM
            cte_Tally t
            -- for each of the 5 positions, randomly generate numbers between 0 & 36. 
            -- 0-9 are left as numbers. 
            -- 10 - 36 are converted to lower cased letters.
            CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n1 (num_1)
            CROSS APPLY ( VALUES (CHAR(CASE WHEN n1.num_1 > 9 THEN n1.num_1 + 87 END)) ) c1 (char_1)
            CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n2 (num_2)
            CROSS APPLY ( VALUES (CHAR(CASE WHEN n2.num_2 > 9 THEN n2.num_2 + 87 END)) ) c2 (char_2)
            CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n3 (num_3)
            CROSS APPLY ( VALUES (CHAR(CASE WHEN n3.num_3 > 9 THEN n3.num_3 + 87 END)) ) c3 (char_3)
            CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n4 (num_4)
            CROSS APPLY ( VALUES (CHAR(CASE WHEN n4.num_4 > 9 THEN n4.num_4 + 87 END)) ) c4 (char_4)
            CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n5 (num_5)
            CROSS APPLY ( VALUES (CHAR(CASE WHEN n5.num_5 > 9 THEN n5.num_5 + 87 END)) ) c5 (char_5);
    END;
END;

初始创建后,将WHILE循环中的代码移动到存储过程,并安排它定期自动运行。

答案 2 :(得分:0)

如果我理解这项权利,就像您尝试将URL /可见数据与DB记录ID分开一样,这是大多数应用程序所使用的,并且提供了与用户将要使用的ID字段没有直接关系的内容看到。 NEWID()确实可以控制字符数,因此您可以使用较小的索引生成较小的字段。或者只是使用完整的NEWID()的一部分

SELECT CONVERT(varchar(255), NEWID())
SELECT SUBSTRING(CONVERT(varchar(40), NEWID()),0,5)

您可能还想查看一个checksum字段,但我不知道它在索引编制方面是否更快。通过将随机的NEWID()与跨2或3个字段的校验和结合在一起,您将变得更加疯狂。

SELECT BINARY_CHECKSUM(5 ,'EP30461105',1)