MSSQL:从同一行的 select 中获取结果
MSSQL: Get results from select in same row
首先让我向您解释一下我的数据库结构。
我将 MSSQL 与 php 的 sqlsrv 驱动程序一起使用。
我有两个数据库 tables Names 和 AccountFieldValues。
Names 有一个可变字段 NameID 和一个固定字段 StringValue。
此 table 包含帐户字段变量的所有名称,因此:名字、姓氏等。
AccountFieldValues 具有相同的变量字段 FieldNameID、FieldValue 和 IdentityID。
table 包含每个用户的帐户字段变量的内容。
现在要获取 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
有没有一种方法可以像丑陋的连接一样获得 table 的结果,但像第一种方法一样更快、更容易扩展?
您正在寻找的是一种数据透视方法。这是一个使用条件聚合的方法:
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:
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
我通常这样做:
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
。
首先让我向您解释一下我的数据库结构。
我将 MSSQL 与 php 的 sqlsrv 驱动程序一起使用。
我有两个数据库 tables Names 和 AccountFieldValues。
Names 有一个可变字段 NameID 和一个固定字段 StringValue。
此 table 包含帐户字段变量的所有名称,因此:名字、姓氏等。
AccountFieldValues 具有相同的变量字段 FieldNameID、FieldValue 和 IdentityID。
table 包含每个用户的帐户字段变量的内容。
现在要获取 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
有没有一种方法可以像丑陋的连接一样获得 table 的结果,但像第一种方法一样更快、更容易扩展?
您正在寻找的是一种数据透视方法。这是一个使用条件聚合的方法:
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:
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
我通常这样做:
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
。