避免"复制和粘贴"存储过程的方法取决于参数

时间:2018-01-25 15:33:18

标签: sql sql-server stored-procedures

我需要添加新位置的50个存储过程。是否有以下列方式编写存储过程的替代方法? (我为每个位置复制相同的select语句)

IF @LOCATION = 'Canada'
BEGIN
    SELECT location_id, location_description
    INTO #tempAssetHistoryCANADA
    FROM [SERVER20].[Shop_Canada].[dbo].[report_asset_history]
END

IF @LOCATION = 'USA'
BEGIN
    SELECT location_id, location_description
    INTO #tempAssetHistoryUSA
    FROM [SERVER20].[Shop_USA].[dbo].[report_asset_history]
END 

我有一个select语句,如果@parameter =" x"然后是完全相同的select语句,但是来自具有相同结构的不同数据源,如果@parameter =" y"。

我想知道是否有更好的方法来编写这些存储过程,因为将来当我需要添加新位置时,我需要更新所有50个存储过程,并复制每个语句并稍微改变它对于新的位置数据?我已经研究过,并没有发现任何有用的东西。

谢谢!

6 个答案:

答案 0 :(得分:2)

一种可能的方法是创建一个视图:

,而不是使用动态查询
CREATE VIEW dbo.Locations
AS

SELECT location_id, location_description, 'Canada' AS location
FROM [SERVER20].[Shop_Canada].[dbo].[report_asset_history]

UNION ALL 

SELECT location_id, location_description, 'USA' AS location
FROM [SERVER20].[Shop_USA].[dbo].[report_asset_history]

然后使用它:

SELECT location_id, location_description
INTO #tempAssetHistory
FROM [dbo].Locations
WHERE location = @LOCATION

如果您有新表[SERVER20].[Shop_XXX].[dbo].[report_asset_history],则必须将它们添加到您的视图中。

答案 1 :(得分:1)

首先,您不需要为每个位置使用不同的#temp表。您在每个中存储相同的数据列。其次,您可以根据位置将“From”子句存储在表中,然后使用动态sql选择临时表。我很快就会提供一些代码和示例。

DECLARE @fromClause VARCHAR(255)
DECLARE @sql VARCHAR(MAX)

--Explicitly create the temp table
CREATE TABLE #T (location_id int, location_description varchar(255) )

--Get the FROM clause
select @fromClause = fromClause from tblLocation WHERE location  = @LOCATION

--Build the Dynamic SQL
SET @sql = 'SELECT location_id, location_description ' + @fromClause

--Insert into your temp table
INSERT INTO #T execute ( @sql )

--View the results
SELECT * FROM #T

这是tblLocation定义 enter image description here

答案 2 :(得分:0)

您可以使用一个表来存储所有选定的数据,使用以下内容:

DECLARE @country VARCHAR(20) = 'USA'

CREATE TABLE #tempHistory (country varchar(20), location_id int, location_description varchar(20))

