Multiple entries in to table variable using Output INTO clause - sql-server

My use case is as below :
Create table Emp
(
id int ,
fname varchar(100),
lname varchar(100)
)
declare #aud table (prop varchar(1000),[oldval] varchar(100),[toval] varchar(100))
INSERT INTO EMP(id, fname, lname)
OUTPUT 'fname', '', inserted.fname INTO #aud (prop, [oldval], [toval])
VALUES (1,'malcolm','dsouza')
SELECT * FROM #aud
I want the data to be formed like:
prop oldval toval
------------------------
fname malcolm
lname dsouza
I know we cannot have have multiple inserts using OUTPUT INTO clause, but is there any other way I can form a data for inserted rows as column based?

You need to try below steps:
OUTPUT clause simply inserts the rows in to the table variable i.e., one row to one row mapping. You need to modify your table definition accordingly.
Now, You can use CROSS APPLY to get the result you want.
**TEST SETUP**
Create table #Emp(
id int ,
fname varchar(100),
lname varchar(100)
)
TEST
declare #aud table(oldfname varchar(100),fname varchar(100),oldlname varchar(100), lname varchar(100));
INSERT INTO #EMP(id,fname,lname)
OUTPUT '' as oldfname,inserted.fname, '' as oldlname, inserted.lname INTO #aud
values (1,'malcolm','dsouza');
SELECT property, oldval, newval as oldValue
FROM #aud
CROSS APPLY
( values('fname',oldfname,fname),('lname',oldlname, lname)) as t(property,oldVal, newVal)
Result Set
+----------+--------+----------+
| property | oldval | oldValue |
+----------+--------+----------+
| fname | | malcolm |
| lname | | dsouza |
+----------+--------+----------+

Related

Execute a stored procedure with multiple IDs

