Sorry I'm a bit new to this so just trying to get my head around linking everything up.
At the moment I have a normal query - SELECT FROM WHERE which basically finds about 2000 records that I need to update which link across several tables.
Can someone tell me how I can link this simple query to something else so I can basically execute several stored procedures, all in the same script? But only affecting the records returned by my simple query?
Apologies, that probably sounds as clear as mud!
*EDIT - MORE DETAIL *
So here is my Select query:
SELECT [MembershipTermID]
,[MemberStatusProgKey]
,[StartDate]
,[EndDate]
,[AdditionalDiscount]
,[EntryDateTime]
,[UpdateDateTime]
,[MembershipID]
,[AgentID]
,[PlanVersionID]
,[ForceThroughReference]
,[IsForceThrough]
,[NextTermPrePaid]
,[IsBillingMonthly]
,[CICSMEMBERNUM]
,[CICSHISTORY]
,[TMPSeqNoColumn]
,[LastPaymentDate]
,[PaidToDate]
,[IsIndeterminate]
,DATEDIFF(MONTH, PaidToDate, GETDATE()) as MonthsDifference
,dbo.FullMonthsSeparation (PaidToDate, GETDATE())
FROM [Apollo].[dbo].[MembershipTerm]
WHERE MemberStatusProgKey='DORMANT'
AND IsBillingMonthly=1
AND dbo.FullMonthsSeparation (PaidToDate, GETDATE()) >= 2
So using the rows that this returns I want to exec several stored procedures to update everything I need to in the database which would be affected by changing these rows. An example of one stored procedure is below, I think I will need to execute about 10 of these if not more:
USE [Apollo]
GO
/****** Object: StoredProcedure [dbo].[spCancellationDetailInsert] Script Date: 01/10/2012 10:21:50 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/* ************************* INSERT *************************/
/* Auto Generated 11/29/2006 7:28:53 PM by Object Builder */
/* ************************* INSERT *************************/
ALTER Procedure [dbo].[spCancellationDetailInsert]
#StampUser char (10),
#CancellationDetailID int,
#RefundAmount float,
#OldEndDate datetime,
#EffectiveDate datetime,
#CancelDate datetime,
#ReasonCodeProgKey nvarchar (50)
As
/* insert CancellationDetail record */
Insert [CancellationDetail]
(
RefundAmount,
OldEndDate,
EffectiveDate,
CancelDate,
ReasonCodeProgKey
)
Values
(
#RefundAmount,
#OldEndDate,
#EffectiveDate,
#CancelDate,
#ReasonCodeProgKey
)
If ##Error <> 0 GoTo InsertErrorHandler
/* save the key of the new row created by the insert */
Select #CancellationDetailID = Scope_Identity()
/* add audit record */
Insert CancellationDetailAudit
(StampUser,
StampDateTime,
StampAction,
CancellationDetailID,
RefundAmount,
OldEndDate,
EffectiveDate,
CancelDate,
ReasonCodeProgKey)
Values
(#StampUser ,
GetDate() ,
'I',
#CancellationDetailID,
#RefundAmount,
#OldEndDate,
#EffectiveDate,
#CancelDate,
#ReasonCodeProgKey)
If ##Error <> 0 GoTo AuditInsertErrorHandler
Select
CancellationDetailID = #CancellationDetailID
Return (0)
InsertErrorHandler:
Raiserror ('SQL Error whilst inserting CancellationDetailrecord: Error Code %d',17,1,##Error)
With Log
Return (99)
AuditInsertErrorHandler:
Raiserror ('SQL Error whilst inserting audit record for CancellationDetailInsert: Error Code %d',17,1,##Error)
With Log
Return (99)
If you're asking what I think you are -
Stored procedures can contain (pretty much) any valid SQL statement. This includes returning multiple results sets, performing multiple updates and calling other stored procedures.
For example:
CREATE PROCEDURE usp_Sample AS
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
SELECT * FROM INFORMATION_SCHEMA.TABLES
UPDATE Users SET Active = 0 WHERE ExpiredDate < GetDate()
SELECT Active, COUNT(*) FROM Users GROUP BY Active
EXEC usp_Sample2
GO
Obviously that's a rather artificial example, but assuming all the objects existed it'd run perfectly well.
In order to perform more queries at the same time you just need to append them after your select.
So you can do
Select *
From table1
Select *
From table2
Select *
From table3
as many times as you want and they'll all execute independently.
If you want to UPDATE based on a SELECT you usually do something like:
UPDATE table1
WHERE ID IN (SELECT ID FROM TABLE2)
with regards to your stored procedures it would help if you posted more details.
Related
I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more
SELECT Val from storedp_Value within the query editor of SQL Server Management Studio, is this possible?
UPDATE
I tried to create a temp table but it didn't seem to work hence why I asked here.
CREATE TABLE #Result
(
batchno_seq_no int
)
INSERT #Result EXEC storedp_UPDATEBATCH
SELECT * from #Result
DROP TABLE #Result
RETURN
Stored Procedure UpdateBatch
delete from batchno_seq;
insert into batchno_seq default values;
select #batchno_seq= batchno_seq_no from batchno_seq
RETURN #batchno_seq
What am I doing wrong and how do I call it from the query window?
UPDATE #2
Ok, I'd appreciate help on this one, direction or anything - this is what I'm trying to achieve.
select batchno_seq from (delete from batchno_seq;insert into batchno_seq default values;
select * from batchno_seq) BATCHNO
INTO TEMP_DW_EKSTICKER_CLASSIC
This is part of a larger select statement. Any help would be much appreciated. Essentially this SQL is broken as we've migrated for Oracle.
Well, no. To select from a stored procedure you can do the following:
declare #t table (
-- columns that are returned here
);
insert into #t(<column list here>)
exec('storedp_Value');
If you are using the results from a stored procedure in this way and you wrote the stored procedure, seriously consider changing the code to be a view or user defined function. In many cases, you can replace such code with a simpler, better suited construct.
This is not possible in sql server, you can insert the results into a temp table and then further query that
CREATE TABLE #temp ( /* columns */ )
INSERT INTO #temp ( /* columns */ )
EXEC sp_MyStoredProc
SELECT * FROM #temp
WHERE 1=1
DROP TABLE #temp
Or you can use OPENQUERY but this requires setting up a linked server, the SQL is
SELECT * FROM (ThisServer, 'Database.Schema.ProcedureName <params>')
The best article (in my opinion) about all possible methods for sharing data between stored procedures in SQL Server you can find here: http://www.sommarskog.se/share_data.html
Try this
Change 'Return'
delete from batchno_seq;
insert into batchno_seq default values;
select #batchno_seq= batchno_seq_no from batchno_seq
RETURN #batchno_seq
to 'Select'
delete from batchno_seq;
insert into batchno_seq default values;
select #batchno_seq= batchno_seq_no from batchno_seq
SELECT #batchno_seq
My approach
select * into new_table from (select t1.col1,t1.col2,..
from table1 t1
union
select t2.cola,t2.colb,..
from table2 t2) as union_table
I MUST be missing something.
Since your stored procedure does not return a result set, and instead returns an integer, using the RETURN functionality of stored procs, you simply CANNOT INSERT into ANY table (since there isn't any result set coming back, at all).
BUT, you can (assuming that this is done iteratively, and not over a set) simply store the return value into a local variable, and insert that variable's value into whatever table is necessary.
However, if you simply want to return the value in the results of a Query Window in SSMS, doing the INSERT and SELECTING is overkill.
It seems to me like THIS would suffice (in a query window):
DECLARE #RetVal INT = 0;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
EXEC #RetVal = storedp_UPDATEBATCH;
COMMIT TRANSACTION;
SELECT #RetVal;
--OR
--PRINT #RetVal;
If this is way far off base, please provide the DDL for "batchno_seq", maybe I can be of better assistance that way.
Cheers!
I am in the process of writing some stored procedures to transfer data from tables used for the current years records to tables used to store historical data. One example I am working on at present is a typical Master- Detail relationship, where the detail records obviously need to be processed before the master records. I wrote a straightforward test query to collect some sample records and see if I had the basic sql correctly written.
DECLARE #cuttoffdate date = '31/03/2009'
SELECT
*
FROM Landings.LandingDetails
INNER JOIN Landings.LandingHeaders
ON Landings.LandingHeaders.LandingId = Landings.LandingDetails.LandingId
WHERE Landings.LandingHeaders.LandingDate1 <= #cuttoffdate
SELECT
*
FROM Landings.LandingHeaders
WHERE Landings.LandingHeaders.LandingDate1 <= #cuttoffdate
This works and produces two sets of records that I would expect. However when I then use this in a stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [HistoricalData].[TransferAllLandingInformation]
-- Add the parameters for the stored procedure here
#cutoffdate date
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO HistoricalData.HistoricalLandingDetails
SELECT
*
FROM Landings.LandingDetails
INNER JOIN Landings.LandingHeaders
ON Landings.LandingHeaders.LandingId = Landings.LandingDetails.LandingId
WHERE Landings.LandingHeaders.LandingDate1 <= #cutoffdate
INSERT INTO HistoricalData.HistoricalLandingHeaders
SELECT
*
FROM Landings.LandingHeaders
WHERE Landings.LandingHeaders.LandingDate1 <= #cutoffdate
END
GO
it fails to execute giving me the following error:
Msg 213, Level 16, State 1, Procedure TransferAllLandingInformation, Line 12
Column name or number of supplied values does not match table definition.
line 12 being just after the BEGIN statement.
Could anyone enlighten me as to why this is happening. I would by no means claim to be fluent in sql and as a result this particular error has me stumped at present and I would like to learn from it.
Just for the sake of clarity this is being written in SSMS 2012.
Thanks
The error message indicates you have a differing number of columns in HistoricalLandingDetails to the results returned by your select query which has all the data from HistoricalLandingDetails and LandingHeaders.
I would recommend specifying columns in the insert and in the select to make sure everything matches up e.g.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [HistoricalData].[TransferAllLandingInformation]
-- Add the parameters for the stored procedure here
#cutoffdate date
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO HistoricalData.HistoricalLandingDetails (cola, colb, colc)
SELECT
ld.cola, ld.colb, ld.colc
FROM Landings.LandingDetails ld
INNER JOIN Landings.LandingHeaders lh
...
They don't have the same structure.
In query you are only selecting so those statement will work. However in procedure your insert statement fails.
The number of columns in the Target Table or number of columns specified in the Insert Statement and the Values supplied in the Insert statements are not matching.
As error suggest you are trying to insert more values than column count in destination table. To fix that type explicitly column names eg:
INSERT INTO HistoricalData.HistoricalLandingHeaders (col1, col2)
SELECT Landings.LandingHeaders.val1, Landings.LandingHeaders.val2
FROM Landings.LandingHeaders
WHERE Landings.LandingHeaders.LandingDate1 <= #cutoffdate
First, here is the code for sp_GetWorkQByUserName:
ALTER PROCEDURE [dbo].[sp_GetWorkQByUserName]
( #UserName varchar(50),
#StartDate datetime,
#EndDate datetime )
AS
BEGIN
SET NOCOUNT ON;
SELECT DISTINCT SpotId FROM tblSpotCount WHERE StoreNum = EXECUTE sp_GetUserLocationCodes(#UserName)
ORDER BY SpotDt ASC
END
I know my SELECT DISTINCT statement is wrong, but I wrote it like that to help show what I'm trying to do. I want to run this stored procedure based on the results from the sp_GetUserLocationCodes with a parameter of #UserName.
From what I can tell, my problem lies in how I'm calling sp_GetUserLocationCodes.
Question: how can I run a SELECT DISTINCT query on tblSpotCount.SpotId based on the results from the sp_GetUserLocationCodes stored procedure?
You cannot use a stored procedure directly in a query. You can, however, insert the results of a stored procedure into a temporary table and use that in your query:
CREATE TABLE #storeLocations
(
-- appropriate column names and data types go here
)
INSERT INTO #storeLocations (put column list here)
EXECUTE sp_GetUserLocationCodes(#UserName)
SELECT DISTINCT SpotId
FROM tblSpotCount
WHERE EXISTS (SELECT 1
FROM #storeLocations
WHERE #storeLocations.StoreNum = tblSpotCount.StoreNum)
ORDER BY SpotDt ASC
DROP TABLE #storeLocations
I am trying to call an IF statement on my trigger so it won't archive expired files. (I only want to keep files that have been deleted but have not been expired)
My error is The multi-part identifier "d.ExpiryDate" could not be bound.
My Code:
ALTER TRIGGER [dbo].[ArchiveDB]
ON [dbo].[TBL_Content]
AFTER DELETE
AS
BEGIN
declare #ContentID int
set #ContentID = (select ContentID from deleted)
IF (d.ExpiryDate > getDate() )
begin
insert into ArchiveBackup.dbo.TBL_Deleted_Content
(ContentID, StartDate, ExpiryDate, Title... etc)
select
d.ContentID,d.StartDate,d.ExpiryDate,d.Title... etc
from deleted as d
end
END
Thanks for the help!
You'd have to tell SQL Server where to find the ExpiryDate, like:
if ((select ExpiryDate from deleted) > getdate())
Be aware that a trigger can be called for cases when multiple rows where deleted. It might be better to replace the entire if contruct with a query:
insert into ArchiveBackup.dbo.TBL_Deleted_Content
(ContentID, StartDate, ExpiryDate, Title... etc)
select
d.ContentID,d.StartDate,d.ExpiryDate,d.Title... etc
from deleted as d
where ExpiryDate > getdate()
Or even better, write a stored procedure that deletes rows instead of deleting them directly from the table. Stored procedures are way easier to understand and maintain than triggers.
There is no table or view anywhere that you alias as d - no wonder it can't find it!
You need to use the full table name in this case:
ALTER TRIGGER [dbo].[ArchiveDB]
ON [dbo].[TBL_Content] AFTER DELETE
AS BEGIN
declare #ContentID int
set #ContentID = (select ContentID from deleted)
IF (deleted.ExpiryDate > getDate() )
BEGIN
insert into ArchiveBackup.dbo.TBL_Deleted_Content
(ContentID, StartDate, ExpiryDate, Title... etc)
select
d.ContentID,d.StartDate,d.ExpiryDate,d.Title... etc
from deleted as d
END
END
The alias d is only ever declared and used inside the BEGIN ... END block - it is not visible outside that block! You can only use a table alias in the statement that declares it - it's not globally visible.
I believe this is what you need, as this will properly handle cases where you delete more than one row at a time as well as check the condition that you want for each row.
ALTER TRIGGER [dbo].[ArchiveDB]
ON [dbo].[TBL_Content]
AFTER DELETE
AS
BEGIN
insert into ArchiveBackup.dbo.TBL_Deleted_Content (ContentID, StartDate, ExpiryDate, Title... etc)
select
d.ContentID,d.StartDate,d.ExpiryDate,d.Title... etc
from
deleted as d
where
d.ExpiryDate > getDate()
END