转置 SQL 服务器 Select 中的数据

Transpose data in SQL Server Select

我想知道是否有更好的方法来编写此查询。它达到了目标结果,但我的同事更喜欢在没有子选择到临时表 t1-t3 的情况下编写它。这里的主要“挑战”是将来自 dbo.ReviewsData 的数据与来自 dbo.Prodcucts 和 dbo.Reviews.

的其余数据一起转置为一行
CREATE TABLE dbo.Products (
idProduct int identity,
product_title varchar(100)
PRIMARY KEY (idProduct)
);
INSERT INTO dbo.Products VALUES
(1001, 'poptart'),
(1002, 'coat hanger'),
(1003, 'sunglasses');

CREATE TABLE dbo.Reviews (
Rev_IDReview int identity,
Rev_IDProduct int
PRIMARY KEY (Rev_IDReview)
FOREIGN KEY (Rev_IDProduct) REFERENCES dbo.Products(idProduct)
);
INSERT INTO dbo.Reviews VALUES
(456, 1001),
(457, 1002),
(458, 1003);

CREATE TABLE dbo.ReviewFields (
RF_IDField int identity,
RF_FieldName varchar(32),
PRIMARY KEY (RF_IDField)
);
INSERT INTO dbo.ReviewFields VALUES
(1, 'Customer Name'),
(2, 'Review Title'),
(3, 'Review Message');

CREATE TABLE dbo.ReviewData (
RD_idData int identity,
RD_IDReview int,
RD_IDField int,
RD_FieldContent varchar(100)
PRIMARY KEY (RD_idData)
FOREIGN KEY (RD_IDReview) REFERENCES dbo.Reviews(Rev_IDReview)
);
INSERT INTO dbo.ReviewData VALUES
(79, 456, 1, 'Daniel'),
(80, 456, 2, 'Love this item!'),
(81, 456, 3, 'Works well...blah blah'),
(82, 457, 1, 'Joe!'),
(84, 457, 2, 'Pure Trash'),
(85, 457, 3, 'It was literally a used banana peel'),
(86, 458, 1, 'Karen'),
(87, 458, 2, 'Could be better'),
(88, 458, 3, 'I can always find something wrong');        

SELECT P.product_title as "item", t1.ReviewedBy, t2.ReviewTitle, t3.ReviewContent
        FROM dbo.Reviews R
        
        INNER JOIN dbo.Products P
        ON P.idProduct = R.Rev_IDProduct
        
        INNER JOIN (
           SELECT D.RD_FieldContent AS "ReviewedBy", D.RD_IDReview
           FROM dbo.ReviewsData D
           WHERE D.RD_IDField = 1
        ) t1
        ON t1.RD_IDReview = R.Rev_IDReview
        
        INNER JOIN (
           SELECT D.RD_FieldContent AS "ReviewTitle", D.RD_IDReview
           FROM dbo.ReviewsData D
           WHERE D.RD_IDField = 2
        ) t2
        ON t2.RD_IDReview = R.Rev_IDReview
        
        INNER JOIN (
           SELECT D.RD_FieldContent AS "ReviewContent", D.RD_IDReview
           FROM dbo.ReviewsData D
           WHERE D.RD_IDField = 3
        ) t3
        ON t3.RD_IDReview = R.Rev_IDReview

编辑:我更新了此 post 表的创建语句而不是数据图像(我感到羞耻)和更具体的描述到底需要改进什么。感谢大家的评论和耐心。

正如其他人在评论中所说,该查询客观上没有任何错误。但是,您可能会说它冗长且难以阅读。

缩短它的一种方法是将 INNER JOIN 替换为 CROSS APPLY:

    INNER JOIN (
       SELECT D.RD_FieldContent AS 'ReviewedBy', D.RD_IDReview
       FROM dbo.ReviewsData D
       WHERE D.RD_IDField = 1
    ) t1
    ON t1.RD_IDReview = R.Rev_IDReview

APPLY 允许您引用外部查询中的值,就像在子查询中一样:

    CROSS APPLY (
       SELECT D.RD_FieldContent AS 'ReviewedBy'
       FROM dbo.ReviewsData D
       WHERE D.RD_IDField = 1 AND D.RD_IDReview = R.Rev_IDReview
    ) t1

我认为 APPLY 就像一个引入新列的子查询。它就像子查询和连接之间的交叉。好处:

  • 查询可以更短,因为您不必重复 ID 列两次。
  • 您不必公开不需要的列。

缺点:

  • 如果 APPLY 中的查询引用了外部值,那么您无法提取它,并且 运行 不加修改地单独使用它。
  • APPLY 特定于 Sql 服务器,并没有被广泛使用。

另一件需要考虑的事情是使用子查询而不是联接来获取您只需要在一个地方的值。好处:

  • 可以缩短查询,因为您不必重复 ID 列两次,也不必为输出列提供唯一的别名。

  • 您只需查看一处即可查看整个子查询。

  • 子查询只能 return 1 行,因此如果只需要 1 行,您不会不小心创建额外的行。

    SELECT
        P.product_title as 'item',
    
        (SELECT D.RD_FieldContent
           FROM dbo.ReviewsData D
           WHERE D.RD_IDField = 1 AND
                 D.RD_IDReview = R.Rev_IDReview) as ReviewedBy,
    
        (SELECT D.RD_FieldContent
           FROM dbo.ReviewsData D
           WHERE D.RD_IDField = 2 AND
                 D.RD_IDReview = R.Rev_IDReview) as ReviewTitle,   
    
        (SELECT D.RD_FieldContent
           FROM dbo.ReviewsData D
           WHERE D.RD_IDField = 3 AND
                 D.RD_IDReview = R.Rev_IDReview) as ReviewContent
    
    FROM dbo.Reviews R
     INNER JOIN dbo.Products P ON P.idProduct = R.Rev_IDProduct
    

编辑:

我刚想到你让连接本身变得不必要地冗长(@Dale K 实际上已经在评论中指出了这一点):

    INNER JOIN (
       SELECT D.RD_FieldContent AS 'ReviewedBy', D.RD_IDReview
       FROM dbo.ReviewsData D
       WHERE D.RD_IDField = 1
    ) t1
    ON t1.RD_IDReview = R.Rev_IDReview

较短:

    SELECT RevBy.RD_FieldContent AS 'ReviewedBy'
    ...
    INNER JOIN dbo.ReviewsData RevBy
                 ON RevBy.RD_IDReview = R.Rev_IDReview AND
                    RevBy.RD_IDField = 1

最初提交的查询无疑是不必要的冗长。在消化了来自社区的各种反馈后,它已被修改为以下内容,工作出色。回想起来,我觉得最初用子选择来做这件事很愚蠢。当涉及到 SQL 时,我显然充其量处于中间状态 - 我没有意识到“JOIN”语句中的“ON”子句中可以包含“AND”子句。不知道为什么我会做出如此糟糕的假设。

SELECT 
P.product_title as 'item',
D1.RD_FieldContent as 'ReviewedBy',
D2.RD_FieldContent as 'ReviewTitle',
D3.RD_FieldContent as 'ReviewContent'
FROM dbo.Reviews R
INNER JOIN dbo.Products P
ON P.idProduct = R.Rev_IDProduct
INNER JOIN dbo.ReviewsData D1
ON D1.RD_IDReview = R.Rev_IDReview AND D1.RD_IDField = 1
INNER JOIN dbo.ReviewsData D2
ON D2.RD_IDReview = R.Rev_IDReview AND D2.RD_IDField = 2
INNER JOIN dbo.ReviewsData D3
ON D3.RD_IDReview = R.Rev_IDReview AND D3.RD_IDField = 3