You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy仅返回含回车行的最后一行(SQL Server)问题解决

问题描述

我编写了一条SQL查询,用于基于现有Schema生成表的创建DDL语句,以此实现Schema备份(因无SQL Server常规操作权限)。在SSMS中执行该查询时,能正常返回**单行单列(表头为"DDL")**的完整DDL内容,但通过SQLAlchemy执行时,仅返回最后一行内容。

示例SQL代码:

SET NOCOUNT OFF;
IF OBJECT_ID('[<schema_name>].[<table_name>]','SN') IS NOT NULL DROP SYNONYM [<schema_name>].[<table_name>];
IF OBJECT_ID('[<schema_name>].[<table_name>]','U') IS NOT NULL DROP TABLE [<schema_name>].[<table_name>];
RAISERROR('CREATE TABLE %s',10,1, '[<schema_name>].[<table_name>]') WITH NOWAIT;

CREATE TABLE [<schema_name>].[<table_name>] (
 ... column definitions ...
)  
RAISERROR('CREATING INDEXES OF  %s',10,1, '[<schema_name>].[<table_name>]') WITH NOWAIT; 

对应的Python代码:

def get_create_ddls(table_name_list: list, conn) -> list:
    fd = open('CreateCreateDDL.sql', 'r')
    original_query = fd.read()
    fd.close()

    return_list = []
    for name in table_name_list:
        # prefixed the views as "V_"
        isView = name[:2] == "V_"

        if isView:
            tbl_insert_str = "('" + name[2:] + "')"
        else:
            tbl_insert_str = "('" + name + "')"

        query = original_query.replace("<INSERT_TABLE_NAME_HERE>", tbl_insert_str, 1)

        result = conn.execute(sqlalchemy.text(query))

        # TODO: Only reading last line of the row
        value_str = ""
        for row in result:
            temp_value = row.DDL 
            value_str += temp_value

        print(value_str)

        if isView:
            # put the "V_" back into the CREATE statement
            view_name = name[2:(name.find("].[") + 3)] + "V_" + name[(name.find("].[") + 3):]
            value_str = value_str.replace(name[2:], view_name)

        return_list.append(value_str)

    return return_list

请问如何无需手动删除源查询中的所有回车来解决该问题?

解决方案

方法1:修改SQL,用单个SELECT返回完整DDL

核心思路是将所有DDL片段拼接成一个完整的字符串,通过单个SELECT语句返回,避免多个结果集或消息干扰。

针对SQL Server 2017+(支持STRING_AGG)

SELECT STRING_AGG(ddl_segment, CHAR(13) + CHAR(10)) AS DDL
FROM (
    -- 生成表创建语句头部
    SELECT 'CREATE TABLE [' + s.name + '].[' + t.name + '] (' AS ddl_segment
    FROM sys.tables t
    JOIN sys.schemas s ON t.schema_id = s.schema_id
    WHERE t.name = '<table_name>' AND s.name = '<schema_name>'

    UNION ALL

    -- 生成列定义
    SELECT '    [' + c.name + '] ' + tp.name + 
           CASE WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR) END + ')' ELSE '' END +
           CASE WHEN c.is_nullable = 0 THEN ' NOT NULL' ELSE ' NULL' END + ','
    FROM sys.columns c
    JOIN sys.tables t ON c.object_id = t.object_id
    JOIN sys.schemas s ON t.schema_id = s.schema_id
    JOIN sys.types tp ON c.system_type_id = tp.system_type_id
    WHERE t.name = '<table_name>' AND s.name = '<schema_name>'

    UNION ALL

    -- 生成表创建语句尾部
    SELECT ') ON [PRIMARY]' AS ddl_segment

    UNION ALL

    -- 生成索引语句
    SELECT 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' ELSE '' END + 'NONCLUSTERED INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' +
           STRING_AGG('[' + c.name + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END, ', ') + ')'
    FROM sys.indexes i
    JOIN sys.tables t ON i.object_id = t.object_id
    JOIN sys.schemas s ON t.schema_id = s.schema_id
    JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
    JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
    WHERE t.name = '<table_name>' AND s.name = '<schema_name>' AND i.type_desc = 'NONCLUSTERED'
    GROUP BY i.name, s.name, t.name, i.is_unique
) AS ddl_parts