I have a PersonInformation table that contains the information below:
| PersonID | Name | Status
+----------+------+------------
| 1234 | John | Active
| 5678 | Mary | Inactive
| 1090 | Tery | Active
| 1554 | Cary | Inactive
I also have a stored procedure called SpStats that does some calculations using PersonID and returns the results of stat_a, stat_b.
I need to execute the SpStats stored procedure for each active person in the PersonInformation table using their PersonID.
It is basically
SELECT PersonId, Name, Status, {Exec SpStats for related PersonID}
FROM PersonInformation
WHERE status = 'Active'
The expected result is this:
| PersonID | Name | Status | stat_a | stat_b |
+----------+------+--------+--------+--------+
| 1234 | John | Active | 25 | 45 |
| 1090 | Tery | Active | 10 | 67 |
If you can create a user defined table function that will do what your stored procedure does, you could use cross apply to get it's results for each row in the select.
Assuming it accepts the PersonID value, it can be done like this:
SELECT PersonId, [Name], [Status], stat_a, stat_b
FROM PersonInformation
CROSS APPLY dbo.UDFStats(PersonID) as stats
WHERE [Status] = 'Active'
Note that CROSS APPLY will not work with stored procedures.
Create your own custom type like so
--Table Valued Parameter Type used for passing lists of Int IDs.
CREATE TYPE [MySchema].[UniqueIDListType] AS TABLE
(
[ID] Int NOT NULL
);
Then in your stored proc set the input parameter to your type
CREATE PROCEDURE [MySchema].[MyProc]
#MyIDList [MySchema].[UniqueIDListType] READONLY
AS
And there you go, you can use your parameter like a table in your select
WHERE EXISTS (SELECT * FROM #MyIDList WHERE ID = PersonID);
You will get a full result set for each id then. Pass it in to the proc like you would any other parameter, you'll just need to format the ids into this single collection. And the bonus is you can reuse your new table valued parameter everywhere you need to do this again. Reusability!! Yay!
If you can't do this, then I'm assuming you can't with your requirements or permissions make a function either, I would then just loop through programmatically and make a table variable in your sql, call the proc for each person, insert that result in your table, and join that on your final select result set when you're all done.
If you have a table
CREATE TABLE Country (
CountryId UNIQUEIDENTIFIER
,Abbreviation VARCHAR(10)
,Name VARCHAR(100)
)
And a User Defined Function
CREATE FUNCTION udfConcatenate(#value1 VARCHAR(4000), #value2 VARCHAR(4000))
RETURNS VARCHAR(8000)
AS
BEGIN
RETURN CONCAT(#value1, #value2)
/* HERE YOU CAN EXECUTE YOU SOTRED PROCEDURE AND RETURN THE RESULT */
END
You can execute
select c.CountryId, dbo.udfConcatenate(c.Abbreviation, c.Name) from country AS C
The User Defined Function can be applied for each row.
So, the User Defined Function execute you Sotred Procedure and return the result.
And, you use the User Defined Function in you SELECT.
EDIT
WHILE way
DECLARE #abb VARCHAR(10)
DECLARE #name VARCHAR(200)
DECLARE cur1 CURSOR FOR
SELECT Abbreviation, Name FROM Country
OPEN cur1
FETCH NEXT FROM cur1 INTO #abb, #name
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT dbo.udfConcatenate(#abb, #name)
/* HERE YOU CAN EXECUTE THE SP AND STORE THE RESULTS IN A TEMP TABLE */
FETCH NEXT FROM cur1 INTO #abb, #name
END
CLOSE cur1
DEALLOCATE cur1
Hope this help you.

TSQL stuff for xml path and sub query

I've to replace csv datas in column by correspondant id always in csv format
I've a problem with this query :
select t0.code , t0.categories, t0.departement, (
SELECT Stuff((
SELECT N', ' + CONVERT(varchar, id_categorie) FROM tcategories t1 WHERE t0.departement = t1.departement COLLATE French_CI_AI and categorie IN (t0.tcategories)
FOR XML PATH(''),TYPE).value('text()[1]','varchar(max)'),1,1,N'')) as id_colonne
FROM #codes_reductions t0 where categories is not null
Here is the result :
code | categories | departement | id_colonne
AIRSTREAM | 'A','B','BA' | JMQ | NULL
If I replace 'and categorie IN (t0.tcategories)' by and categorie IN ('A','B','BA') the query works good
Here is the result :
code | categories | departement | id_colonne
AIRSTREAM | 'A','B','BA' | JMQ | 128, 129, 260
I tryed to use COLLATE French_CI_AI on my column, but without success. Any idea ?
... categorie IN (t0.tcategories) ....
The cause of your problem is that categorie column stores those values ('A','B','BA') as a single value not as an array / list / table of values. So, SQL Server compares two strings thus s1 IN (s2) which is equivalent to s1 = s2 => A IN ('A','B','BA') <=> A = 'A','B','BA'.
Example (note: double single quotes ('') are used to define an empty string while four single quotes ('''') are used to define a string with a single quote: SELECT '''' --> '):
DECLARE #categories VARCHAR(1000);
SET #categories = '''A'',''B'',''BA'''
SELECT #categories AS ColA, CASE WHEN 'A' IN (#categories) THEN 'TRUE' ELSE 'FALSE' END AS Col2
/*
ColA Col2
------------ -----
'A','B','BA' FALSE
*/
The solution on short term is to use one of following conditions:
C#1 (if categorie contains single quotes): ... t0.tcategories LIKE '%' + categorie + '%' ....
C#2 (when categorie doesn't contains single quotes): ... t0.tcategories LIKE '%''' + categorie + '''%' ....
Example:
DECLARE #categories VARCHAR(1000);
SET #categories = '''A'',''B'',''BA'''
SELECT #categories AS ColA, CASE WHEN #categories LIKE '%''A''%' THEN 'TRUE' ELSE 'FALSE' END AS Col2
/*
ColA Col2
------------ -----
'A','B','BA' TRUE
*/
Second note: this works when every separate value from t0.tcategories column doesn't includes single quote(s) (example: 'B'A' / 'B''A' ).
On medium/long term, you should store separately every single value from tReduction.tCategories column using another table :
CREATE TABLE dbo.ReductionCategory (
... pk ...,
ReductionCode INT NOT NULL REFERENCES dbo.tReduction(ReductionCode), -- FK
CategoryCode INT NOT NULL REFERENCES dbo.tCategories(CategoryCode) -- FK
)
Thus, condition becomes
... categorie /*CategoryCode*/
IN (
SELECT rc.CategoryCode FROM dbo.ReductionCategory rc
WHERE rc.ReductionCode = t0.ReductionCode
) ....

How to retrieve old values in OUTPUT clause with SQL MERGE statement

I'm using MERGE statement to update a product table containing (Name="a", Description="desca"). My source table contains (Name="a", Description="newdesca") and I merge on the Name field.
In my Output clause, I would like to get back the field BEFORE the update -> Description = "desca".
I couldn't find a way to do that, I'm always getting back the new value ("newdesca"). Why?
Can you not just used the deleted memory-resident table. e.g:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
CREATE TABLE #T (Name VARCHAR(5), Description VARCHAR(20));
INSERT #T (Name, Description)
VALUES ('a', 'desca'), ('b', 'delete');
MERGE #T AS t
USING (VALUES ('a', 'newdesca'), ('c', 'insert')) AS m (Name, Description)
ON t.Name = m.Name
WHEN MATCHED THEN
UPDATE SET Description = m.Description
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, Description)
VALUES (m.Name, m.Description)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $Action, inserted.*, deleted.*;
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
The output of this would be:
$Action | Name | Description | Name | Description
--------+-------+-------------+------+--------------
INSERT | c | insert | NULL | NULL
UPDATE | a | newdesca | a | desca
DELETE | NULL | NULL | b | delete

Access to any element of a table

I have built a table in a stored procedure :
declare #storeTable table
(
path varchar(1000) not null,
nbdays int,
offset int
)
insert #storeTable
select PATH, NUMBER, OFFSET from
FILENAME f left outer join ...
Let's say that my table have 4 rows, like this:
Path1 | 3 | 1
Path2 | 9 | -1
Path3 | 2 | 3
Path4 | 5 | 0
I would like to know how I can have access to any element from this table.
For instance, I would like to use the value -1 of the offset on the row 2, in order to include it at the end of the Path2 (and thus modify the path2)
Any clue?
Instead of a variable what I do is to create a temp table. Something like this:
CREATE TABLE [dbo].[#storeTable] (
[path] varchar(1000) not null,
[nbdays] int,
[offset] int
) ON [PRIMARY]
Then you can do select or joins/updates with your regular tables. Temp table is deleted when you exist SP. Hope it helps you
Select path+cast(offset as varchar) as path
From #storeTable

SQL Server: results of the table valued function doesn't match column names

I have this function:
CREATE FUNCTION [dbo].[full_ads](#date SMALLDATETIME)
returns TABLE
AS
RETURN
SELECT *,
COALESCE((SELECT TOP 1 ptype
FROM special_ads
WHERE [adid] = a.id
AND #date BETWEEN starts AND ends), 1) AS ptype,
(SELECT TOP 1 name
FROM cities
WHERE id = a.cid) AS city,
(SELECT TOP 1 name
FROM provinces
WHERE id = (SELECT pid
FROM cities
WHERE id = a.cid)) AS province,
(SELECT TOP 1 name
FROM models
WHERE id = a.mid) AS model,
(SELECT TOP 1 name
FROM car_names
WHERE id = (SELECT car_id
FROM models
WHERE id = a.mid)) AS brand,
(SELECT TOP 1 pid
FROM cities
WHERE id = a.cid) pid,
(SELECT TOP 1 car_id
FROM models
WHERE id = a.mid) bid,
(SELECT TOP 1 name
FROM colors
WHERE id = a.color_id) AS color,
COALESCE((SELECT TOP 1 fileid
FROM carimgs
WHERE adid = a.id), 'nocarimage.png') AS [image]
FROM ads a
WHERE isdeleted <> 1
Sometimes it works correctly, but sometimes column names doesn't match values like (I have written a sample results with fewer columns just to show the problem):
ID Name City Color Image
----------------------------------------------
1 John New York Null Red
2 Ted Chicago Null Blue
As you see color and Image values are shifted one column and this continues to the last column.
Can anyone tell me where the problem is?
This arises from using *.
If the definition of ads changes (columns added or removed) this can mess up the metadata associated with the TVF.
You would need to run sp_refreshsqlmodule on it to refresh this metadata after such changes. It is best to avoid * in view definitions or inline TVFs for this reason.
An example of this
CREATE TABLE T
(
A CHAR(1) CONSTRAINT DF_A DEFAULT 'A',
B CHAR(1) CONSTRAINT DF_B DEFAULT 'B',
C CHAR(1) CONSTRAINT DF_C DEFAULT 'C',
D CHAR(1) CONSTRAINT DF_D DEFAULT 'D'
)
GO
INSERT INTO T DEFAULT VALUES
GO
CREATE FUNCTION F()
RETURNS TABLE
AS
RETURN
SELECT * FROM T
GO
SELECT * FROM F()
GO
ALTER TABLE T DROP CONSTRAINT DF_C, COLUMN C
ALTER TABLE T ADD E CHAR(1) DEFAULT 'E' WITH VALUES
GO
SELECT * FROM F()
Returns
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
| A | B | D | E |
+---+---+---+---+
Note that the D and E values are shown in the wrong columns. It still shows column C even though it has been dropped.

Resources