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
Related
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 |
+----------+--------+----------+
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.
I have a situation where I have a store procedure that contains a merge statement. The procedure grabs data from table A and uses the merge statement to update certain records in table B. The procedure works fine but occasionally, there are instances where there are duplicate records in table A. The store procedure is in a package with error notification set up and the package runs in a job and it gives the error below. Are there any ways that we can debug this? Like some where in the store procedure say if it gives an error then insert the source data into a table? Any input is appreciated.
Thanks
Error :
failed with the following error: "The MERGE statement attempted to
UPDATE or DELETE the same row more than once. This happens when a
target row matches more than one source row. A MERGE statement cannot
UPDATE/DELETE the same row of the target table multiple times. Refine
the ON clause to ensure a target row matches at most one source row,
or use the GROUP BY clause to group the source rows.". Possible
failure reasons: Problems with the query, "ResultSet" property not set
correctly, parameters not set correctly, or connection not established
correctly.
You could add something like this to the beginning of your merge procedure:
if exists (
select 1
from a
group by a.OnColumn
having count(*)>1
)
begin;
insert into merge_err (OnColumn, OtherCol, rn, cnt)
select
a.OnColumn
, a.OtherCol
, rn = row_number() over (
partition by OnColumn
order by OtherCol
)
, cnt = count(*) over (
partition by OnColumn
)
from a
raiserror( 'Duplicates in source table a', 0, 1)
return -1;
end;
test setup: http://rextester.com/EFZ77700
create table a (OnColumn int, OtherCol varchar(16))
insert into a values
(1,'a')
, (1,'b')
, (2,'c')
create table b (OnColumn int primary key, OtherCol varchar(16))
insert into b values
(1,'a')
, (2,'c')
create table merge_err (
id int not null identity(1,1) primary key clustered
, OnColumn int
, OtherCol varchar(16)
, rn int
, cnt int
, ErrorDate datetime2(7) not null default sysutcdatetime()
);
go
dummy procedure:
create procedure dbo.Merge_A_into_B as
begin
set nocount, xact_abort on;
if exists (
select 1
from a
group by a.OnColumn
having count(*)>1
)
begin;
insert into merge_err (OnColumn, OtherCol, rn, cnt)
select
a.OnColumn
, a.OtherCol
, rn = row_number() over (
partition by OnColumn
order by OtherCol
)
, cnt = count(*) over (
partition by OnColumn
)
from a
raiserror( 'Duplicates in source table a', 0, 1)
return -1;
end;
/*
merge into b
using a
on b.OnColumn = a.OnColumn
...
--*/
end;
go
execute test proc and check error table:
exec dbo.Merge_A_into_B
select *
from merge_err
where cnt > 1
results:
+----+----------+----------+----+-----+---------------------+
| id | OnColumn | OtherCol | rn | cnt | ErrorDate |
+----+----------+----------+----+-----+---------------------+
| 1 | 1 | a | 1 | 2 | 05.02.2017 17:22:39 |
| 2 | 1 | b | 2 | 2 | 05.02.2017 17:22:39 |
+----+----------+----------+----+-----+---------------------+
I have a stored procedure in SQL Server, I am trying to select only the records where a column's value is in there more than once, This may seem a bit of an odd request but I can't seem to figure it out, I have tried using HAVING clauses but had no luck..
I want to be able to only select records that have the ACCOUNT in there more than once, So for example:
ACCOUNT | PAYDATE
-------------------
B066 | 15
B066 | OUTSTAND
B027 | OUTSTAND <--- **SHOULD NOT BE IN THE SELECT**
B039 | 09
B039 | OUTSTAND
B052 | 09
B052 | 15
B052 | OUTSTAND
BO27 should NOT show in my select, and the rest of the ACCOUNTS should.
here is my start and end of the Stored Procedure:
Select * from (
*** SELECTS ARE HERE ***
) X where O_STAND <> 0.0000
group by X.ACCOUNT, X.ACCT_NAME , X.DAYS_CR, X.PAYDATE, X.O_STAND
order by X.ACCOUNT
I have been struggling with this for a while, any help or advice would be appreciated. Thank you in advance.
you could replace the first string with
Select *, COUNT(*) OVER (PARTITION BY ACCOUNT) cnt FROM (
and then wrap your query as subquery once more
SELECT cols FROM ( query ) q WHERE cnt>1
Yes, the having clause is for solving exactly this kind of tasks. Basically, it's like where, but allows to filter not only by column values, but also by aggregate functions' results:
declare #t table (
Id int identity(1,1) primary key,
AccountId varchar(20)
);
insert into #t (AccountId)
values
('B001'),
('B002'),
('B015'),
('B015'),
('B002');
-- Get all rows for which AccountId value is encountered more than once in the table
select *
from #t t
where exists (
select 0
from #t h
where h.AccountId = t.AccountId
group by h.AccountId
having count(h.AccountId) > 1
);
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.