In SQL management studio I want to add trigger to one table. When data is inserted into table I want to insert or update data in other tables if some requirements are met.
ALTER trigger [dbo].[game_data_received]
on [dbo].[GameRounds]
for insert
as
declare #LastDate datetime
declare #CurrDate datetime
declare #GameID int
declare #TimeDiff int
declare #CurrMult int
declare #MaxMult int
declare #MaxDiff int
select #GameID = i.GameID from inserted i
select #CurrDate = i.[TimeStamp] from inserted i
select #CurrMult = i.MaxMultiplier from inserted i
select #LastDate = max([TimeStamp]) from GameRounds where GameID = #GameID
select #TimeDiff = DATEDIFF(SECOND, #LastDate, #CurrDate)
select #MaxDiff = (select TimeDifference from AcceptableTimeDiffGameRounds where GameID = #GameID)
select #MaxMult = (select MaxMultiplier from AcceptableTimeDiffGameRounds where GameID = #GameID)
if #TimeDiff > #MaxDiff
if #CurrMult >= #MaxMult
update AcceptableTimeDiffGameRounds
set MaxMultiplier = #CurrMult, TimeDifference = #TimeDiff
where GameID = #GameID
else
insert into Defects(DownFrom, DownUntill)
values (#LastDate, #CurrDate)
So this is my trigger query. Data in the GameRounds table are inserted automaticaly, but it doesn't executes this whole procedure. I can tell this because, when I select the tables where data should be added or change they stay same. Thanks in advance.
SQL Server is a SET-based language and the best practice is to handle it as such.
A commom misconception with triggers is that they're only responsible for 1 row, however, an insert trigger could have 1 or thousands of rows. The trigger you shared only handles a single inserted record (last in)--not all of them.
While I would not recommend handling your request this way--it really should be written to handle the entire inserted rowset at once--I have had to process an insert row-by-agonizing-row (RBAR) in the past. In this case, you could try rewriting your trigger like the following:
DECLARE #GameID int, #CurrDate datetime, #CurrMult int, #row_id int = 1;
DECLARE #inserted_rows table (
GameID int, CurrDate datetime, CurrMult int, row_id int IDENTITY ( 1, 1 )
);
INSERT INTO #inserted_rows (
GameID, CurrDate, CurrMult
)
SELECT
GameID, [TimeStamp], MaxMultiplier
FROM inserted;
WHILE #row_id <= ( SELECT MAX ( row_id ) FROM #inserted_rows )
BEGIN
-- Current row.
SELECT
#GameID = GameID, #CurrDate = CurrDate, #CurrMult = CurrMult
FROM #inserted_rows WHERE row_id = #row_id;
-- Process row.
DECLARE #MaxMult int, #MaxDiff int;
DECLARE #LastDate datetime = ( SELECT MAX( [TimeStamp] ) FROM GameRounds WHERE GameID = #GameID );
DECLARE #TimeDiff int = DATEDIFF( second, #LastDate, #CurrDate);
SELECT
#MaxMult = MaxMultiplier,
#MaxDiff = TimeDifference
FROM AcceptableTimeDiffGameRounds
WHERE
GameID = #GameID;
IF #TimeDiff > #MaxDiff
BEGIN
IF #CurrMult >= #MaxMult
UPDATE AcceptableTimeDiffGameRounds
SET
MaxMultiplier = #CurrMult,
TimeDifference = #TimeDiff
WHERE GameID = #GameID;
END
ELSE
BEGIN
INSERT INTO Defects ( DownFrom, DownUntill )
VALUES ( #LastDate, #CurrDate );
END
-- Next row.
SET #row_id = #row_id + 1;
END
This example captures all of the inserted rows and then processes them one at a time (without using a cursor)--which can be painfully slow, not to mention locking issues.
Ideally, you would rewrite this using a set-based approach.
Related
I get an error
Subquery returned more than 1 value
when executing a stored procedure. I need to copy data from the database I am building to the live database. The code inserted the data into TestTextmessage table and updateed TextMessage table. The error occurred when try to insert into the TestMobileRecipient table that is the reason why the table is empty.
The table structure and code are below
Stored procedure
CREATE PROCEDURE [dbo].[TestSendITMessage]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #i int
DECLARE #idmessage int
DECLARE #numrows int
DECLARE #messagehold TABLE
(
idx SMALLINT PRIMARY KEY IDENTITY(1,1),
MessageId INT
)
DECLARE #InsertedID INT
INSERT INTO #messagehold
SELECT DISTINCT Id
FROM [MPFT_SendIT].dbo.TextMessage
WHERE DontSendBefore < GETDATE()
AND DateSent IS NULL
AND MessageSent = 0
SET #i = 1
SET #numrows = (SELECT COUNT(*) FROM #messagehold)
IF #numrows > 0
WHILE (#i <= (SELECT MAX(idx) FROM #messagehold))
BEGIN
SET #idmessage = (SELECT MessageId FROM #messagehold WHERE idx = #i)
--Do something with Id here
PRINT #idmessage
INSERT INTO [dbo].[TestTextMessage] ([Origin], [MessageBody], [MessageSent], [DateCreated], DontSendBefore)
SELECT
'LogIT', MessageBody, 0, GETDATE(), DontSendBefore
FROM
[MPFT_SendIT].dbo.TextMessage
WHERE Id = #idmessage
SET #InsertedID = SCOPE_IDENTITY();
INSERT INTO [dbo].[TestMobileRecipient] ([MessageId], MobileNumber])
VALUES (#InsertedID, (SELECT MobileNumber FROM MobileRecipient
WHERE MessageId = #idmessage))
UPDATE TextMessage
SET DateSent = GETDATE(),
MessageSent = 1
WHERE Id = #idmessage
SET #i = #i + 1
END
END
the error message is very clear. Your sub-query SELECT MobileNumber FROM MobileRecipient WHERE MessageId= #idmessage is returning more than 1 row
Change the insertion of table TestMobileRecipient to following
Insert into [dbo].[TestMobileRecipient]
(
[MessageId]
,[MobileNumber]
)
SELECT #InsertedID
, MobileNumber
FROM MobileRecipient
WHERE MessageId= #idmessage
You should update your this line
SET #idmessage = (SELECT MessageId FROM #messagehold WHERE idx = #i)
with
SET #idmessage = (SELECT top 1 MessageId FROM #messagehold WHERE idx = #i)
I have a trigger "after insert/update/delete/". It is supposed to count Balance on Account table based on transactions in Transaction table. It is on Transaction table. I am getting Balance discrepancies rarely, so have decided to add some logging into it. It dumps inserted+deleted tables (they are combined into a table var) and tsql statement which fired it. Judging from my log, it looks like the trigger did not fire for some inserts into Transaction table. Can this happen ? Are there any TSQL statement which change table data without firing trigger (except truncate table etc)?
Here is the trigger :
CREATE TRIGGER [dbo].[trg_AccountBalance]
ON [dbo].[tbl_GLTransaction]
AFTER INSERT, UPDATE, DELETE
AS
set nocount on
begin try
declare #OldOptions int = ##OPTIONS
set xact_abort off
declare #IsDebug bit = 1
declare #CurrentDateTime datetime = getutcdate()
declare #TriggerMessage varchar(max), #TriggerId int
if #IsDebug = 1
begin
select #TriggerId = isnull(max(TriggerId), 0) + 1
from uManageDBLogs.dbo.tbl_TriggerLog
declare #dbcc_INPUTBUFFER table(EventType nvarchar(30), Parameters Int, EventInfo nvarchar(4000) )
declare #my_spid varchar(20) = CAST(##SPID as varchar(20))
insert #dbcc_INPUTBUFFER
exec('DBCC INPUTBUFFER ('+#my_spid+')')
select #TriggerMessage = replace(EventInfo, '''', '''''') from #dbcc_INPUTBUFFER
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
declare #Oper int
select #Oper = 0
-- determine type of sql statement
if exists (select * from inserted) select #Oper = #Oper + 1
if exists (select * from deleted) select #Oper = #Oper + 2
if #IsDebug = 1
begin
select #TriggerMessage = '#Oper = ' + convert(varchar, #Oper)
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
if #Oper = 0 return -- No data changed
declare #TomorrowDate date = dateadd(day, 1, convert(date, getdate()))
declare #CurrentDate date = convert(date, getdate())
-- transactions from both inserted and deleted tables
declare #tbl_Trans table (FirmId int, GLAccountId int,
AmountDebit money, AmountCredit money, "Status" char(1), TableType char(1))
declare #tbl_AccountCounters table (FirmId int, GLAccountId int, Balance money)
declare #IsChange bit = null
insert into #tbl_Trans (FirmId, GLAccountId, AmountDebit, AmountCredit, "Status", TableType)
select FirmId, GLAccountId, AmountDebit, AmountCredit, "Status", 'I'
from inserted
union
select FirmId, GLAccountId, AmountDebit, AmountCredit, "Status", 'D'
from deleted
if #IsDebug = 1
begin
select #TriggerMessage = (select * from #tbl_Trans for xml path ('tbl_Trans'))
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
insert into #tbl_AccountCounters (FirmId, GLAccountId, Balance)
select FirmId, GLAccountId, 0
from #tbl_Trans
group by FirmId, GLAccountId
if #Oper = 1 or #Oper = 2 -- insert/delete
begin
update #tbl_AccountCounters
set Balance = cnt.TransSum
from #tbl_AccountCounters as ac join
(
select trans.FirmId, trans.GLAccountId,
isnull(sum((trans.AmountDebit - trans.AmountCredit) * iif(trans.TableType = 'I', 1, -1)), 0) as TransSum
from #tbl_Trans as trans
where trans.Status = 'A'
group by trans.FirmId, trans.GLAccountId
) as cnt on ac.FirmId = cnt.FirmId and ac.GLAccountId = cnt.GLAccountId
select #IsChange = 1
end
else
begin
if update(AmountDebit) or update(AmountCredit) or update(Status) or update(GLAccountId)
begin
update #tbl_AccountCounters
set Balance = cnt.TransBalance
from #tbl_AccountCounters as ac join
(select trans.FirmId, trans.GLAccountId, isnull(sum(trans.AmountDebit - trans.AmountCredit), 0) as TransBalance
from dbo.tbl_GLTransaction as trans
where trans."Status" = 'A' and exists (select 1 from #tbl_AccountCounters as ac
where ac.GLAccountId = trans.GLAccountId and ac.FirmId = trans.FirmId)
group by trans.FirmId, trans.GLAccountId) as cnt on
ac.FirmId = cnt.FirmId and ac.GLAccountId = cnt.GLAccountId
select #IsChange = 0
end
end
if #IsDebug = 1
begin
select #TriggerMessage = '#IsChange = ' + isnull(convert(varchar, #IsChange), 'null')
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
select #TriggerMessage = (select * from #tbl_AccountCounters for xml path ('tbl_AccountCounters'))
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
if #IsChange is not null
begin
update tbl_GLAccount
set tbl_GLAccount.Balance = iif(#IsChange = 1, cnt.Balance + acc.Balance, cnt.Balance),
tbl_GLAccount.LastUpdate = getutcdate(),
tbl_GLAccount.LastUpdatedBy = 1
from #tbl_AccountCounters as cnt join dbo.tbl_GLAccount as acc on
cnt.FirmId = acc.FirmId and cnt.GLAccountId = acc.GLAccountId
end
if (16384 & #OldOptions) = 16384 set xact_abort on
end try
begin catch
declare #ErrorLine varchar(max)
select #ErrorLine = uManageDb.dbo.udf_GetErrorInfo()
insert into uManageDb.dbo.tbl_TriggerError ("Name", "Message", CreateDate)
values ('AccountingDB..trg_AccountBalance', #ErrorLine, GETUTCDATE())
end catch
I think I've found it. I have this line:
select .. from inserted
union
select .. from deleted
and they inserted 5 trans for $300 and 4 trans $100. I've got 2 records (300 and 100) in my #tbl_Trans (it was in the log). That's probably was the bug. So log hellps and trigger run as it had to.
I'll replace union with union all.
I have created a trigger for a asset_verification. Whenever a new record is inserted in this table, the same record is inserted in the asset_verification_history table because of this trigger.
The trigger is as follows
Create trigger [dbo].[tr_insert_after_asset_verification] on [dbo].[asset_verification]
for insert
As
Begin
declare #verification_id int
declare #id int
declare #audit_id int
declare #date date
declare #status varchar(15)
declare #remarks varchar(200)
declare #creationDate datetime
declare #modificationDate datetime
declare #updatedBy int
declare #audit_action varchar(20)
Select #verification_id = i.verification_id from inserted i
If #verification_id IS NOT NULL
Begin
Select #id = i.id from inserted i
Select #audit_id = i.audit_id from inserted i
Select #date = i.date from inserted i
Select #status = i.status from inserted i
Select #remarks = i.remarks from inserted i
Select #creationDate = i.creationDate from inserted i
Select #modificationDate = i.modificationDate from inserted i
Select #updatedBy = i.updatedBy from inserted i
set #audit_action = 'Insert Record'
INSERT INTO [dbo].[asset_verification_history]
([verification_id]
,[id]
,[audit_id]
,[date]
,[status]
,[remarks]
,[creationDate]
,[modificationDate]
,[updatedBy]
,[audit_action])
VALUES
(#verification_id
,#id
,#audit_id
,#date
,#status
,#remarks
,#creationDate
,#modificationDate
,#updatedBy
,#audit_action)
End
End
When I insert the data in the asset_verification table using a procedure in which OPEN XML is used, then this trigger works only for the first record. For the rest of the records the trigger doesn't work
The procedure is as follows
Create procedure [dbo].[usp_AddVerificationBulkData]
(
#vXML XML
)
As
Begin
DECLARE #DocHandle INT
SET NOCOUNT ON
EXEC sp_xml_preparedocument #DocHandle OUTPUT, #vXML
Update asset_verification
set
audit_id = x.AuditId,
id = x.SerialId,
date = x.VerificationDate,
status = x.Status
,remarks = x.Remarks
,creationDate = x.CreatedOn
,modificationDate = x.ModifiedOn
,updatedBy = x.ModifiedBy
From
asset_verification a
Inner Join
OpenXml(#DocHandle,'/ArrayOfAssetVerificationModel/AssetVerificationModel',2)
With(SerialId int, AuditId int, VerificationDate datetime, Status int, Remarks varchar(200), CreatedOn datetime, ModifiedOn datetime, ModifiedBy int) x
On a.audit_id = x.AuditId where a.id = x.SerialId;
INSERT INTO [dbo].[asset_verification]
([id]
,[audit_id]
,[date]
,[status]
,[remarks]
,[creationDate]
,[modificationDate]
,[updatedBy])
select SerialId,AuditId,VerificationDate,Status,Remarks,CreatedOn,ModifiedOn,ModifiedBy from OpenXml(#DocHandle,'/ArrayOfAssetVerificationModel/AssetVerificationModel',2)
With(SerialId int, AuditId int, VerificationDate datetime, Status int, Remarks varchar(200), CreatedOn datetime, ModifiedOn datetime, ModifiedBy int) x
where SerialId NOT IN (select a.id from asset_verification a where a.audit_id = x.AuditId);
End
Problem:- How to make this trigger work for every record that is inserted through Open XML ?
You've made the classic mistake of thinking that triggers fire once-per-row. They dont, it's once-per-action, so the inserted pseudo table holds all the rows affected by the action. Your trigger needs to work in a set based manner, not row based. Try this;
CREATE TRIGGER [dbo].[tr_insert_after_asset_verification] ON [dbo].[asset_verification] FOR INSERT AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[asset_verification_history]
( [verification_id]
,[id]
,[audit_id]
,[date]
,[status]
,[remarks]
,[creationDate]
,[modificationDate]
,[updatedBy]
,[audit_action]
)
SELECT i.verification_id
,i.id
,i.audit_id
,i.date
,i.status
,i.remarks
,i.creationDate
,i.modificationDate
,i.updatedBy
,'Insert Record'
FROM inserted i
WHERE i.verification_id IS NOT NULL
END
As an aside, and strictly speaking, your original trigger will log one row, not necessarily the first.
and thanks in advance
I have a very basic stored procedure that inserts a row into a table. It has been working flawlessly until today
Here is the script
(
#emp varchar(16),
#logdate date,
#logtime time,
#term char(20),
#SSID char(16)
)
AS
INSERT INTO AccessLog (EmployeeID, LogDate, LogTime, TerminalID, InOut, ChangedBy)
VALUES (#emp, #logdate, #logtime, #term, 3, #SSID)
When the string of 5118 is passed to it the insert will fail. There are several triggers that fire after this insert finishes.
Here's the strange part. You can pass it anyother number for the #emp variable and it works just fine, but pass it 5118, it fails.
The error I receive is below:
Msg 241, Level 16, State 1, Procedure UpdateTimeWorked, Line 27
Conversion failed when converting date and/or time from character
string.
Here is the procedure that is failing – the highlighted line is Line 27
TRIGGER [dbo].[UpdateTimeWorked] ON [dbo].[TimeLog]
FOR UPDATE
AS
SET NOCOUNT ON
DECLARE #ID int;
DECLARE #RCDIDIN int;
DECLARE #RCDIDOUT int;
DECLARE #ComboIn datetime;
DECLARE #ComboOut datetime;
SELECT #ID = ID FROM INSERTED;
SELECT #ComboIn = LoginCombo FROM INSERTED;
SELECT #ComboOut = LogoutCombo FROM INSERTED;
SELECT #RCDIDIN = RCDIDIN FROM INSERTED;
SELECT #RCDIDOUT = RCDIDOUT FROM INSERTED;
**IF ( UPDATE(LogoutCombo))**
BEGIN
IF (#RCDIDOUT != 0)
BEGIN
UPDATE TimeLog
SET LogOutRND = (select CAST(dbo.roundtime(LogOutRND,0.25) AS TIME))
WHERE ID = #ID
UPDATE TimeLog
SET LogOutComboRND = CAST(CAST(LogOutDate AS DATE) AS SMALLDATETIME) + CAST(LogOutRND AS TIME)
WHERE ID = #ID
UPDATE TimeLog
SET TimeWorked = dbo.gettime(DATEDIFF(ss,LogInComboRND,LogoutComboRND))
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
UPDATE TimeLog
SET TimeWorked = (select CAST(dbo.roundtime(TimeWorked,0.25) AS TIME)), Rounded = 1
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
END
END
IF ( UPDATE(LoginCombo))
BEGIN
IF (#RCDIDIN != 0)
BEGIN
UPDATE TimeLog
SET LogInRND = (select CAST(dbo.roundtime(LogInRND,0.25) AS TIME))
WHERE ID = #ID
UPDATE TimeLog
SET LogInComboRND = CAST(CAST(LogInDate AS DATE) AS SMALLDATETIME) + CAST(LogInRND AS TIME)
WHERE ID = #ID
UPDATE TimeLog
SET TimeWorked = dbo.gettime(DATEDIFF(ss,LogInComboRND,LogoutComboRND))
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
UPDATE TimeLog
SET TimeWorked = (select CAST(dbo.roundtime(TimeWorked,0.25) AS TIME)), Rounded = 1
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
END
END
GO
I am at a total blank to come up with why this is not working.
Anyone have any ideas?
Like I stated, pass it any other entry for the #emp and it will run fine. I can even pass it ‘5118’ and it will work, but not 5118.
Hello and good afternoon. I am facing an issue with the system i support. I am able to build "Macros" which can pull sql views to a document. The issue is that the columns for these views do not wrap on the document. If a certain row contains too much text, it will push the other columns out of line. To resolve this i am trying to build a function to use with my view that will sort of brute force the wrap by looping through each row and creating an additional row to hold text for certain columns where the text limit was reached. I have something that works, but it's terribly slow at times. Does anyone have any ideas on how i can optimize this?
(
)
RETURNS #medlist2 TABLE (uniq_id UNIQUEIDENTIFIER, enterprise_id CHAR(5), practice_id CHAR (4), person_id UNIQUEIDENTIFIER,
enc_id UNIQUEIDENTIFIER, medication_name VARCHAR (70), sig_desc VARCHAR (512), start_date VARCHAR(10), row_num INT)
AS
BEGIN
DECLARE #medlist TABLE (uniq_id UNIQUEIDENTIFIER, enterprise_id CHAR (5), practice_id CHAR (4), person_id UNIQUEIDENTIFIER,
enc_id UNIQUEIDENTIFIER, medication_name VARCHAR (70), sig_desc VARCHAR (512), start_date DATETIME, processed INT)
DECLARE #medicationName VARCHAR (70)
DECLARE #sigDesc VARCHAR (512)
DECLARE #startDate VARCHAR (10)
DECLARE #uniqID UNIQUEIDENTIFIER
DECLARE #enterpriseID CHAR (5)
DECLARE #practiceID CHAR (4)
DECLARE #personID UNIQUEIDENTIFIER
DECLARE #encID UNIQUEIDENTIFIER
DECLARE #RowNum INT
DECLARE #RowCount INT
INSERT INTO #medlist (uniq_id, enterprise_id, practice_id, person_id,
enc_id, medication_name, sig_desc, start_date, processed)
SELECT uniq_id, enterprise_id, practice_id, person_id,
enc_id, medication_name, sig_desc, start_date, 0
FROM med_table
WHERE person_id IN (select distinct person_id from active_users where create_timestamp > GETDATE()-.2)
AND date_stopped = ''
ORDER BY medication_name
SET #RowCount = (SELECT COUNT(*) FROM #medlist WHERE processed = 0)
SET #RowNum = 0
WHILE #RowCount > 0
BEGIN
SET #RowNum = #RowNum + 1
SELECT TOP(1) #uniqid = uniq_id, #enterpriseID = enterprise_id, #practiceID = practice_id,
#personID = person_id, #encID = enc_id, #medicationName = '- ' +medication_name, #sigDesc = sig_desc,
#startDate = CONVERT(VARCHAR(10), start_date, 101)
FROM #medlist
WHERE processed = 0
INSERT INTO #medlist2(uniq_id, enterprise_id, practice_id, person_id,
enc_id, start_date, row_num, medication_name, sig_desc)
SELECT #uniqID, #enterpriseID, #practiceID, #personID, #encID, #startDate, #RowNum,
(CASE WHEN DATALENGTH(#medicationName) > 28 THEN LEFT(#medicationNAME, 28) + '-' ELSE #medicationName END),
(CASE WHEN DATALENGTH(#sigDesc) > 41 THEN LEFT(#sigDesc, 41) + '-' ELSE #sigDesc END)
WHILE DATALENGTH(#sigDesc) > 42 OR DATALENGTH(#medicationName) > 29
BEGIN
SET #medicationName = substring(#medicationName, 29,DATALENGTH(#medicationName))
SET #sigDesc = substring(#sigDesc, 42,DATALENGTH(#sigDesc))
SET #RowNum = #RowNum + 1
INSERT INTO #medlist2 (uniq_id, enterprise_id, practice_id, person_id,
enc_id, medication_name, sig_desc, row_num)
SELECT #uniqID, #enterpriseID, #practiceID, #personID, #encID, LEFT(#medicationNAME, 28), LEFT(#sigDesc, 41), #RowNum
IF DATALENGTH(#sigDesc) < 42 OR DATALENGTH(#medicationName) > 29
BREAK
ELSE
CONTINUE
END
UPDATE #medlist
SET processed = 1
WHERE uniq_id = #uniqID
SET #RowCount = (SELECT COUNT(*) FROM #medlist WHERE processed = 0)
IF #RowCount = 0
BREAK
ELSE
CONTINUE
END
RETURN
END
Don't do this in the database. Do it in your application layer!
Something as trivial as wrapping text is extremely expensive when SQL server is doing it on a row-by-row bases, but should be very quick to do in whatever application is displaying your results.