SQL While loop or c# linq foreach? - c#

I have a credit card payment process stored procedure written in SQL. This is using SQL while to loop through half million payment records. This is horribly slow, because i have to loop through another set of data for each of these payment records and update bunch of tables accordingly. That means while loop inside while loop in sql.
I have to re-write this in c# to improve the performance. We are using LINQ & Entity framework. Can anybody please suggest me if this is going to help or not?
Thanks
Updating my question with modified stored procedure. I have replaced the references to my tables and removed some part of the code. But let me know if this is not clear.
CREATE PROCEDURE [dbo].[USP_ProcessPayments]
(
#UserKey int
)
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #TempUserPayments
(
-- with Columns declared
)
INSERT INTO #TempUserPayments
SELECT DISTINCT up.*, ROW_NUMBER() OVER(ORDER BY up.UserKey DESC, up.DateReceived) AS RN --generate row number
FROM UserPayment up
where up.UserKey = #UserKey
CREATE INDEX UP_1 on #TempUserPayments (RN);
DECLARE #LOOPCOUNTER INT, #ROWCOUNTER INT, #REQUESTEDAMOUNT MONEY, #UPDATEDREQUESTEDAMOUNT MONEY, #AMOUNT MONEY,
#AMOUNTNOTAPPLIED MONEY, #APPLIEDAMOUNT MONEY, #SYSTEMKEY INT, #LOOPCOUNTER_PR INT, #ROWCOUNTER_PR INT, #IS_UPDATE BIT
SELECT #ROWCOUNTER = COUNT(*) from #TempUserPayments;
SET #LOOPCOUNTER = 1
WHILE (#LOOPCOUNTER <= #ROWCOUNTER)
BEGIN
SELECT *,
ROW_NUMBER() OVER (ORDER BY mp.Month DESC, mp.PayPriority) AS RN_PR
INTO #TempPR
FROM Requests pr
INNER JOIN Premium mp ON mp.PremiumKey = pr.PremiumKey
--Joing couple of other tables here
ORDER BY mp.[Month] DESC, mp.PayPriority
CREATE INDEX PR_1 on #TempPR (RN_PR);
SELECT #SYSTEMKEY = SystemKey FROM #TempUserPayments WHERE RN = #LOOPCOUNTER;
SELECT #ROWCOUNTER_PR = COUNT(*) from #TempPR;
SET #LOOPCOUNTER_PR = 1
WHILE (#LOOPCOUNTER_PR <= #ROWCOUNTER_PR)
BEGIN
SET #IS_UPDATE = 0;
IF(#APPLIEDAMOUNT IS NULL OR (#REQUESTEDAMOUNT <> #APPLIEDAMOUNT AND #APPLIEDAMOUNT < #REQUESTEDAMOUNT))
BEGIN
IF(#SYSTEMKEY = 1 OR #SYSTEMKEY = 3)
BEGIN
--Check all the conditions here
SET #IS_UPDATE = 1;
END
IF(#IS_UPDATE = 1)
BEGIN
--Update bunch of requets tables ---
INSERT INTO userpaid
(
--Column names --
)
VALUES
(
--Values --
)
END
END
-- END
SET #LOOPCOUNTER_PR = #LOOPCOUNTER_PR + 1;
END
SET #LOOPCOUNTER = #LOOPCOUNTER + 1;
DROP TABLE #TempPR;
END
DROP TABLE #TempUserPayments;
END

Related

Batch delete operation procedure not working

I have a stored procedure which looks like following:
alter procedure [dbo].[zsp_deleteEndedItems]
(
#ItemIDList nvarchar(max)
)
as
delete from
SearchedUserItems
WHERE EXISTS (SELECT 1 FROM dbo.SplitStringProduction(#ItemIDList,',') S1 WHERE ItemID=S1.val)
The parameter IDList is passed like following:
124125125,125125125...etc etc
And the split string function look like following:
ALTER FUNCTION [dbo].[SplitStringProduction]
(
#string nvarchar(max),
#delimiter nvarchar(5)
) RETURNS #t TABLE
(
val nvarchar(500)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#string,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(500)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
This is supposed to delete all items from table "SearcheduserItems" under the IDs:
124125125 and 125125125
But for some reason after I do a select to check it out:
select * from SearchedUserItems
where itemid in('124125125','125125125')
The records are still there...
What am I doing wrong here? Can someone help me out?
As mentioned in the comments, a different option would be to use a table type parameter. This makes a couple of assumptions (some commented), however, should get you on the right path:
CREATE TYPE dbo.IDList AS TABLE (ItemID int NOT NULL); --Assumed int datatype;
GO
ALTER PROC dbo.zsp_deleteEndedItems #ItemIDList dbo.IDList READONLY AS
DELETE SUI
FROM dbo.SearchedUserItems SUI
JOIN #ItemIDList IDL ON SUI.ItemID = IDL.ItemID;
GO
--Example of usage
DECLARE #ItemList dbo.IDList;
INSERT INTO #ItemList
VALUES(123456),(123457),(123458);
EXEC dbo.zsp_deleteEndedItems #ItemList;
GO
In regards to the question of an inline table value function, one such example is the below, which I quickly wrote up, that provides a tally table of the next 1000 numbers:
CREATE FUNCTION dbo.NextThousand (#Start int)
RETURNS TABLE
AS RETURN
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 + #Start AS I
FROM N N1 --10
CROSS JOIN N N2 --100
CROSS JOIN N N3; --1,000
GO
The important thing about an iTVF is that it has only one statement, and that is the RETURN statement. Declaring the table as a return type variable, inserting data into it, and returning that variable turns it into a multi-line TVF; which perform far slower.

Duplicate Entry Insertion while insertion of DataTable in Stored Procedure

I'm inserting DataTable in Database using StoredProcedure but the issue is, its inserting twice the actual number of entries of DataTable to be inserted, the procedure is below, kindly guide me, if I'm using wrong approach, why its duplicating the rows? The return which is required is working fine.
Thanks In Advance
ALTER PROCEDURE [dbo].[proc_InsertStore_Recvry]
(#dt_Recovery Recovery_Store READONLY)
AS
Declare #RecoveryIDs as Table (IDs int, ClientIds int)
declare #StoreID int
declare #ClientID int
declare #Arrears decimal(18, 2)
declare #NetDues decimal(18, 2)
declare #Received decimal(18, 2)
Declare #RecoveryRecID int
begin
select * into #tempTable from #dt_Recovery
declare #Count int
set #Count= (select COUNT(*) from #tempTable)
while(#Count > 0)
begin
set #Count = #Count-1
set #ClientID = (Select top 1 ClientID from #tempTable)
set #StoredID = (Select top 1 StoredID from #tempTable where ClientID=#ClientID)
set #Arrears = (Select top 1 Arrears from #tempTable where ClientID=#ClientID)
set #NetDues = (Select top 1 NDues from #tempTable where ClientID=#ClientID)
set #Received = (Select top 1 Received from #tempTable where ClientID=#ClientID)
Insert into tblRecovery (StoreID, ClientID, Arrears, NetDues, Received)
values (#StoreID,#ClientID,#Arrears,#NetDues,#Received)
select #RecoveryID = Scope_Identity()
insert into #RecoveryIDs (IDs,ClientIds) values (#RecoveryID, #ClientID )
delete from #tempTable where ClientID=#ClientID
end
Select * from #RecoveryIDs
it looks like you are using SQL Server. If yes then why are you using a while-loop to insert values into a table and return the inserted Ids?
The same can be accomplished in a far better way via the OUTPUT clause:
OUTPUT documentation
Example:
INSERT INTO tblRecovery(StoreID, ClientID, Arrears, NetDues, Received) OUTPUT INSERTED.ID, INSERTED.CLientId INTO #RecoveryIDs(IDs, ClientIds) SELECT StoredID, ClientID, Arrears, NDues, Received FROM #tempTable
Aside from that there seems to be no issue with your SQL code. So could you post the .NET code as well?

(Additional) EF can't infer return schema from stored procedure selecting from a #temp table

I have a similar problem as described in
EF can't infer return schema from Stored Procedure selecting from a #temp table
and I have created my stored procedure solution based on the solution described above BUT I am still getting a similar EF error and I really don't know why or understand how I can fix it.
A member of the type, 'rowNum', does not have a corresponding column in the data reader with the same name.
My specific error:
The data reader is incompatible with the specified 'TestModel.sp_SoInfoDocs_Result'. A member of the type, 'rowNum', does not have a corresponding column in the data reader with the same name.
My stored procedure:
ALTER PROCEDURE [dbo].[sp_SoInfoDocs]
#searchText nvarchar(200),
#PageNumber int,
#PageSize int
AS
BEGIN
IF 1 = 2
BEGIN
SELECT
cast(null as int ) as rowNum
,cast(null as text) as serverName
,cast(null as text) as jobName
,cast(null as DATETIME) as oDate
,cast(null as int) as runCount
,cast(null as nvarchar(10)) as orderID
,cast(null as text) as applicationName
,cast(null as text) as memberName
,cast(null as text) as nodeID
,cast(null as nvarchar(10)) as endStatus
,cast(null as int) as returnCode
,cast(null as DATETIME) as startTime
,cast(null as DATETIME) as endTime
,cast(null as nvarchar(50)) as status
,cast(null as text) as owner
,cast(null as bit) as existsNote
WHERE
1 = 2
END
DECLARE #LowerLimit int;
SET #LowerLimit = (#PageNumber - 1) * #PageSize;
DECLARE #UpperLimit int;
SET #UpperLimit = #PageNumber * #PageSize;
PRINT CAST (#LowerLimit as varchar)
PRINT CAST (#UpperLimit as varchar)
SELECT ROW_NUMBER() over (order by Expr1) as rowNum, *
into #temp
from (
SELECT dbo.SOInfo.jobName, dbo.SOInfo.nodeID, dbo.SOInfo.nodeGroup, dbo.SOInfo.endStatus, dbo.SOInfo.returnCode, dbo.SOInfo.startTime, dbo.SOInfo.endTime,
dbo.SOInfo.oDate, dbo.SOInfo.orderID, dbo.SOInfo.status, dbo.SOInfo.runCount, dbo.SOInfo.owner, dbo.SOInfo.cyclic, dbo.SOInfo.soInfoID, dbo.SOInfo.docInfoID,
dbo.SOInfo.existsNote, dbo.SOInfo.noSysout, dbo.serverInfo.serverName, dbo.Groups.label AS applicationName, Groups_1.label AS memberName,
Groups_2.label AS groupName, Groups_3.label AS scheduleTableName, dbo.SOInfo.serverInfoID, dbo.SOInfo.applicationID, dbo.SOInfo.groupID,
dbo.SOInfo.memberID, dbo.SOInfo.scheduleTableID, dbo.docFile.docFileID, dbo.docInfo.docInfoID AS Expr1, dbo.docFile.docFileObject
FROM dbo.SOInfo INNER JOIN
dbo.serverInfo ON dbo.SOInfo.serverInfoID = dbo.serverInfo.serverInfoID INNER JOIN
dbo.docInfo ON dbo.SOInfo.docInfoID = dbo.docInfo.docInfoID INNER JOIN
dbo.docFile ON dbo.docInfo.docFileID = dbo.docFile.docFileID LEFT OUTER JOIN
dbo.Groups AS Groups_3 ON dbo.SOInfo.scheduleTableID = Groups_3.ID LEFT OUTER JOIN
dbo.Groups AS Groups_1 ON dbo.SOInfo.memberID = Groups_1.ID LEFT OUTER JOIN
dbo.Groups AS Groups_2 ON dbo.SOInfo.groupID = Groups_2.ID LEFT OUTER JOIN
dbo.Groups ON dbo.SOInfo.applicationID = dbo.Groups.ID
WHERE CONTAINS (docfileObject,#searchText)
) tbl
SELECT Count(1) FROM #temp
SELECT rowNum, serverName, jobName ,oDate,runCount,orderID,applicationName,memberName,nodeID, endStatus, returnCode,startTime,endTime,status,owner,existsNote
FROM #temp WHERE rowNum > #LowerLimit AND rowNum <= #UpperLimit
END
My overall goals are:
search through clustered indexed table (docInfo) and find all rows that contain a specific search string value
at the same time capture metadata from other tables associated with each docInfo object
The results of actions (1) and (2) above are written to a #temp table to which I then add a rowNum column to enable me to introduce paging i.e.
introduce paging for the number of metadata results that can be returned at any one time, based on supplied PageNumber and PageSize variables.
What does work
I am able to successfully create the stored procedure.
Within SSMS I am able to successfully execute the stored procedure and it delivers the results I expect, here's an example
Within EF I have been able to update and import the stored procedure by updating from database
Within EF I am then able to see the Function Imports and can see the Mapping
Within ED I am then able to see the generated complex types
I use the following code to call the process
using (TestEntities context = new TestEntities())
{
List<sp_SoInfoDocs_Result> lst = context.sp_SoInfoDocs(searchText, 1, 10).ToList();
}
I compile and run my solution and get the following error from EF
'System.Data.Entity.Core.EntityCommandExecutionException' occurred in EntityFramework.SqlServer.dll
Additional information: The data reader is incompatible with the specified 'SysviewModel.sp_SoInfoDocs_Result'. A member of the type, 'rowNum', does not have a corresponding column in the data reader with the same name.
I am very much a novice / basic user when it comes to both SSMS / SQL and EF, this has stretched me as far as I understand / can go and I really don't know where to turn to next in order to resolve this problem.
I've searched extensively through SO and can see others who have had similar problems and have tried the solutions suggested but nothing seems to work for me.
I really would be very very grateful to anyone who could help me understand
what is it that is wrong / I've done wrong?
is there a better approach to achieve what I need?
ideas as to how I can fix this.
Thanks in advance
The solution I have found is to use SQL temporary variables in stead of using temporary tables which then enables me to expose the table columns via my final SQL Select statement and then consume them as meta data in EF using the "Add Function Imports" function.
Here's an example of my successfully working sp.
USE [TestDB]
GO
/****** Object: StoredProcedure [dbo].[sp_SoInfoDocs_Archive] Script Date: 09/07/2015 10:35:43 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_SoInfoDocs_Archive]
#searchText nvarchar(200),
#PageNumber int,
#PageSize int,
#out int = 0 output
AS
BEGIN
DECLARE #LowerLimit int;
SET #LowerLimit = (#PageNumber - 1) * #PageSize;
DECLARE #UpperLimit int;
SET #UpperLimit = #PageNumber * #PageSize;
-- Create temporary column variables
Declare #temp TABLE
(
rowNum INT,
jobName text,
nodeID nvarchar(50),
nodeGroup text,
endStatus nvarchar(10),
returnCode int,
startTime datetime,
endTime datetime,
oDate smalldatetime,
orderID nvarchar(10),
status nvarchar(50),
runCount int,
owner text,
cyclic text,
soInfoID int,
docInfoID int,
existsNote bit,
noSysout bit,
serverName varchar(256),
applicationName nvarchar(255),
memberName nvarchar(255),
groupName nvarchar(255),
scheduleTableName nvarchar(255),
serverInfoID int,
applicationID int,
groupID int,
memberID int,
scheduleTableID int,
docFileID int,
Expr1 int,
docFileObject varbinary(MAX)
)
INSERT INTO #temp
SELECT ROW_NUMBER() over (order by Expr1) as rowNum, *
from (
SELECT dbo.SOInfoArchive.jobName,
dbo.SOInfoArchive.nodeID,
dbo.SOInfoArchive.nodeGroup,
dbo.SOInfoArchive.endStatus,
dbo.SOInfoArchive.returnCode,
dbo.SOInfoArchive.startTime,
dbo.SOInfoArchive.endTime,
dbo.SOInfoArchive.oDate,
dbo.SOInfoArchive.orderID,
dbo.SOInfoArchive.status,
dbo.SOInfoArchive.runCount,
dbo.SOInfoArchive.owner,
dbo.SOInfoArchive.cyclic,
dbo.SOInfoArchive.soInfoID,
dbo.SOInfoArchive.docInfoID,
dbo.SOInfoArchive.existsNote,
dbo.SOInfoArchive.noSysout,
dbo.serverInfo.serverName,
dbo.Groups.label AS applicationName,
Groups_1.label AS memberName,
Groups_2.label AS groupName,
Groups_3.label AS scheduleTableName,
dbo.SOInfoArchive.serverInfoID,
dbo.SOInfoArchive.applicationID,
dbo.SOInfoArchive.groupID,
dbo.SOInfoArchive.memberID,
dbo.SOInfoArchive.scheduleTableID,
dbo.docFile.docFileID,
dbo.docInfo.docInfoID AS Expr1,
dbo.docFile.docFileObject
FROM dbo.SOInfoArchive INNER JOIN
dbo.serverInfo ON dbo.SOInfoArchive.serverInfoID = dbo.serverInfo.serverInfoID INNER JOIN
dbo.docInfo ON dbo.SOInfoArchive.docInfoID = dbo.docInfo.docInfoID INNER JOIN
dbo.docFile ON dbo.docInfo.docFileID = dbo.docFile.docFileID LEFT OUTER JOIN
dbo.Groups AS Groups_3 ON dbo.SOInfoArchive.scheduleTableID = Groups_3.ID LEFT OUTER JOIN
dbo.Groups AS Groups_1 ON dbo.SOInfoArchive.memberID = Groups_1.ID LEFT OUTER JOIN
dbo.Groups AS Groups_2 ON dbo.SOInfoArchive.groupID = Groups_2.ID LEFT OUTER JOIN
dbo.Groups ON dbo.SOInfoArchive.applicationID = dbo.Groups.ID
WHERE CONTAINS (docfileObject,#searchText)
) tbl
-- Select enables me to consume the following columns as meta data in EF
SELECT rowNum,
serverName,
jobName ,
oDate,
runCount,
orderID,
applicationName,
memberName,
nodeID,
endStatus,
returnCode,
startTime,
endTime,
status,
owner,
existsNote,
docFileID
FROM #temp WHERE rowNum > #LowerLimit AND rowNum <= #UpperLimit
END
So to recap, I can now
1) Import the stored procedure into my EDMX.
2) The "Add Function Import" successfully creates
a) sp_SoInfoDocs Function Imports
b) sp_SoInfoDocs Complex Types
I can now successfully call my stored procedure as follows
using (TestEntities context = new TestEntities())
{
string searchText = "rem";
ObjectParameter total = new ObjectParameter("out",typeof(int));
List<sp_SoInfoDocs_Result> lst = context.sp_SoInfoDocs(searchText, 1, 10, total).ToList();
foreach (var item in lst)
{
Console.WriteLine(item.jobName + " " + item.oDate + " " + item.serverName + " " + item.startTime + " " + item.endTime);
}
Console.ReadLine();
}
And an example of the results returned.
I am now successfully using the basis of this process to import and display the metadata in a dynamically created HTML table in my View.
If anyone else is experiencing similar problems and I have neglected to explain fully why I adopted this solution and how I made it work ~ then please feel free to P.M. me and I'll do my best to explain.
Why to use temporary table or table variable at all. Table variables has many performance drawbacks like:
Table variables does not have distribution statistics, they will not
trigger recompiles. Therefore, in many cases, the optimizer will build
a query plan on the assumption that the table variable has no rows.
For this reason, you should be cautious about using a table variable
if you expect a larger number of rows (greater than 100). Temp tables
may be a better solution in this case. Alternatively, for queries that
join the table variable with other tables, use the RECOMPILE hint,
which will cause the optimizer to use the correct cardinatlity for the
table variable.
table variables are not supported in the SQL Server optimizer's
cost-based reasoning model. Therefore, they should not be used when
cost-based choices are required to achieve an efficient query plan.
Temporary tables are preferred when cost-based choices are required.
This typically includes queries with joins, parallelism decisions, and
index selection choices.
Queries that modify table variables do not generate parallel query
execution plans. Performance can be affected when very large table
variables, or table variables in complex queries, are modified. In
these situations, consider using temporary tables instead. For more
information, see CREATE TABLE (Transact-SQL). Queries that read table
variables without modifying them can still be parallelized.
Use simple CTE:
ALTER PROCEDURE [dbo].[sp_SoInfoDocs_Archive]
#searchText NVARCHAR(200),
#PageNumber INT,
#PageSize INT,
#out INT = 0 OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #LowerLimit INT = (#PageNumber - 1) * #PageSize;
DECLARE #UpperLimit INT = #PageNumber * #PageSize;
;WITH cte AS
(
SELECT
dbo.SOInfoArchive.jobName,
dbo.SOInfoArchive.nodeID,
dbo.SOInfoArchive.nodeGroup,
dbo.SOInfoArchive.endStatus,
dbo.SOInfoArchive.returnCode,
dbo.SOInfoArchive.startTime,
dbo.SOInfoArchive.endTime,
dbo.SOInfoArchive.oDate,
dbo.SOInfoArchive.orderID,
dbo.SOInfoArchive.status,
dbo.SOInfoArchive.runCount,
dbo.SOInfoArchive.owner,
dbo.SOInfoArchive.cyclic,
dbo.SOInfoArchive.soInfoID,
dbo.SOInfoArchive.docInfoID,
dbo.SOInfoArchive.existsNote,
dbo.SOInfoArchive.noSysout,
dbo.serverInfo.serverName,
dbo.Groups.label AS applicationName,
Groups_1.label AS memberName,
Groups_2.label AS groupName,
Groups_3.label AS scheduleTableName,
dbo.SOInfoArchive.serverInfoID,
dbo.SOInfoArchive.applicationID,
dbo.SOInfoArchive.groupID,
dbo.SOInfoArchive.memberID,
dbo.SOInfoArchive.scheduleTableID,
dbo.docFile.docFileID,
dbo.docInfo.docInfoID AS Expr1,
dbo.docFile.docFileObject
FROM dbo.SOInfoArchive
JOIN dbo.serverInfo
ON dbo.SOInfoArchive.serverInfoID = dbo.serverInfo.serverInfoID
JOIN dbo.docInfo
ON dbo.SOInfoArchive.docInfoID = dbo.docInfo.docInfoID
JOIN dbo.docFile
ON dbo.docInfo.docFileID = dbo.docFile.docFileID
LEFT JOIN dbo.Groups AS Groups_3
ON dbo.SOInfoArchive.scheduleTableID = Groups_3.ID
LEFT JOIN dbo.Groups AS Groups_1
ON dbo.SOInfoArchive.memberID = Groups_1.ID
LEFT JOIN dbo.Groups AS Groups_2
ON dbo.SOInfoArchive.groupID = Groups_2.ID
LEFT JOIN dbo.Groups
ON dbo.SOInfoArchive.applicationID = dbo.Groups.ID
WHERE CONTAINS (docfileObject,#searchText)
),
cte2 AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Expr1) AS rowNum, *
FROM cte
)
SELECT rowNum,
serverName,
jobName ,
oDate,
runCount,
orderID,
applicationName,
memberName,
nodeID,
endStatus,
returnCode,
startTime,
endTime,
status,
owner,
existsNote,
docFileID
FROM cte2
WHERE rowNum > #LowerLimit
AND rowNum <= #UpperLimit
END
Probably nobody cares anymore, but the reason it didn't work is that ROW_NUMBER() returns a BIGINT and your code defines the structure this way: cast(null as int) as rowNum

Run a foreach loop on returned results in a stored procedure.

I have a stored procedure that returns a result from table1, I then use that result in another stored procedure to update table2. I was wondering if I could combine this into one stored procedure, the problem is that it would need to run a foreach loop on table2 based on the value from table1 to find all the values to update. No idea where to start or if its even possible.
This is example of running something for each item. But for update you should not need this, you should be able to solve this with CROSS JOIN, anyway example below.
declare #i int;
set #i = 0;
declare #t table
(
int id identity,
varchar(max) col1,
varchar(max) col2
)
insert into #t(col1,col2)
select col1,
col2 from FROM [Table]
set i = 0;
declare #sum int
set #sum = 0;
while(i < select count(*) from #t)
begin
select case when (col1 == col2) then #sum = #sum+1 else #sum from #t
where id = i;
i++;
end

How do I select few random values from a datatable column

I have a DataTable with a column studentid. It has 1000 records. I need to select some 30 random ids and insert them into a database table. Then, I need to exclude these 30 ids, select another 30 random ids, and... so on until 1000 records.
And, in every iteration, I will have a given number of ids, so only that many ids should be selected (the 300 is not constant, it may be 30, 25, 23, 24...).
This may get you started:
--Create a temporary table
CREATE TABLE #temp (id INTEGER)
go
--Insert 30 randow ids into the #temp table, excluding any ids that were previously picked.
-- run this line as many times as needed.
INSERT INTO #temp select top 30 id from [student] where [id] not in (select [id] from #temp) order by newid()
Create an array with the 1,000 student ids and shuffle the array. Then just start at the beginning of the array and go forward.
If you have to be persistent, you can write the contents of the array to a temporary table and step through it sequentially.
Or, you could do:
SELECT id FROM table
ORDER BY RAND()
and write it to a temp table. I don't remember the SQL syntax. SELECT INTO? That'll put all of the id's into a table in random order and then you can pick them out 30 at a time, starting at the beginning.
The use of some dynamic SQL may be tolerable here.
IF ( OBJECT_ID( 'tempdb.dbo.#t_Student' ) IS NULL )
BEGIN
CREATE TABLE #t_Student
(
StudentID INTEGER
);
SET NOCOUNT ON;
DECLARE #i INTEGER;
SET #i = 0;
WHILE ( #i < 1000 )
BEGIN
INSERT INTO #t_Student ( StudentID )
VALUES ( #i );
SET #i = #i + 1;
END;
SET NOCOUNT OFF;
END;
IF ( OBJECT_ID( 'tempdb.dbo.#t_Processed' ) IS NOT NULL )
BEGIN
DROP TABLE #t_Processed;
END;
DECLARE #Random INTEGER,
#LowerBound INTEGER,
#UpperBound INTEGER,
#SQL NVARCHAR( MAX );
SET #LowerBound = 1;
SET #UpperBound = 30;
CREATE TABLE #t_Processed
(
StudentID INTEGER
);
WHILE ( ( SELECT COUNT( 1 )
FROM dbo.#t_Processed ) <
( SELECT COUNT( 1 )
FROM dbo.#t_Student ) )
BEGIN
SET #Random = ROUND( ( ( #UpperBound - #LowerBound ) * RAND() + #LowerBound ), 0 );
SET #SQL = '
SELECT TOP ' + LEFT( #Random, 10 ) + ' StudentID
FROM #t_Student
WHERE StudentID NOT IN ( SELECT StudentID
FROM #t_Processed )
ORDER BY NEWID();';
INSERT INTO #t_Processed ( StudentID )
EXECUTE dbo.sp_executesql #statement = #SQL;
END;
GO

Categories

Resources