背景:想在表中隨機取10條記錄,讓取出來的數據不重復(表中必須是有個遞增列,且遞增從1開始間隔為1)。
數據表:
CREATE TABLE testable
(
id INT IDENTITY(1,1),
myname NVARCHAR(1000),
insertedTime DATETIME DEFAULT SYSDATETIME()
)
表中共有100條數據,如下
1. 首先想到的是MSSQL自帶的newid()
采用這種方法時,需要將表中所有記錄與newid()生成的值進行比較從而進行排序。因此,如果表中的記錄較多,操作會非常緩慢。
USE Gift163DBgoSELECT TOP 14 * FROM dbo.testable ORDER BY NEWID()
缺點:1. 取出的10條數據會出現重復 2.當數據表數據很多的時候,速度將很慢 (每次重新計算newid)
2. 自定義函數返回一個表,表中記錄的是隨機生成的N個id值。
1)rand()生成隨機數 rand()*count,CEILING,floor用法
2)如果臨時表中無此數據,則放入,否則重新生成
3)直到N條記錄已經生成
注意:標量函數function內不能出現rand()方法,變通下生成個view v_random,然后在函數內調用 v_random獲取隨機數
create view v_randomASselect CEILING(rand()*51) as random --注意51,生成的是1到51之間的數字,因為事先知道數據庫中有51條記錄go
自定義函數代碼如下:
ALTER FUNCTION randomIntStringWithCommaSplit(@counts int) –counts 表明返回的個數RETURNS @t TABLE (filed1 int) --返回表@t,有個int類型的 field列ASBEGINDECLARE @randomInt INTDECLARE @i INTSET @i=0WHILE @i<@countsBEGINselect @randomInt= random FROM v_random--不能是 SET @randomInt=SELECT random FROM v_random
IF NOT EXISTS(SELECT TOP 1 * FROM @t WHERE filed1=@randomInt)BEGININSERT INTO @t VALUES (@randomInt)SET @i=@i+1ENDENDRETURNEND
上面函數返回的是一個表類型,表中有個int字段,存放要查找的N個不同的keyId (keyId為要查找表的遞增列,且遞增為1,從1開始遞增)
所以返回的表中存放的數據是 dbo.Articles中的id列的值。
使用:調用上面的自定義函數返回10個不重復的id
SELECT * FROM randomIntStringWithCommaSplit(10)
下面是幾次的執行結果,可以看到每個結果中都不存在重復的值(fidled1為臨時表的唯一列)
最后通過 select * from table where id in randomIntStringWithCommaSplit(10) ,這樣就可以從table中隨機取出10條不重復的數據來了。
3.存儲過程取不重復的數據
--dbo.getRandomDataFromTable
--輸入參數 @tableName nvarchar(100),--表名
---@dataCount nvarchar(100)--取N條數據
輸出結果集:列id,存放N條要查詢的數據
USE Gift163DBGOIF OBJECT_ID ( 'dbo.getRandomDataFromTable', 'P' ) IS NOT NULLDROP PROCEDURE dbo.getRandomDataFromTable;GOCREATE PROC [dbo].[getRandomDataFromTable]@tableName nvarchar(100),@dataCount nvarchar(100)ASBEGIN--SET NOCOUNT ON;
DECLARE @t TABLE (id INT) --臨時表DECLARE @i INT --臨時變量DECLARE @randomInt INT --每次隨機生成的整數DECLARE @tableCount INT --表的行數--先獲取表中最大數據的id
EXEC( 'SELECT '+@tableCount+'=COUNT(*) FROM '+@tableName+'')SET @i=0WHILE @i<@dataCountBEGINSELECT @randomInt=CEILING(RAND()*@tableCount)IF NOT EXISTS(SELECT TOP 1 * FROM @t)BEGININSERT INTO @t VALUES (@randomInt)SET @i=@i+1ENDEND--打印出取出的表的id
SELECT * FROM @tENDGo
生成測試數據100條
USE Gift163DBGOif exists (select 1from sysobjectswhere id = object_id('testable')and type = 'U')drop table testablegoCREATE TABLE testable(id INT IDENTITY(1,1),myname NVARCHAR(1000),insertedTime DATETIME DEFAULT SYSDATETIME())//插入100條數據DECLARE @i INTSET @i=1WHILE @i<100BEGININSERT INTO tesTable (myname) VALUES ('我的名字是'+CONVERT(NVARCHAR, @i) ) --將 varchar 值 '我的名字是' 轉換成數據類型 int 時失敗。SET @i=@i+1End
調用上面的存儲過程從表testable取10條不重復id
USE Gift163DBgoexec getRandomDataFromTable 'testable',10
自定義的存儲過程不會出現重復的記錄
4.改進的存儲過程,最終存儲過程
輸入參數3個:表名,表的遞增列名 , 要取的N條數據
USE Gift163DBGOIF OBJECT_ID ( 'dbo.getRandomDataFromTable', 'P' ) IS NOT NULLDROP PROCEDURE dbo.getRandomDataFromTable;GOCREATE PROC [dbo].[getRandomDataFromTable]@tableName nvarchar(100),@identityKey NVARCHAR(100),@dataCount nvarchar(100)ASBEGIN--SET NOCOUNT ON;
--DECLARE @t TABLE (id INT) --臨時表
DECLARE @i INT --臨時變量DECLARE @randomInt INT --每次隨機生成的整數DECLARE @tableCount INT --表的行數--先獲取表中最大數據的id
DECLARE @str NVARCHAR(3000)SET @str='SELECT @tableCount=COUNT(*) FROM '+@tableNameexec sp_executesql @str, N'@tableCount int output', @tableCount outputcreate TABLE #sdf (id int)SET @i=0WHILE @i<@dataCount AND @i<@tableCountBEGINSELECT @randomInt=CEILING(RAND()*@tableCount)IF NOT EXISTS(SELECT TOP 1 * FROM #sdf WHERE id=@randomInt)BEGININSERT INTO #sdf VALUES (@randomInt)SET @i=@i+1ENDEND--取出數據
DECLARE @str2 NVARCHAR(2000)SET @str2=' SELECT * FROM '+@tableName+' where '+@identitykey
+' in '
+' (select id from #sdf )'
PRINT @str2--select id from @t
EXEC (@str2)--exec sp_executesql @str2,N'@t TABLE',@t OUTPUT
END
調用存儲過程:隨機取10條數據
最終的存儲過程不管你隨機取多少條數據(只要每次取的數據數目小於表中行數) 就可以保證每次取的數據不會重復。當然前提是,取的表必須有個遞增列,而且以1開始,遞增1.