DECLARE @sql VARCHAR(max)
SET @sql = 'SELECT ''' + @country + ''' as country, location_id, location_description FROM [SERVER20].[Shop_' + @country + '].[dbo].[report_asset_history]'

INSERT INTO #tempHistory EXEC (@sql)

或者您可以使用更灵活的解决方案将国家/地区列表作为参数:

CREATE PROCEDURE dbo.prepare_tempHistory(@listOfCountries VARCHAR(max))
AS
    DECLARE @sql varchar(max)
    SET @sql = ''

    SELECT @sql = @sql + ' UNION ALL ' +
           'SELECT ''' + val + ''' as country, location_id, location_description FROM [SERVER20].[Shop_' + val + '].[dbo].[report_asset_history]'
    FROM dbo.fnSplit(@listOfCountries, ',')

    SET @sql = RIGHT(@sql, len(@sql)-11)

    INSERT INTO #tempHistory EXEC (@sql)
GO

但是你需要一个小函数来将参数分割成表格:

CREATE FUNCTION dbo.fnSplit
(
    @delimited nvarchar(max),
    @delimiter nvarchar(5)
) 
RETURNS @ret TABLE (val nvarchar(max))
AS
BEGIN
    declare @xml xml
    set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'

    insert into @ret(val)
    select r.value('.','varchar(max)') as item
    from @xml.nodes('//root/r') as records(r)

    RETURN
END
GO

现在您可以通过简单的方式准备数据:

CREATE TABLE #tempHistory (country varchar(20), location_id int, location_description varchar(20))

EXEC dbo.prepare_tempHistory 'USA,Canada,Mexico'

SELECT * FROM #tempHistory 

对于任何其他国家/地区仅修改参数

答案 3 :(得分:0)

您可以通过为所有助手提供#Temp表并检查数据库中的位置表,然后在执行SP时调用变量来实现此目的:

致电SP:

SP_Some_SP 'Canada'

在SP内部

Declare @Location Varchar(100)
Declare @Location_ID int = (Select Location_ID from [Location] where Location_Description = @Location)


CREATE TABLE #TempAssetHistory
(
  location_ID int, 
  location_Description varchar(100) 
)

If Exists(Select Location_Description from [Location] where Location_Description = @Location )
BEGIN
    Insert INTO #TempAssetHistory
    Values(@Location_ID,@Location)
END
ELSE
BEGIN 
-- Do something
END

答案 4 :(得分:0)

使用回答中的建议和提到动态SQL的评论我为自己提出了这个小解决方案。它比Denis Rubashkin所说的更好,因为使用他的解决方案我每次添加新位置时仍然需要复制整个SQL查询。这样,我可以复制4行...

IF @LOCATION = 'CANADA'
BEGIN
SET @location_server = 'SHOP_Canada'
END
...每当我想添加一个新位置而不是整个SQL语句时,从IF语句开始。它将使用参数中的正确名称替换服务器名称,并将其名称附加到临时表。

@LOCATION varchar(50),
@sqlCommand nvarchar(2000),
@location_server varchar(75)

IF @LOCATION = 'CANADA'
BEGIN
    SET @location_server = 'SHOP_Canada'
END

IF @LOCATION = 'USA'
BEGIN
    SET @location_server = 'SHOP_USA'
END

SET @sqlCommand = 'SELECT location_id, location_description

    into ##MarineShopAssetExpensesLTD_'+@location_server+'

    FROM [SERVER20].'+QUOTENAME(@location_server)+'.[dbo].[report_asset_history]

    INNER JOIN [SERVER20].'+QUOTENAME(@location_server)+'.[dbo].imtbl_asset ON [report_asset_history].asset_id = imtbl_asset.id

    GROUP BY location_id, location_description

    EXECUTE sp_executesql @sqlCommand

答案 5 :(得分:0)

由于每个SP都在寻找不同的数据,因此您可以使用另一种更为激烈的方法。

这种方法要求您将所有50个SP的所有数据SELECT语句放入一个SP,比如spDataLoad,它带有两个参数 - 数据集名称和位置。 spDataLoad根据指定的数据集和位置选择数据,并将请求的数据返回给调用者。

对于每种不同的数据集和位置组合,您仍然需要多个select语句,但至少所有内容都在一个SP中,并且对数据的更改不会影响所有50个SP。如果每个位置的表和数据相同,则可以将代码细分为多个部分,每个部分对应一个部分,使用相同的代码,但与该位置对应的数据库名称除外。

使用上面的代码作为示例,如果我们选择'AssetHistory'作为数据集名称,那么现有SP中的代码将如下所示:

:
:
CREATE TABLE #AssetHistory (
     location_ID int, 
     location_Description varchar(100) 
  );

INSERT INTO #AssetHistory EXEC spDataLoad @DataSet='AssetHistory', @Location=@Location;
:
:  use the data set
:

现在假设您有另一个需要数据集'AssetDetails'的SP,那么代码将是这样的:

CREATE TABLE #AssetDetails (
     :
     :  Specification for Asset Details table
     : 
  );

INSERT INTO #AssetDetails EXEC spDataLoad @DataSet='AssetDetails', @Location=@Location;
:
:  use the data set
:

每个位置的存储过程spDataLoad(包含多个部分),基于请求的数据集进行单独选择可能如下所示:

CREATE PROCEDURE spDataLoad
    @DATASET varchar(20)
    , @LOCATION Varchar(50)

AS
BEGIN

-- CANADA SECTION ------------------------------------
  IF @LOCATION = 'CANADA'
    BEGIN
        IF @DATASET = 'AssetHistory'
            SELECT location_id, location_description
            FROM [SERVER20].[Shop_Canada].[dbo].[report_asset_history]

       ELSE IF @DATASET = 'AssetDetails'
            SELECT 
               : Asset details data
            FROM [SERVER20].[Shop_Canada].[dbo].[report_asset_details]

       ELSE IF @DATASET = '....'
         : 
         : Etc, Etc for CANADA SECTION

    END;

-- USA SECTION ------------------------------------
    IF @LOCATION = 'USA'
    BEGIN
        IF @DATASET = 'AssetHistory'
            SELECT location_id, location_description
            FROM [SERVER20].[Shop_USA].[dbo].[report_asset_history]

       ELSE IF @DATASET = 'AssetDetails'
            SELECT 
               : Asset details data
            FROM [SERVER20].[Shop_USA].[dbo].[report_asset_details]

       ELSE IF @DATASET = '....'
         : 
         : Etc, Etc for USA SECTION

    END;

-- SOME OTHER SECTION ---------------------------
    IF @LOCATION = 'SOME OTHER'
    BEGIN
         : Same logic
    END

  RETURN 0;

END

要管理性能,您可能需要添加可由调用者指定的过滤默认参数,并将WHERE子句添加到数据集选择中。