【问题标题】:MSSQL: Get results from select in same rowMSSQL:从同一行中的选择中获取结果
【发布时间】:2026-01-07 19:00:01
【问题描述】:

首先让我解释一下我的数据库结构。

我使用 MSSQL 和 php 的 sqlsrv 驱动程序。

我有两个数据库表 Names 和 AccountFieldValues。

Names 有一个变量字段 NameID 和一个固定字段 StringValue。
此表包含帐户字段变量的所有名称:FirstName、LastName 等。

AccountFieldValues 具有相同的变量字段 FieldNameID、FieldValue 和 IdentityID。
此表包含每个用户的帐户字段变量的内容。

现在要获取 IdentityID=10 和 StringValue='FirstName' 的用户的特定变量内容,您可以创建如下查询:

SELECT FieldValue FROM AccountFieldValues 
WHERE IdentityID=10 AND FieldNameID IN (SELECT NameID FROM Names WHERE StringValue='FirstName')

或者使用内部连接

SELECT ACF.FieldValue FROM AccountFieldValues ACF
INNER JOIN Names N ON (N.StringValue='FirstName' AND N.NameID=ACF.FieldNameID)
WHERE ACF.IdentityID=10  

当您想要单个变量内容时,两种方法都可以正常工作。
但是现在问题来了,当我想在一个查询中获取多个变量内容时,这变得很棘手。

我可以像这样展开这两个查询:

SELECT FieldValue FROM AccountFieldValues 
WHERE IdentityID=10 AND FieldNameID IN 
(SELECT NameID FROM Names WHERE StringValue='FirstName' OR StringValue='LastName')  

另外一个查询是这样的:

SELECT ACF.FieldValue AS 'FirstName', ACF2.FieldValue AS 'LastName' FROM AccountFieldValues ACF
INNER JOIN Names N ON (N.StringValue='FirstName' AND N.NameID=ACF.FieldNameID)
INNER JOIN AccountFieldValues ACF2 ON (ACF2.IdentityID=ACF.IdentityID)
INNER JOIN Names N2 ON (N2.StringValue='LastName' AND N2.NameID=ACF2.FieldNameID)
WHERE ACF.IdentityID=10

虽然与上面的方法相比丑得要命,但这会给我一个结果行中的 FirstName 和 LastName 的结果。像这样:

FirstName|LastName
------------------
Foo      |Bar

另一方面,第一个方法会给我一个如下所示的结果:

FieldValue
----------
Foo
Bar

这是不可取的,因为我无法为一个结果集中的每个用户获取所需的 AccountFieldValues

有没有一种方法可以像使用丑陋的联接一样获得表格的结果,但像第一种方法一样更快、更容易扩展?

【问题讨论】:

标签: php sql sql-server resultset


【解决方案1】:

您正在寻找一种数据透视方式。这是一个使用条件聚合的例子:

SQL Fiddle

SELECT
    FirstName   = MAX(CASE WHEN n.StringValue = 'FirstName' THEN acf.FieldValue END),
    LastName    = MAX(CASE WHEN n.StringValue = 'LastName' THEN acf.FieldValue END)
FROM AccountFieldValues acf
INNER JOIN Names n
    ON n.NameId = acf.FieldNameId
WHERE acf.IdentityId = 10
GROUP BY acf.IdentityId

这不是动态的。您还可以删除Names 上的JOIN,并在CASE 中使用acf.FieldId。像这样:

MAX(CASE WHEN acf.FieldId = <FIELD_ID_OF_FIRSTNAME> THEN acf.FieldValue END)

如果您想要动态方法,这里是使用动态 sql 的一种方法:

SQL Fiddle

DECLARE @sql1 NVARCHAR(MAX) = '',
        @sql2 NVARCHAR(MAX) = '',
        @sql3 NVARCHAR(MAX) = ''

DECLARE @identityId INT = 10

SELECT @sql1 = 
'SELECT
    acf.IdentityId' + CHAR(10)

SELECT @sql2 = @sql2 +
'   , MAX(CASE WHEN acf.FieldNameId = ' + CONVERT(VARCHAR(10), t.FieldNameId) + ' THEN acf.FieldValue END) AS '
     + QUOTENAME(n.StringValue) + CHAR(10)
FROM(
    SELECT DISTINCT FieldNameId FROM AccountFieldValues WHERE IdentityId = @identityId
)t
INNER JOIN Names n ON t.FieldNameId = n.NameId

SELECT @sql3 =
'FROM AccountFieldValues acf
INNER JOIN Names n
    ON n.NameId = acf.FieldNameId
WHERE acf.IdentityId = @identityId
GROUP BY acf.IdentityId'

DECLARE @finalSql NVARCHAR(MAX) = ''
SET @finalSql = @sql1 + @sql2 + @sql3

EXEC sp_executesql @finalSql, N'@identityId INT', @identityId

【讨论】:

  • 哇!非常感谢!我不知道您可以在一个查询中添加多个查询,这非常有效!
【解决方案2】:

我通常是这样做的:

SELECT *
FROM dbo.Names AS N
CROSS APPLY (
    SELECT MAX(CASE WHEN N.StringValue='FirstName' THEN AFV.FieldValue END)
        , MAX(CASE WHEN N.StringValue='LastName' THEN AFV.FieldValue END)
    FROM dbo.AccountFieldValues AS AFV
    WHERE AFV.FieldNameID = N.NameID
    ) AS AFV(FirstName, LastName);

您可以在那里轻松地在CROSS APPLY 中添加新行。正如Felix Pamittan 所说,这几乎是PIVOT

【讨论】:

    最近更新 更多