Apologies if my question seems to be naive:
I cannot get my head around the 2 statements below, can someone please explain the difference:
OUTPUT $ACTION, INSERTED.BuildRequestID, ..... and
PRINT ##ROWCOUNT
Apparently, they both can be used to print something on the window, with output in the example above, the records that have been inserted will be displayed. And, PRINT ##ROWCOUNT returns the number of rows affected by the last executed statement in the batch, so, if the function was insert, then it will show the inserted records?
Thank you,
In its simplest terms, OUTPUT will give you the actual records affected by a DML statement (INSERT, UPDATE, DELETE, MERGE), ##ROWCOUNT will just tell you how many rows were affected by the previous Statement (not limited to DML).
This is probably easiest understood with a working example that you can run yourself and see both in action:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromDropTable INT = ##ROWCOUNT;
-- CREATE A TABLE
CREATE TABLE #T (ID INT NOT NULL PRIMARY KEY, Col CHAR(1) NOT NULL);
-- INSERT SOME VALUES AND CHECK THE OUTPUT
INSERT #T (ID, Col)
OUTPUT inserted.*
VALUES (1, 'A'), (2, 'B'), (3, 'C');
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromInsert INT = ##ROWCOUNT;
-- DELETE A VALUE AND INSPECT THE DELETED RECORD WITH OUTPUT
DELETE #T
OUTPUT deleted.*
WHERE ID = 3;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromDelete INT = ##ROWCOUNT;
-- UPDATE A RECORD AND VIEW BEFORE AND AFTER VALUES
UPDATE #T
SET Col = 'X'
OUTPUT inserted.ID AS ID,
inserted.Col AS UpdatedTo,
deleted.Col AS UpdatedFrom
WHERE ID = 2;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromUpdate INT = ##ROWCOUNT;
-- USE MERGE, AND CAPTURE ACTION:
MERGE #T AS t
USING (VALUES (2, 'B'), (3, 'C')) AS s (ID, Col)
ON s.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, Col) VALUES (s.ID, s.Col)
WHEN MATCHED THEN UPDATE SET Col = s.Col
WHEN NOT MATCHED BY SOURCE THEN DELETE
OUTPUT $Action AS DMLAction,
inserted.ID AS InsertedID,
inserted.Col AS InsertedCol,
deleted.ID AS DeletedID,
deleted.Col AS DeletedCol;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromMerge INT = ##ROWCOUNT;
SELECT RowCountFromDropTable = #RowCountFromDropTable,
RowCountFromInsert = #RowCountFromInsert,
RowCountFromDelete = #RowCountFromDelete,
RowCountFromUpdate = #RowCountFromUpdate,
RowCountFromMerge = #RowCountFromMerge;
The recordsets output from each of the DML are:
INSERT
ID Col
-------
1 A
2 B
3 C
DELETE
ID Col
-------
3 C
UPDATE
ID UpdatedTo UpdatedFrom
---------------------------
2 X B
MERGE
DMLAction InsertedID InsertedCol DeletedID DeletedCol
------------------------------------------------------------
INSERT 3 C NULL NULL
DELETE NULL NULL 1 A
UPDATE 2 B 2 X
INSPECT ##ROWCOUNTS
RowCountFromDropTable RowCountFromInsert RowCountFromUpdate RowCountFromMerge
--------------------------------------------------------------------------------
0 3 1 3
A quick point on some wording in the qeustion too: You cannot use OUTPUT directly to print something to the window, it returns records much like a SELECT statement. ##ROWCOUNT can be used like any scalar function, so you could use this in consecutive statements. So you could do something like this:
SELECT TOP (1) *
FROM (VALUES (1), (2), (3)) AS t (ID);
SELECT TOP (##ROWCOUNT + 1) *
FROM (VALUES (1), (2), (3)) AS t (ID);
SELECT TOP (##ROWCOUNT + 1) *
FROM (VALUES (1), (2), (3)) AS t (ID);
Which returns 1, 1,2 and 1,2,3 respectively. I have no idea why you would want to do this, but it demonstrates the scope of ##ROWCOUNT a bit better than the above, and how it can be used elsewhere.
Related
I have a table that regroups some users and which event (as in IRL event) they've joined.
I have set up a server query that lets a user join an event.
It goes like this :
INSERT INTO participations
VALUES(:usr,:event_id)
I want that statement to also return the number of people who have joined the same event as the user. How do I proceed? If possible in one SQL statement.
Thanks
You can use a common table expression like this to execute it as one query.
with insert_tbl_statement as (
insert into tbl values (4, 1) returning event_id
)
select (count(*) + 1) as event_count from tbl where event_id = (select event_id from insert_tbl_statement);
see demo http://rextester.com/BUF16406
You can use a function, I've set up next example, but keep in mind you must add 1 to the final count because still transaction hasn't been committed.
create table tbl(id int, event_id int);
✓
insert into tbl values (1, 2),(2, 2),(3, 3);
3 rows affected
create function new_tbl(id int, event_id int)
returns bigint as $$
insert into tbl values ($1, $2);
select count(*) + 1 from tbl where event_id = $2;
$$ language sql;
✓
select new_tbl(4, 2);
| new_tbl |
| ------: |
| 4 |
db<>fiddle here
I have an update statement with returning statement.
What I want is to put the result into a variable or temp table.
update my_table
set something = 'x'
where id ...
returning *;
I tried to that using the execute statement and e.g. returning row_to_json(my_table.*) but the variable, off cause, only contains the first row returned from the update.
Use a CTE with the UPDATE-RETURNING statement in it, then use that in the INSERT:
CREATE TEMP TABLE t (i int, j int);
CREATE TEMP TABLE u (i int, j int);
INSERT INTO u VALUES (1, 1), (2, 2), (3, 3);
WITH updated AS (
UPDATE u
SET i = i * 10
WHERE i < 3
RETURNING *
)
INSERT INTO t
SELECT *
FROM updated;
SQLFiddle
I am going to use MERGE to insert or update a table depending upon ehether it's exist or not. This is my query,
declare #t table
(
id int,
name varchar(10)
)
insert into #t values(1,'a')
MERGE INTO #t t1
USING (SELECT id FROM #t WHERE ID = 2) t2 ON (t1.id = t2.id)
WHEN MATCHED THEN
UPDATE SET name = 'd', id = 3
WHEN NOT MATCHED THEN
INSERT (id, name)
VALUES (2, 'b');
select * from #t;
The result is,
id name
1 a
I think it should be,
id name
1 a
2 b
You have your USING part slightly messed up, that's where to put what you want to match against (although in this case you're only using id)
declare #t table
(
id int,
name varchar(10)
)
insert into #t values(1,'a')
MERGE INTO #t t1
USING (SELECT 2, 'b') AS t2 (id, name) ON (t1.id = t2.id)
WHEN MATCHED THEN
UPDATE SET name = 'd', id = 3
WHEN NOT MATCHED THEN
INSERT (id, name)
VALUES (2, 'b');
select * from #t;
As Mikhail pointed out, your query in the USING clause doesn't contain any rows.
If you want to do an upsert, put the new data into the USING clause:
MERGE INTO #t t1
USING (SELECT 2 as id, 'b' as name) t2 ON (t1.id = t2.id) --This no longer has an artificial dependency on #t
WHEN MATCHED THEN
UPDATE SET name = t2.name
WHEN NOT MATCHED THEN
INSERT (id, name)
VALUES (t2.id, t2.name);
This query won't return anything:
SELECT id FROM #t WHERE ID = 2
Because where is no rows in table with ID = 2, so there is nothing to merge into table.
Besides, in MATCHED clause you are updating a field ID on which you are joining table, i think, it's forbidden.
For each DML operations you have to commit (Marks the end of a successful the transaction)Then only you will be able to see the latest data
For example :
GO
BEGIN TRANSACTION;
GO
DELETE FROM HumanResources.JobCandidate
WHERE JobCandidateID = 13;
GO
COMMIT TRANSACTION;
GO
I have two tables. One table A has n rows of data and the other table B is empty. I want to insert n rows into table B, 1 row for each row in table A. Table B will have a couple of fields from table A in it, including a foreign key from table A.
In the end I want one row in B for each row in A. To do this I used:
INSERT INTO B(Col1
,Col2
,Col3
,Col4
,Col5
);
SELECT 100
,25
,'ABC'
,1
,A.ID
FROM Auctions A
Now, I've put this code in a stored procedure and this SP takes an int param called NumInserts.
I want to insert n * NumInserts rows. So, if n is 10 and NumInserts is 5 I want to run this code 5 * 10 (50) times.
In other words for each row in table A I want to insert 5 rows in table B. How would I do that?
create procedure insert_into_b
#numInserts int
as
begin
while #numInserts > 0
begin
insert into b (id)
select id from a
set #numInserts = #numInserts - 1
end
end
exec insert_into_b 2
This is a hack and I wouldn't recommend using it in production or big volumes of data. However, in development quick-and-dirty scenarios I found it often useful:
Use GO \[count\] to execute a batch of commands a specified number of times.
Concretely, if you had a stored procedure called InsertAIntoB, you could run this in Management Studio:
exec InsertAIntoB
GO 10
(replace 10 with whatever NumInserts is)
I prefer to avoid looping when I can, just so I don't have to maintain some easily breakable and somewhat ugly loop structure in my stored procedure.
You could easily do this with a Numbers table, the CROSS APPLY statement, and your existing INSERT statement.
Given that your numbers table would look like this:
Number
======
0
1
2
...
Your SQL statement simply becomes:
INSERT INTO B
(
[Col1]
,[Col2]
,[Col3]
,[Col4]
,[Col5]
)
SELECT
100
,25
,'ABC'
,1
,a.ID
FROM
Auctions a
CROSS APPLY
Numbers n
WHERE
n.Number BETWEEN 1 AND #NumInserts
Numbers tables can be useful if use appropriately. If you're unfamiliar with them, here are a few resources and some pros/cons:
http://dataeducation.com/you-require-a-numbers-table/ (the code to create a numbers table in this article is shown below)
http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=NumbersTable
https://dba.stackexchange.com/questions/11506/why-are-numbers-tables-invaluable
Maybe this solution is overkill if #NumInserts is always going to be a reasonably small number, but if you already have a Numbers table sitting around, you might as well take advantage of it!
UPDATE:
Here's a quick and dirty method to populate a numbers table from 0 to 65,535:
CREATE TABLE Numbers
(
Number INT NOT NULL,
CONSTRAINT PK_Numbers
PRIMARY KEY CLUSTERED (Number)
WITH FILLFACTOR = 100
)
GO
INSERT INTO Numbers
SELECT
(a.Number * 256) + b.Number AS Number
FROM
(
SELECT number
FROM master..spt_values
WHERE
type = 'P'
AND number <= 255
) a (Number),
(
SELECT number
FROM master..spt_values
WHERE
type = 'P'
AND number <= 255
) b (Number)
GO
Credit: http://dataeducation.com/you-require-a-numbers-table/
Create procedure DoitNTimes
#N integer = 1
As
Set NoCount On
While #N > 0 Begin
Insert B (Col1, Col2, Col3, Col4, Col5)
Select 100, 25, 'ABC', 1, A.ID
From Auctions A
-- -----------------------------------
Set #N -= 1
End
If using SQL Server 2005 or earlier replace the Set #N -= 1' withSet #N = #N-1`
and if you really want to avoid loop using T-SQL variables, then use a CTE, not a disk-based table:
Create procedure DoitNTimes
#N integer = 1
As
Set NoCount On
With nums(num) As
(Select #N Union All
Select num - 1
From nums
Where num > 1)
Insert B (Col1, Col2, Col3, Col4, Col5)
Select 100, 25, 'ABC', 1, A.ID
From Auctions A Full Join nums
Option(MaxRecursion 10000)
but of course, this is also still looping, just like any solution to this issue.
Very late answer but there is no need to loop and it's a little simpler than Corey's good answer;
DECLARE #n int = 10;
INSERT INTO B(Col1,Col2,Col3,Col4,Col5);
SELECT 100,25,'ABC',1,A.ID
FROM Auctions A
JOIN (SELECT TOP(#n) 1 [junk] FROM sys.all_objects) as copies ON 1 = 1
You could use any table in the join as long as it has the number of rows you'll need. You could also change "1 [junk]" to "ROW_NUMBER() OVER(ORDER BY object_id) [copyno]" if you wanted a copy number somewhere in the insert table.
Hopefully this will save someone a little work down the road...
Try this (on SQL server databases):
DECLARE #NumInserts SMALLINT = 3
INSERT INTO B (Col1, Col2, Col3, Col4, Col5)
SELECT 100, 25, 'ABC', 1, A.ID
FROM Auctions A
JOIN master.dbo.spt_values numbers ON numbers.number < #NumInserts
WHERE numbers.[type] = 'P'
Note: This will only work if #NumInserts is less than or equal to 2048
master.dbo.spt_values WHERE type = 'P' is just a built-in SQL Server table of numbers from 0 to 2047
We have a loop in SQL Server 2005 that loops around on a table getting each items parent until it gets to the top of the tree:
DECLARE #T Table
(
ItemID INT NOT NULL PRIMARY KEY,
AncestorID INT NULL
)
Which has data like this:
ItemID | AncestorID
1 2
2 3
3 4
4 NULL
We have a loop that basically does this:
DECLARE #AncestorID INT
SELECT #AncestorID = 1
WHILE (#AncestorID IS NOT NULL)
BEGIN
--Do some work
SELECT #AncestorID = T.AncestorID
FROM #T t
WHERE T.ItemID = #AncestorID
print #AncestorID
END
(Yes I know SQL is set based, and this is processing row by row, the "Do some work" needs to be done line by line for a reason).
This has always worked fine until today when we ended up in an endless loop. Turns out the cause was some wrong data:
ItemID | AncestorID
1 2
2 3
4 NULL
ItemID 3 was deleted. The loop now never ends because AncestorID is never NULL - it stays at 3.
Is there anyway to rewrite the select statement to make #AncestorID null if the SELECT query returns 0 rows, or do I need to have a separate SELECT statement to count the records and some IF ELSE type logic?
Is there anyway to rewrite the select statement to make #AncestorID
null if the SELECT query returns 0 rows,
You can use an aggregate on T.AncestorID.
SELECT #AncestorID = min(T.AncestorID)
FROM #T t
WHERE T.ItemID = #AncestorID
You could use another variable, e.g. #PreviousAncestorId, to hold the previous value and reset #AncestorId to NULL before the query.
You could check ##RowCount after the query to see if a row was found.
The code will still have issues dealing with cycles of arbitrary length within the data, e.g. a row where both values are the same. You would need to keep track of the visited rows in order to detect cycles. A simple reality check would be to count the number of iterations of the loop and check it against the number of rows.
Use a Break
e.g.
WHILE (#AncestorID IS NOT NULL)
BEGIN
SELECT T.AncestorID INTO #TEMP
FROM #T t WHERE T.ItemID = #AncestorID
IF((SELECT COUNT(*) FROM #TEMP) = 0) BREAK;
SELECT #AncestorID=T.AncestorID
FROM #TEMP t
print #AncestorID
DROP TABLE #TEMP
END