从查询的WITHIN将SQL结果从“N行1列”转换为“1行N列”

问题描述 投票:0回答:2

使用PIVOT做这个看似微不足道的任务应该是简单而明显的 - 但事实并非如此。

什么是最简洁的转换方式,不一定使用数据透视表,仅限于使用“纯”SQL(请参阅下面的其他因素)?

它不应该影响答案,但请注意,Python 3.X前端正用于在MS SQL Server 2012后端上运行SQL查询。

背景 :

我需要通过调用Python 3.x中的SQL代码来创建CSV文件。 CSV标题行是根据保存查询结果的SQL表的字段(列)名称创建的。 以下SQL代码提取字段名称并将它们返回为N行1列 - 但我需要它们作为N行的1行。 (在下面的例子中,最终结果必须是“A”,“B”,“C”。)

CREATE TABLE #MyTable   -- ideally the real code uses "DECLARE @MyTable TABLE"
(           
    A  varchar( 32 ),   
    B  varchar( 32 ),   
    C  varchar( 32 )    
) ;
CREATE TABLE #MetaData  -- ideally the real code uses "DECLARE @MetaData TABLE"
(
    NameOfField varchar( 32 ) not NULL
) ;
INSERT INTO #MetaData 
SELECT   name 
FROM     tempdb.sys.columns as X
WHERE   ( object_id = Object_id( 'tempdb..#MyTable' ) )     
ORDER BY column_id ;    -- generally redundant, ensures correct order if results returned in random order
/*
OK so far, the field names are returned as 3 rows of 1 column (entitled "NameOfField"). 
Pivoting them into 1 row of 3 columns should be something simple like:
*/
SELECT NameOfField
FROM #MetaData AS Source
PIVOT
(
    COUNT( [ NameOfField ] )  FOR [ NameOfField ] 
                  IN ( #MetaData )   -- I've tried "IN (SELECT NameOfField FROM #Metadata)"
) AS Destination ;

此错误会引发两次,一次是COUNT,一次是PIVOT语句的“FOR”子句:

Msg 207, Level 16, State 1, Line 32  
Invalid column name ' NameOfField'.

如何使用#Metadata的内容让PIVOT工作?还是有另一种简单的方法吗?

其他背景因素需要注意:

  • OBDC(Python的pyodbc包)用于传递SQL查询 - 并将结果(游标)返回到Python 3.x前端。因此,在将结果集返回到Python之前,没有机会使用任何类型的手动干预。
  • 上面的SQL代码旨在成为传递给SQL的每个查询的标准样板。代码必须动态地“适应”#MyTable的结构(例如,如果删除字段B而在C之后添加D和E,则最终结果必须是“A”,“C”,“D”,“E” )。这意味着表的字段名称绝不能出现在PIVOT的IN子句中(#MetaData表旨在提供这些值)。
  • 必须使用“标准”SQL。必须避免使用所有特定于供应商的(例如Microsoft)扩展/实用程序(例如“bcp”,sqlcmd),除非有非常令人信服的理由使用它们(因为“它在那里”不计算在内)。
  • 由于已知原因,select子句(到#Metadata)不适用于临时变量(@MyTable)。是否存在适用于临时变量的等效Select(即@MetaData)?

更新:这个问题与SQL Server dynamic PIVOT query?略有不同。在我的情况下,我必须保留字段的顺序,这个问题不是必需的。

为什么我需要这样做:

  • python代码是非技术人员的GUI。他们使用GUI来挑选和选择从大量报告中运行的(或甚至所有)SQL报告。
  • 像Excel这样的应用程序用于查看这些文件:为了让用户满意,每个CSV文件都必须有标题行。标题行将包含SQL表中包含查询结果的字段名称。
  • 这些脚本可以随时更改(例如添加/删除列),而无需提前通知。为满足用户需求,标题行必须自动“自行调整”以进行相应的更改。下面的SQL代码实现了这一点。
  • 标题行与查询结果合并(使用UNION)以形成传递回Python的结果集(游标)。然后,Python处理返回的数据并创建客户使用的CSV文件(包括标题行)。

简而言之:我们有很多网站,很多用户,很多查询。通过让SQL“动态创建”标题行,我们消除了必须手动管理/协调/推出SQL更改给所有受影响方的麻烦。

sql sql-server
2个回答
0
投票

我不确定“纯粹的”sql是什么。你指的是ANSI-92 SQL吗?

无论如何,如果你可以使用SQL变量,试试这个:

DECLARE @STRING VARCHAR(MAX)

SELECT  @STRING = COALESCE(@STRING + ', ' + '"' + NameOfField + '"', '"' + NameOfField + '"')
FROM    #MetaData  

SELECT   @STRING 

/*
Results:
"A", "B", "C"
*/

0
投票

致@Tab Alleman,谢谢。我能够修改SQL Server dynamic PIVOT query?的答案,以满足我所有需求的方式进行交换(见下文)。

注意:由于某种原因,“DISTINCT”关键字按字母顺序放置字段 - 这是我不想要的。 评论该单词(如下所述)保留了字段的顺序。我对这样做有点不安,但在这种情况下它应该是安全的,因为选择#MetaData的值保证是唯一的。 通过在#MyTable中交换字段A和B并取消注释“DISTINCT”关键字,可以很容易地看出差异

--drop table #MyTable 
--drop table #MetaData 

Create TABLE #MyTable 
(           
    A  varchar( 10 ),
    B  varchar( 10 ),
    C  varchar( 10 )
)
;
CREATE TABLE #MetaData 
(
   NameOfField  varchar( 100 ) not NULL,
   Position int
)
;

INSERT INTO #MetaData 
SELECT   name, column_id
FROM     tempdb.sys.columns as X
WHERE    ( object_id = Object_id( 'tempdb..#MyTable' ) ) 
--ORDER BY column_id    -- normally redundant, guards against results being returned in random order
;

select * from #MetaData 

DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX);

SET @cols = STUFF( (SELECT 
--                  DISTINCT 
                    ',' + QUOTENAME( c.NameOfField ) 
            FROM #MetaData AS  c

            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

--print( @cols )

set @query = 'SELECT ' + @cols + ' from 
           (
                select NameOfField
                from #MetaData
           ) AS x
            pivot 
            (
                MAX( NameOfField )
                for NameOfField in ( '+ @cols + ' )
            ) AS p 

            '
--print( @query )

execute( @query )

drop table #MyTable 
drop table #MetaData 
© www.soinside.com 2019 - 2024. All rights reserved.