针对SQL Server 2016及更早版本(用FOR XML PATH拼接)

SELECT 
    STUFF((
        SELECT CHAR(13) + CHAR(10) + ddl_segment
        FROM (
            -- 表头部
            SELECT 'CREATE TABLE [' + s.name + '].[' + t.name + '] (' AS ddl_segment
            FROM sys.tables t
            JOIN sys.schemas s ON t.schema_id = s.schema_id
            WHERE t.name = '<table_name>' AND s.name = '<schema_name>'

            UNION ALL

            -- 列定义
            SELECT '    [' + c.name + '] ' + tp.name + 
                   CASE WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR) END + ')' ELSE '' END +
                   CASE WHEN c.is_nullable = 0 THEN ' NOT NULL' ELSE ' NULL' END + ','
            FROM sys.columns c
            JOIN sys.tables t ON c.object_id = t.object_id
            JOIN sys.schemas s ON t.schema_id = s.schema_id
            JOIN sys.types tp ON c.system_type_id = tp.system_type_id
            WHERE t.name = '<table_name>' AND s.name = '<schema_name>'

            UNION ALL

            -- 表尾部
            SELECT ') ON [PRIMARY]' AS ddl_segment

            UNION ALL

            -- 索引
            SELECT 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' ELSE '' END + 'NONCLUSTERED INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' +
                   STUFF((
                       SELECT ', [' + c2.name + '] ' + CASE WHEN ic2.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
                       FROM sys.index_columns ic2
                       JOIN sys.columns c2 ON ic2.object_id = c2.object_id AND ic2.column_id = c2.column_id
                       WHERE ic2.object_id = i.object_id AND ic2.index_id = i.index_id
                       FOR XML PATH(''), TYPE
                   ).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')'
            FROM sys.indexes i
            JOIN sys.tables t ON i.object_id = t.object_id
            JOIN sys.schemas s ON t.schema_id = s.schema_id
            WHERE t.name = '<table_name>' AND s.name = '<schema_name>' AND i.type_desc = 'NONCLUSTERED'
        ) AS parts
        FOR XML PATH(''), TYPE
    ).value('.', 'NVARCHAR(MAX)'), 1, 2, '') AS DDL

这样修改后,SQL会返回一个包含完整DDL的单行结果,SQLAlchemy只需读取这一行即可,无需处理多个结果集。

方法2:修改Python代码,遍历所有结果集

如果无法修改SQL,可调整Python代码,让SQLAlchemy遍历所有结果集,收集所有包含DDL列的内容:

def get_create_ddls(table_name_list: list, conn) -> list:
    fd = open('CreateCreateDDL.sql', 'r')
    original_query = fd.read()
    fd.close()

    return_list = []
    for name in table_name_list:
        isView = name[:2] == "V_"

        if isView:
            tbl_insert_str = "('" + name[2:] + "')"
        else:
            tbl_insert_str = "('" + name + "')"

        query = original_query.replace("<INSERT_TABLE_NAME_HERE>", tbl_insert_str, 1)

        result = conn.execute(sqlalchemy.text(query))
        value_str = ""
        
        # 遍历所有结果集
        current_result = result
        while current_result is not None:
            try:
                # 读取当前结果集的所有行
                for row in current_result:
                    if hasattr(row, 'DDL'):
                        value_str += row.DDL
            except Exception:
                # 跳过无DDL列的结果集(如DROP语句的影响行数)
                pass
            # 获取下一个结果集
            current_result = current_result.nextset()

        print(value_str)

        if isView:
            view_name = name[2:(name.find("].[") + 3)] + "V_" + name[(name.find("].[") + 3):]
            value_str = value_str.replace(name[2:], view_name)

        return_list.append(value_str)

    return return_list

这段代码会逐个处理SQL执行产生的所有结果集,收集所有包含DDL字段的内容,避免只获取最后一个结果集的问题。

方法3:调整SQL的消息输出逻辑

如果SQL中的RAISERROR只是用于进度提示,而非DDL内容的一部分,可将这些语句的级别调整为不干扰结果集的形式,或改用PRINT语句(SQLAlchemy默认不捕获PRINT消息,不会影响结果集读取)。同时确保核心DDL内容通过单个SELECT语句返回。


内容的提问来源于stack exchange,提问作者javery

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:17:03