#sql #sql-server
#sql #sql-сервер #sql-server-2008 #сводная
Вопрос:
Я ищу эффективный способ преобразования строк в столбцы в SQL Server, я слышал, что PIVOT работает не очень быстро, и мне нужно иметь дело с большим количеством записей.
Это мой пример:
ID | Значение | ColumnName |
---|---|---|
1 | Джон | FirstName |
2 | 2.4 | Количество |
3 | ZH1E4A | PostalCode |
4 | Форк | Фамилия |
5 | 857685 | Номер учетной записи |
Это мой результат:
FirstName | Количество | PostalCode | Фамилия | Номер учетной записи |
---|---|---|---|---|
Джон | 2.4 | ZH1E4A | Форк | 857685 |
Как я могу построить результат?
Ответ №1:
Существует несколько способов преобразования данных из нескольких строк в столбцы.
Использование PIVOT
В SQL Server вы можете использовать PIVOT
функцию для преобразования данных из строк в столбцы:
select Firstname, Amount, PostalCode, LastName, AccountNumber
from
(
select value, columnname
from yourtable
) d
pivot
(
max(value)
for columnname in (Firstname, Amount, PostalCode, LastName, AccountNumber)
) piv;
Смотрите демонстрацию.
Сводная таблица с неизвестным количеством columnnames
Если у вас есть неизвестное количество columnnames
элементов, которые вы хотите перенести, вы можете использовать динамический SQL:
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' QUOTENAME(ColumnName)
from yourtable
group by ColumnName, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = N'SELECT ' @cols N' from
(
select value, ColumnName
from yourtable
) x
pivot
(
max(value)
for ColumnName in (' @cols N')
) p '
exec sp_executesql @query;
Смотрите демонстрацию.
Использование агрегатной функции
Если вы не хотите использовать PIVOT
функцию, вы можете использовать агрегатную функцию с CASE
выражением:
select
max(case when columnname = 'FirstName' then value end) Firstname,
max(case when columnname = 'Amount' then value end) Amount,
max(case when columnname = 'PostalCode' then value end) PostalCode,
max(case when columnname = 'LastName' then value end) LastName,
max(case when columnname = 'AccountNumber' then value end) AccountNumber
from yourtable
Смотрите демонстрацию.
Использование нескольких соединений
Это также можно выполнить с помощью нескольких объединений, но вам понадобится некоторый столбец для связывания каждой из строк, которых нет в ваших образцах данных. Но базовый синтаксис будет:
select fn.value as FirstName,
a.value as Amount,
pc.value as PostalCode,
ln.value as LastName,
an.value as AccountNumber
from yourtable fn
left join yourtable a
on fn.somecol = a.somecol
and a.columnname = 'Amount'
left join yourtable pc
on fn.somecol = pc.somecol
and pc.columnname = 'PostalCode'
left join yourtable ln
on fn.somecol = ln.somecol
and ln.columnname = 'LastName'
left join yourtable an
on fn.somecol = an.somecol
and an.columnname = 'AccountNumber'
where fn.columnname = 'Firstname'
Комментарии:
1. 1 . , , Но в последнем примере вы можете использовать
cross join
вместоleft join
, потому что каждый подзапрос возвращает одну строку.2. Мне нужно создать динамический запрос, потому что я не знаю количество строк. Давайте поговорим о преобразовании таблицы с миллионами записей 10.000.000
3. @tbag Если у вас неизвестное количество строк, вам придется использовать динамический sql, но имейте в виду, что преобразование миллионов строк будет неэффективным.
4. Я не могу использовать представление для этого преобразования, как насчет использования TFV? вот почему я ищу эффективное решение.
5. @tbag Динамический SQL нельзя использовать в представлении или функции с табличным значением, его необходимо использовать в хранимой процедуре
Ответ №2:
Это скорее метод, чем просто один скрипт, но он дает вам гораздо больше гибкости.
Прежде всего, есть 3 объекта:
- Определяемый пользователем тип ТАБЛИЦЫ [
ColumnActionList
] -> содержит данные в качестве параметра - SP [
proc_PivotPrepare
] -> подготавливает наши данные - SP [
proc_PivotExecute
] -> выполнить сценарий
СОЗДАЙТЕ ТИП [dbo].[ColumnActionList] В ВИДЕ ТАБЛИЦЫ ( [ID] [smallint] NOT NULL, [ColumnName] nvarchar NOT NULL, [Action] nchar NOT NULL ); ПЕРЕЙТИ
CREATE PROCEDURE [dbo].[proc_PivotPrepare]
(
@DB_Name nvarchar(128),
@TableName nvarchar(128)
)
AS
SELECT @DB_Name = ISNULL(@DB_Name,db_name())
DECLARE @SQL_Code nvarchar(max)
DECLARE @MyTab TABLE (ID smallint identity(1,1), [Column_Name] nvarchar(128), [Type] nchar(1), [Set Action SQL] nvarchar(max));
SELECT @SQL_Code = 'SELECT [<| SQL_Code |>] = '' '' '
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
'UNION ALL '
'SELECT ''-----| Declare user defined type [ID] / [ColumnName] / [PivotAction] '' '
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
'UNION ALL '
'SELECT ''DECLARE @ColumnListWithActions ColumnActionList;'''
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
'UNION ALL '
'SELECT ''-----| Set [PivotAction] (''''S'''' as default) to select dimentions and values '' '
'UNION ALL '
'SELECT ''-----|'''
'UNION ALL '
'SELECT ''-----| ''''S'''' = Stable column || ''''D'''' = Dimention column || ''''V'''' = Value column '' '
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
'UNION ALL '
'SELECT ''INSERT INTO @ColumnListWithActions VALUES ('' CAST( ROW_NUMBER() OVER (ORDER BY [NAME]) as nvarchar(10)) '', '' '''''''' [NAME] '''''''' '', ''''S'''');'''
'FROM [' @DB_Name '].sys.columns '
'WHERE object_id = object_id(''[' @DB_Name ']..[' @TableName ']'') '
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
'UNION ALL '
'SELECT ''-----| Execute sp_PivotExecute with parameters: columns and dimentions and main table name'' '
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
'UNION ALL '
'SELECT ''EXEC [dbo].[sp_PivotExecute] @ColumnListWithActions, ' '''''' @TableName '''''' ';'''
'UNION ALL '
'SELECT ''----------------------------------------------------------------------------------------------------'' '
EXECUTE SP_EXECUTESQL @SQL_Code;
GO
CREATE PROCEDURE [dbo].[sp_PivotExecute]
(
@ColumnListWithActions ColumnActionList ReadOnly
,@TableName nvarchar(128)
)
AS
--#######################################################################################################################
--###| Step 1 - Select our user-defined-table-variable into temp table
--#######################################################################################################################
IF OBJECT_ID('tempdb.dbo.#ColumnListWithActions', 'U') IS NOT NULL DROP TABLE #ColumnListWithActions;
SELECT * INTO #ColumnListWithActions FROM @ColumnListWithActions;
--#######################################################################################################################
--###| Step 2 - Preparing lists of column groups as strings:
--#######################################################################################################################
DECLARE @ColumnName nvarchar(128)
DECLARE @Destiny nchar(1)
DECLARE @ListOfColumns_Stable nvarchar(max)
DECLARE @ListOfColumns_Dimension nvarchar(max)
DECLARE @ListOfColumns_Variable nvarchar(max)
--############################
--###| Cursor for List of Stable Columns
--############################
DECLARE ColumnListStringCreator_S CURSOR FOR
SELECT [ColumnName]
FROM #ColumnListWithActions
WHERE [Action] = 'S'
OPEN ColumnListStringCreator_S;
FETCH NEXT FROM ColumnListStringCreator_S
INTO @ColumnName
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @ListOfColumns_Stable = ISNULL(@ListOfColumns_Stable, '') ' [' @ColumnName '] ,';
FETCH NEXT FROM ColumnListStringCreator_S INTO @ColumnName
END
CLOSE ColumnListStringCreator_S;
DEALLOCATE ColumnListStringCreator_S;
--############################
--###| Cursor for List of Dimension Columns
--############################
DECLARE ColumnListStringCreator_D CURSOR FOR
SELECT [ColumnName]
FROM #ColumnListWithActions
WHERE [Action] = 'D'
OPEN ColumnListStringCreator_D;
FETCH NEXT FROM ColumnListStringCreator_D
INTO @ColumnName
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @ListOfColumns_Dimension = ISNULL(@ListOfColumns_Dimension, '') ' [' @ColumnName '] ,';
FETCH NEXT FROM ColumnListStringCreator_D INTO @ColumnName
END
CLOSE ColumnListStringCreator_D;
DEALLOCATE ColumnListStringCreator_D;
--############################
--###| Cursor for List of Variable Columns
--############################
DECLARE ColumnListStringCreator_V CURSOR FOR
SELECT [ColumnName]
FROM #ColumnListWithActions
WHERE [Action] = 'V'
OPEN ColumnListStringCreator_V;
FETCH NEXT FROM ColumnListStringCreator_V
INTO @ColumnName
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @ListOfColumns_Variable = ISNULL(@ListOfColumns_Variable, '') ' [' @ColumnName '] ,';
FETCH NEXT FROM ColumnListStringCreator_V INTO @ColumnName
END
CLOSE ColumnListStringCreator_V;
DEALLOCATE ColumnListStringCreator_V;
SELECT @ListOfColumns_Variable = LEFT(@ListOfColumns_Variable, LEN(@ListOfColumns_Variable) - 1);
SELECT @ListOfColumns_Dimension = LEFT(@ListOfColumns_Dimension, LEN(@ListOfColumns_Dimension) - 1);
SELECT @ListOfColumns_Stable = LEFT(@ListOfColumns_Stable, LEN(@ListOfColumns_Stable) - 1);
--#######################################################################################################################
--###| Step 3 - Preparing table with all possible connections between Dimension columns excluding NULLs
--#######################################################################################################################
DECLARE @DIM_TAB TABLE ([DIM_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO @DIM_TAB
SELECT [DIM_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName] FROM #ColumnListWithActions WHERE [Action] = 'D';
DECLARE @DIM_ID smallint;
SELECT @DIM_ID = 1;
DECLARE @SQL_Dimentions nvarchar(max);
IF OBJECT_ID('tempdb.dbo.##ALL_Dimentions', 'U') IS NOT NULL DROP TABLE ##ALL_Dimentions;
SELECT @SQL_Dimentions = 'SELECT [xxx_ID_xxx] = ROW_NUMBER() OVER (ORDER BY ' @ListOfColumns_Dimension '), ' @ListOfColumns_Dimension
' INTO ##ALL_Dimentions '
' FROM (SELECT DISTINCT' @ListOfColumns_Dimension ' FROM ' @TableName
' WHERE ' (SELECT [ColumnName] FROM @DIM_TAB WHERE [DIM_ID] = @DIM_ID) ' IS NOT NULL ';
SELECT @DIM_ID = @DIM_ID 1;
WHILE @DIM_ID <= (SELECT MAX([DIM_ID]) FROM @DIM_TAB)
BEGIN
SELECT @SQL_Dimentions = @SQL_Dimentions 'AND ' (SELECT [ColumnName] FROM @DIM_TAB WHERE [DIM_ID] = @DIM_ID) ' IS NOT NULL ';
SELECT @DIM_ID = @DIM_ID 1;
END
SELECT @SQL_Dimentions = @SQL_Dimentions ' )x';
EXECUTE SP_EXECUTESQL @SQL_Dimentions;
--#######################################################################################################################
--###| Step 4 - Preparing table with all possible connections between Stable columns excluding NULLs
--#######################################################################################################################
DECLARE @StabPos_TAB TABLE ([StabPos_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO @StabPos_TAB
SELECT [StabPos_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName] FROM #ColumnListWithActions WHERE [Action] = 'S';
DECLARE @StabPos_ID smallint;
SELECT @StabPos_ID = 1;
DECLARE @SQL_MainStableColumnTable nvarchar(max);
IF OBJECT_ID('tempdb.dbo.##ALL_StableColumns', 'U') IS NOT NULL DROP TABLE ##ALL_StableColumns;
SELECT @SQL_MainStableColumnTable = 'SELECT xxx_ID_xxx = ROW_NUMBER() OVER (ORDER BY ' @ListOfColumns_Stable '), ' @ListOfColumns_Stable
' INTO ##ALL_StableColumns '
' FROM (SELECT DISTINCT' @ListOfColumns_Stable ' FROM ' @TableName
' WHERE ' (SELECT [ColumnName] FROM @StabPos_TAB WHERE [StabPos_ID] = @StabPos_ID) ' IS NOT NULL ';
SELECT @StabPos_ID = @StabPos_ID 1;
WHILE @StabPos_ID <= (SELECT MAX([StabPos_ID]) FROM @StabPos_TAB)
BEGIN
SELECT @SQL_MainStableColumnTable = @SQL_MainStableColumnTable 'AND ' (SELECT [ColumnName] FROM @StabPos_TAB WHERE [StabPos_ID] = @StabPos_ID) ' IS NOT NULL ';
SELECT @StabPos_ID = @StabPos_ID 1;
END
SELECT @SQL_MainStableColumnTable = @SQL_MainStableColumnTable ' )x';
EXECUTE SP_EXECUTESQL @SQL_MainStableColumnTable;
--#######################################################################################################################
--###| Step 5 - Preparing table with all options ID
--#######################################################################################################################
DECLARE @FULL_SQL_1 NVARCHAR(MAX)
SELECT @FULL_SQL_1 = ''
DECLARE @i smallint
IF OBJECT_ID('tempdb.dbo.##FinalTab', 'U') IS NOT NULL DROP TABLE ##FinalTab;
SELECT @FULL_SQL_1 = 'SELECT t.*, dim.[xxx_ID_xxx] '
' INTO ##FinalTab '
'FROM ' @TableName ' t '
'JOIN ##ALL_Dimentions dim '
'ON t.' (SELECT [ColumnName] FROM @DIM_TAB WHERE [DIM_ID] = 1) ' = dim.' (SELECT [ColumnName] FROM @DIM_TAB WHERE [DIM_ID] = 1);
SELECT @i = 2
WHILE @i <= (SELECT MAX([DIM_ID]) FROM @DIM_TAB)
BEGIN
SELECT @FULL_SQL_1 = @FULL_SQL_1 ' AND t.' (SELECT [ColumnName] FROM @DIM_TAB WHERE [DIM_ID] = @i) ' = dim.' (SELECT [ColumnName] FROM @DIM_TAB WHERE [DIM_ID] = @i)
SELECT @i = @i 1
END
EXECUTE SP_EXECUTESQL @FULL_SQL_1
--#######################################################################################################################
--###| Step 6 - Selecting final data
--#######################################################################################################################
DECLARE @STAB_TAB TABLE ([STAB_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO @STAB_TAB
SELECT [STAB_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName]
FROM #ColumnListWithActions WHERE [Action] = 'S';
DECLARE @VAR_TAB TABLE ([VAR_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO @VAR_TAB
SELECT [VAR_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName]
FROM #ColumnListWithActions WHERE [Action] = 'V';
DECLARE @y smallint;
DECLARE @x smallint;
DECLARE @z smallint;
DECLARE @FinalCode nvarchar(max)
SELECT @FinalCode = ' SELECT ID1.*'
SELECT @y = 1
WHILE @y <= (SELECT MAX([xxx_ID_xxx]) FROM ##FinalTab)
BEGIN
SELECT @z = 1
WHILE @z <= (SELECT MAX([VAR_ID]) FROM @VAR_TAB)
BEGIN
SELECT @FinalCode = @FinalCode ', [ID' CAST((@y) as varchar(10)) '.' (SELECT [ColumnName] FROM @VAR_TAB WHERE [VAR_ID] = @z) '] = ID' CAST((@y 1) as varchar(10)) '.' (SELECT [ColumnName] FROM @VAR_TAB WHERE [VAR_ID] = @z)
SELECT @z = @z 1
END
SELECT @y = @y 1
END
SELECT @FinalCode = @FinalCode
' FROM ( SELECT * FROM ##ALL_StableColumns)ID1';
SELECT @y = 1
WHILE @y <= (SELECT MAX([xxx_ID_xxx]) FROM ##FinalTab)
BEGIN
SELECT @x = 1
SELECT @FinalCode = @FinalCode
' LEFT JOIN (SELECT ' @ListOfColumns_Stable ' , ' @ListOfColumns_Variable
' FROM ##FinalTab WHERE [xxx_ID_xxx] = '
CAST(@y as varchar(10)) ' )ID' CAST((@y 1) as varchar(10))
' ON 1 = 1'
WHILE @x <= (SELECT MAX([STAB_ID]) FROM @STAB_TAB)
BEGIN
SELECT @FinalCode = @FinalCode ' AND ID1.' (SELECT [ColumnName] FROM @STAB_TAB WHERE [STAB_ID] = @x) ' = ID' CAST((@y 1) as varchar(10)) '.' (SELECT [ColumnName] FROM @STAB_TAB WHERE [STAB_ID] = @x)
SELECT @x = @x 1
END
SELECT @y = @y 1
END
SELECT * FROM ##ALL_Dimentions;
EXECUTE SP_EXECUTESQL @FinalCode;
После выполнения первого запроса (путем передачи исходной базы данных и имени таблицы) вы получите предварительно созданный запрос на выполнение для второго SP, все, что вам нужно сделать, это определить столбец из вашего источника:
Стабильный
Значение (будет использоваться для концентрации значений на основе этого)
Dim (столбец, который вы хотите использовать для поворота)
Имена и типы данных будут определены автоматически!
Я не могу рекомендовать его для любых производственных сред, но выполняет работу для запросов adhoc BI.
Комментарии:
1. Все хорошо до
Could not find stored procedure 'dbo.sp_PivotIt'.
каких-либо советов?2. @DarXyde жаль, что я должен смешать 2 версии, пожалуйста, перекомпилируйте и повторите попытку
3. Спасибо, Бартош, мне удалось использовать некоторые идеи из вашего скрипта и сделать то, что я уже имел в виду, но, тем не менее, спасибо за его обновление
. Я должен был подумать об изменении этой строки, но, честно говоря, подумал, что хранимая процедура, которую вы забыли, не используется по умолчанию в системе или что-то в этом родевот так. Я запущу его, когда снова приблизлюсь к этому проекту, и обновлю здесь!
4. Все еще неправильное имя SP, но теперь, когда я знаю, что не так, легко исправить:
sp_Pivot_Execute
измените наproc_PivotExecute
.5. @DarXyde — спасибо, что заметили это — теперь это исправлено
Ответ №3:
Я изменил ответ Тарин (версия «Свод с неизвестным количеством имен столбцов»), чтобы в результате отображалось более 1 строки. Для этого требуется дополнительный столбец «Group»
DROP TABLE #yourtable
CREATE table #yourtable
([Id] int,[Group] int, [Value] varchar(6), [ColumnName] varchar(13))
;
INSERT INTO #yourtable
([Id],[Group], [Value], [ColumnName])
VALUES
(1,1, 'John', 'FirstName'),
(2,1, '2.4', 'Amount'),
(3,1, 'ZH1E4A', 'PostalCode'),
(4,1, 'Fork', 'LastName'),
(5,1, '857685', 'AccountNumber'),
(6,2, 'Pedro', 'FirstName'),
(7,2, '5.1', 'Amount'),
(8,2, '123456', 'PostalCode'),
(9,2, 'Torres', 'LastName'),
(10,2, '857686', 'AccountNumber')
;
;
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' QUOTENAME(ColumnName)
from #yourtable
group by [Group], ColumnName, id
having [group] = (SELECT TOP 1 MIN([Group])FROM #yourtable)
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = N'SELECT ' @cols N' from
(
select value, ColumnName,[Group]
from #yourtable
GROUP BY [Group],ColumnName,Value
) x
pivot
(
max(value)
for ColumnName in (' @cols N')
) p '
exec sp_executesql @query;
Ответ №4:
Еще один вариант, который может быть очень полезен, — это ПЕРЕКРЕСТНОЕ ПРИМЕНЕНИЕ
-- Original data
SELECT * FROM (VALUES ('1', 1, 2, 3),('2', 11, 22, 33)) AS Stage(id,col1,col2,col3)
-- row to columns using CROSS APPLY
SELECT Stage.id,v.idd, v.colc
FROM (VALUES ('1', 1, 2, 3),('2', 11, 22, 33)) AS Stage(id,col1,col2,col3)
CROSS APPLY (VALUES ('col1', col1),('col2', col2),('col3', col3)) AS v(idd,colc)
GO
Ответ №5:
Пожалуйста, попробуйте
CREATE TABLE pvt (Present int, [Absent] int);
GO
INSERT INTO pvt VALUES (10,40);
GO
--Unpivot the table.
SELECT Code, Value
FROM
(SELECT Present, Absent
FROM pvt) p
UNPIVOT
(Value FOR Code IN
(Present, [Absent])
)AS unpvt;
GO
DROP TABLE pvt
Комментарии:
1. Как это улучшает уже принятый ответ?