I'm not sure if this is the best way, given you have a fairly small fixed data set, but it's an excuse for a dynamic pivot.
First get all the column names you need like this. They're not your exact column names, but you get the idea
DECLARE @columns varchar(max);
SELECT @columns = COALESCE (
@columns + ',[' + cast(DATEPART(yyyy, Date) as varchar) + '_' + cast(DATEPART(mm, Date) as varchar) + ']',
'[' + cast(DATEPART(yyyy, Date) as varchar)+ '_' + cast(DATEPART(mm, Date) as varchar) + ']'
)
FROM #Test
GROUP BY
DATEPART(yyyy, Date),DATEPART(MM, Date)
ORDER BY DATEPART(yyyy, Date),DATEPART(MM, Date);
SELECT @columns;
Next get a dynamic pivot on just one VALx, in this case VAL1
DECLARE @SQL VARCHAR(MAX)
SET @SQL = 'SELECT *
FROM ( SELECT ''Val1'' AS VALUE,
cast(DATEPART(yyyy, Date) as varchar) + ''_'' + cast(DATEPART(mm, Date) as varchar) AS DateMonth,
Val1
FROM #Test ) P
PIVOT (
MAX(Val1) FOR DateMonth in (' + @columns + ')
) AS PVT';
PRINT @SQL
That will give you the Val1 row only.
As you only have VAL1 to VAL4 - its probably easier to just UNION this with 3 copies of itself, replacing VAL1 with VAL2, VAL3, VAL4. But its possible to continue ... with help from a tally table ... Replace the above dynamic pivot with this one to do it for you.
DECLARE @SQL VARCHAR(MAX);
SELECT @SQL = COALESCE ( @SQL + ' SELECT *
FROM ( SELECT ''Val' + CAST(N-1 AS VARCHAR) + ''' AS VALUE,
cast(DATEPART(yyyy, Date) as varchar) + ''_'' + cast(DATEPART(mm, Date) as varchar) AS DateMonth,
Val' + CAST(N-1 AS VARCHAR) + '
FROM #Test ) P
PIVOT (
MAX(Val' + CAST(N-1 AS VARCHAR) + ') FOR DateMonth in (' + @columns + ')
) AS PVT '
+ CASE N WHEN 5 THEN '' ELSE ' UNION ' END
, '' )
FROM dbo.Tally AS N
WHERE N < 6;
PRINT @SQL
EXEC (@SQL)