【问题标题】:Creating an Dynamic Tables in MS-SQL (feasibility)在 MS-SQL 中创建动态表(可行性)
【发布时间】:2019-03-22 15:48:21
【问题描述】:

有没有办法根据动态值创建表数组(或动态创建的表)?例如,我知道在 C++ 中可以使用指针和动态内存,但我需要在 SQL 中执行类似的操作。

用户指定他们希望查看每年 X 年的数据,我们需要查看可以构建到临时表中的 15+ 列(每年都相同)。

用户选择5 年份的数据和当前年份为2018,所以我想为2018, 2017, 2016, 2015 and 2014. 动态创建一个本地表。每个表都将跟踪客户数据、销售等。每个表都一样。

如果上述情况不可行,我们是否可以限制用户仅选择年份并动态始终坚持 5 年。用户指定他们想以2017 开头,我们默认为他们构建2017, 2016 ... 2013.

我可以继续说创建表#year1, #year2, #year3 etc. 但这需要大量编码(主要是提前复制和粘贴)。有没有办法避免这种情况——比如创建表#years[] 或指针?然后动态构建某种#years 表数组。

我们最终要做的是通过完全相同的查询/逻辑逐年提取完全相同的数据集。然后最后比较谁/属于哪一年以及如何。底线是避免复制和粘贴相同的声明/选择/插入语句 5 次或更多次? (并且尽可能避免光标/循环)

我尝试编写类似这样的代码

DECLARE @season_counter int = 5

    DECLARE @cnt int = 0
    Declare @cnt_v varchar(1)  =  CONVERT(varchar(10), @cnt)

    DECLARE @SQL NVARCHAR(max) = ''

     WHILE @cnt <= @season_counter
     BEGIN
        select   @SQL =  @SQL + ' create table #year' + @cnt_v
         +  ' (customer_no int, order_no int, perf_no int, due_amt money, paid_amt money, status int ) ' 

        exec sp_executesql @SQL
        print @SQL 

        set @cnt = @cnt + 1
        set @cnt_v = CONVERT(varchar(10), @cnt)
        set @sql = ''


    END

select * from #year1
select * from #year2

以上产生的错误

Msg 208, Level 16, State 0, Line 81
Invalid object name '#year1'. 

我不能使用全局变量,而且我肯定不想为 20 个表声明 Create 语句(作为某个最大值)。我希望它是动态的和动态的。请指教。

【问题讨论】:

  • 听起来你需要一个立方体.....
  • @MitchWheat 这对于立方体来说太小了(无论如何对我们来说)。

标签: arrays sql-server dynamic dynamic-memory-allocation sp-executesql


【解决方案1】:

毫无疑问,动态 SQL 可以成为您的朋友: 例如,要创建您需要的数据透视表,您可以使用如下脚本:

GO
DECLARE @startYear AS INT = 2000
DECLARE @endYear AS INT =2100
--Magic insert
;
WITH L0
AS ( SELECT c
     FROM   (   SELECT 1
                UNION ALL
                SELECT 1 ) AS D(c) ) , -- 2^1
     L1
AS ( SELECT NULL AS c
     FROM   L0 AS A
            CROSS JOIN L0 AS B ) ,       -- 2^2
     L2
AS ( SELECT NULL AS c
     FROM   L1 AS A
            CROSS JOIN L1 AS B ) ,       -- 2^4
     L3
AS ( SELECT NULL AS c
     FROM   L2 AS A
            CROSS JOIN L2 AS B ) ,       -- 2^8
     L4
AS ( SELECT NULL AS c
     FROM   L3 AS A
            CROSS JOIN L3 AS B ) ,       -- 2^16
     L5
AS ( SELECT NULL AS c
     FROM   L4 AS A
            CROSS JOIN L4 AS B ) ,       -- 2^32
     Nums
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )) AS id
     FROM   L5 ) ,
     years
AS ( SELECT id AS [year]
     FROM   Nums
     WHERE  id >= @startYear AND id<=@endYear )

SELECT [year] INTO #years
FROM   years


DECLARE @columns VARCHAR(MAX)='' 
DECLARE @curentYear int = @startYear;
WHILE @curentYear <= (SELECT MAX([year]) from #years)
BEGIN
    SET @columns+= '['+(SELECT Cast([year] as VARCHAR(4)) FROM #years WHERE [year] = @curentYear)+'],'
    SET @curentYear += 1;
END

SET @columns = SUBSTRING(@columns,1,LEN(@columns)-1)

DECLARE @SQL NVARCHAR(max)=
'SELECT * FROM #years PIVOT(MAX([year]) FOR [year] IN ('+   @columns+')) AS piv'

EXEC sys.sp_executesql @SQL

【讨论】:

    【解决方案2】:

    如果不使用某种循环和动态 SQL,我看不出有任何方法可以做到这一点。 [已添加:] 您的循环遇到了 SQL Server 的已知限制:您无法通过动态 SQL 创建临时表。如果先创建临时表,则可以在动态 SQL 中引用它,但这违背了目的。但是,您可以通过 SELECT INTO 功能创建表。这会创建一个实际的表,但您可以在代码中删除并重新创建它们。

    就我个人而言,我也喜欢为这些东西创建一个新模式——通常是“x”——这样在 Management Studio 中它们只显示在底部,毕竟是“dbo”模式表。您只创建一次架构,因此不要在循环中包含该部分:

    CREATE SCHEMA x;
    GO
    
    DECLARE @SQLCmd NVARCHAR(MAX)
    --Loopify this part:
    SET @SQLCmd = 
    'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''Temp1'')
        DROP TABLE Temp1;
    
    SELECT *
    INTO x.Temp1
    FROM YourDB.dbo.YourTable
    WHERE foo = ''bar'';'
    
    --PRINT @SQLCmd
    EXEC(@SQLCmd);
    

    【讨论】:

    • 例如,我将如何使用它来创建 X 数量的诱惑表。我得到了作为参数传递并作为字符串的一部分执行的脚本。但是我该怎么做才能创建#table1、#table2 等。我需要创建/填充表。
    • 这是个好主意,测试一下临时表是否存在——如果存在则删除它。然后从头开始创建它。我将对此进行测试。如果多人尝试运行它会发生什么。一个人可以放下表格,而另一个人试图填充它。
    • 是的,那会是个问题。您可以在表名或类似名称中添加时间戳,但您应该有某种清理过程,例如在午夜运行并删除任何以“temp”开头的表的代理。
    猜你喜欢
    • 1970-01-01
    • 2022-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    相关资源
    最近更新 更多