Search code examples
phpsqlsql-serverresultset

MSSQL: Get results from select in same row


At first let me explain you my DB construct.

I use MSSQL with the sqlsrv driver of php.

I have two DB tables Names and AccountFieldValues.

Names has a variable field NameID and a fix field StringValue.
This table contains all names of the accountfield variables ergo: FirstName,LastName etc.

AccountFieldValues has the same variable field FieldNameID,FieldValue and IdentityID.
This table contains the content of the accountfield variables for every user.

Now to get a specific variable content of the user with the IdentityID=10 and the StringValue='FirstName' you can create a query like that:

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

Or with a inner join

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

When you want a single variable content both methods work well.
But now comes the problem, when i want to get multiple variable contents in one query this gets tricky.

I can expand the two queries like this:

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

And the other query like this:

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

Although ugly as hell in comparison with the upper method this will give me the result of FirstName and LastName in one Result Row. like this:

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

On the other hand the first Method will give me a result looking like this:

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

This isn't desirable because i cannot fetch the wanted AccountFieldValues for every user in one resultset

Is there a method to get the result of the tables like with the ugly joins but faster and easily expandable like the first method?


Solution

  • What you're looking for is a way to pivot your data. Here is one using conditional aggregation:

    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
    

    This is a not dynamic. You can also remove the JOIN on Names and use the acf.FieldId in your CASE instead. Like this:

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

    In case you want a dynamic approach, here is one way using dynamic 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