【问题标题】:Dynamic query with union based on parameter基于参数的联合动态查询
【发布时间】:2017-02-22 04:00:04
【问题描述】:

我正在开发一个存储过程,该过程获取每个公司的数据并执行 SELECT INTO 语句。目前,我有一个 if 条件,它只适用于选定的公司。但是,在此之后,我有许多联合语句,它们联合了由 SELECT INTO 创建的所有表:

SELECT * INTO ##LevelAll FROM ##Levela
UNION
SELECT * FROM ##Levelb 
UNION
SELECT * FROM ##Levelc
UNION
SELECT * FROM ##Leveld
UNION
SELECT * FROM ##Levele

我遇到的问题是,如果我不运行级别 c 的查询,那么我上面的代码将失败。

有没有一种方法可以动态查询...基于我正在运行存储过程 4 的公司? (我可以为多个公司“CompanyA、CompanyB、CompanyC”运行它)。

【问题讨论】:

  • 公司和等级有什么关系?不要告诉我您将每个公司的记录存储在单独的表中!如果是这样,请重新设计数据库。
  • 您是否也在条件下创建临时表...如果您在存储过程开始时在 if 之外声明它们会怎样...
  • 然后总是运行 C 级的查询,这样这个查询就不会失败。您可以不填充任何内容(IF @doC THEN SELECT ... INTO ... FROM; ELSE SELECT TOP(0) ... INTO ... FROM;,或用于创建具有正确架构的空表的类似技术)。如果您可以通过使其他代码更复杂来避免动态查询,那么这样做通常还是有回报的。然而,根据@Gordon,您可能会更好地预先设计一个更好的设计,因此首先不需要这种查询。

标签: sql-server tsql sql-server-2012


【解决方案1】:

正如在 cmets 中提到的,您将真正受益于简单地更改您的数据库结构。

但是,如果您不能,您可以通过使用参数化的top (x) percent 来避免动态 SQL 和大量重复代码,您可以在其中指定 1000,具体取决于您是否要从该部分返回数据union 或不是。

我还要说,您似乎不会关心此查询中的重复项,因为“级别”是不同的公司,因此您可以使用 union all 而不是 union,这将加快您的表现:

declare @a int = 100
        ,@b int = 100
        ,@c int = 0     -- No records from table c will be returned.
        ,@d int = 100
        ,@e int = 100;

SELECT TOP (@a) PERCENT * 
INTO ##LevelAll
FROM ##Levela
UNION ALL
SELECT TOP (@b) PERCENT * 
FROM ##Levelb
UNION ALL
SELECT TOP (@c) PERCENT * 
FROM ##Levelc
UNION ALL
SELECT TOP (@d) PERCENT * 
FROM ##Leveld
UNION ALL
SELECT TOP (@e) PERCENT * 
FROM ##Levele;

【讨论】:

    【解决方案2】:

    我已经基于通过您的存储过程传递的变量构建了一个动态查询。我的文字被评论为正在发生的事情。

    首先,您需要一个表值函数来拆分您的参数,以便其中列出的每个公司都是结果返回表中的一行。下面是一个函数,你需要在你的数据库上创建它:

    CREATE FUNCTION [dbo].[ufnSplit] 
    ( 
        @string VARCHAR(MAX), 
        @delimiter CHAR(1) 
    ) 
    RETURNS @output TABLE(Item VARCHAR(MAX) 
    ) 
    BEGIN 
        DECLARE @start INT, @end INT 
        SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
        WHILE @start < LEN(@string) + 1 BEGIN 
            IF @end = 0  
                SET @end = LEN(@string) + 1
    
            INSERT INTO @output (Item)  
            VALUES(SUBSTRING(@string, @start, @end - @start)) 
            SET @start = @end + 1 
            SET @end = CHARINDEX(@delimiter, @string, @start)
    
        END 
        RETURN 
    END
    

    接下来在存储过程中,我们使用此函数将您的参数拆分为行,放到一个名为#Companies 的临时表中。然后,我们使用循环遍历#Companies 来创建您的UNION 查询(或者,如果只有一个值,则不创建)。再一次,我对我正在做的事情发表了很多评论。

    USE [DatabaseName]
    GO
    
    CREATE PROCEDURE uspLevelReports
     @param VARCHAR(MAX)
    
    /* Use this for testing your parameters: 
    EXEC uspLevelReports @param = 'A,B,D'
     */
    
    AS
    
    BEGIN TRY
    
    
    IF OBJECT_ID('tempdb..#Companies') IS NOT NULL
     DROP TABLE #Companies
    
    -- use the split function to put each company into a table, and assign it a row number (necessary for the loop below)
    SELECT
    Item
    ,ROW_NUMBER() OVER(ORDER BY Item ASC) AS RowNo
    INTO #Companies
    FROM dbo.ufnSplit(@param,',')
    
    
    -- Select below is for testing so you can see the effect:
    -- SELECT * FROM #Companies
    
    -- Create the basic query here, with the company suffix
    DECLARE @sql NVARCHAR(4000) =  'SELECT * From tempdb..##Level'
    
    -- Start to loop through #Companies, for each row no higher than 1, a union will be added
    -- First, tell the loop where to start and end:
    DECLARE @RowNo INT = 1 -- Your starting RowNo, should always be 1 so won't do a MIN() here
    DECLARE @MaxNo INT = ( -- The highest RowNo in #Companies
        SELECT 
        MAX(RowNo)
        FROM #Companies
        )
    DECLARE @originalSQL  NVARCHAR(4000) = @sql -- @Sql will be modified in the loop but we still need the original form to keep adding to each union if one is there
    
    WHILE @RowNo <= @MaxNo  -- we will increase the value of @RowNo by 1 each time,the loop will break when @RowNo gets higher than @MaxNo
    BEGIN
    
    -- Find the company that corresponds to the @RowNo value in #Companies and add it to a variable:
    DECLARE @suffix VARCHAR(100) = (
        SELECT 
        Item 
        FROM #Companies 
        WHERE RowNo = @RowNo)
    
    IF @RowNo = @MaxNo -- If statement for the final Item in #Companies, this stops us adding UNION where we've finished the query
        SET @sql += @suffix
    ELSE
        SET @sql += @suffix + ' UNION ' + @originalSQL;
    
    SET @RowNo += 1 -- Add one to move to next RowNo in #Companies
    
    PRINT @sql -- This is for testing so you can see in Messages how the query is being built each loop
    
    END
    
    PRINT 'Final query: ' + @sql  -- This is for testing so you can see in Messages what your final query is
    
    
    
    -- Create your level all table
    IF OBJECT_ID('tempdb..##LevelAll') IS NOT NULL
     DROP TABLE ##LevelAll
    
    -- You need to create your ##LevelAll table with all the columns needed, so amend them with their datatypes below mine is just for example:
    
    CREATE TABLE ##LevelAll(
    Company CHAR(1)
    ,value INT
    )
    --- insert into your level all table
    INSERT INTO ##LevelAll
    -- by executing the statement inside your string variable
    exec sp_executesql @sql
    -- publish the results
    SELECT * FROM ##LevelAll
    
    END TRY
    
    BEGIN CATCH
    
        -- Your error trapping variables
    
    END CATCH
    

    【讨论】:

    • 谢谢,但这不是我想要的。本质上,我正在尝试执行一个 Union 语句,但是如果参数没有该 Company 代码未执行,因此我实际上是在为不存在的表执行 UNION,因为代码为每个公司执行为 SELECT INTO 语句.
    猜你喜欢
    • 1970-01-01
    • 2021-10-27
    • 1970-01-01
    • 2013-07-31
    • 1970-01-01
    • 1970-01-01
    • 2022-12-10
    • 2010-10-04
    • 2014-10-11
    相关资源
    最近更新 更多