Transposing a SQL query result - sql

I have a query that produces a table that looks like this:
+----------------+-----------+------------+----------+
| CustomerNumber | FirstName | MiddleName | LastName |
+----------------+-----------+------------+----------+
| 123456 | Test | Test1 | Test2 |
+----------------+-----------+------------+----------+
What I am needing is for the table to look like this:
+----------------+--------+
| CustomerNumber | 123456 |
+----------------+--------+
| FirstName | Test |
| MiddleName | Test1 |
| LastName | Test2 |
+----------------+--------+
This is my current sql query:
SELECT
CustomerNumber,
FirstName,
MiddleName,
LastName
FROM Customers
WHERE CustermerNumber = 123456
Is there a way to complete this? I have been looking at transposing it via unpivot but have not been able to understand how.

You can use apply :
select tt.*
from table t cross apply (
values ('CustomerNumber', CustomerNumber), ('FirstName', FirstName),
('MiddleName', MiddleName), ('LastName', LastName)
) tt (name, v);

With dynamic TSQL pivoting you can also manage multiple rows input:
if OBJECT_ID('Test') is not null
drop table [dbo].[Test]
CREATE TABLE [dbo].[Test](CustomerNumber varchar(10), FirstName varchar(10),
MiddleName varchar(10), LastName varchar(10))
--populate test table
insert into [dbo].[Test] values
(123456, 'Test','Test1','Test2')
, (234567, 'Test_2','Test_21','Test_22')
, (345678, 'Test_3','Test_31','Test_32')
--this variable holds all the customer numbers that will become column names
declare #columns nvarchar(max)=''
--this variable contains the dinamically generated TSQL code
declare #sql nvarchar(max)=''
select #columns = #columns + ', [' + [CustomerNumber] + ']' from [dbo].[Test]
set #columns = RIGHT(#columns, len(#columns)-2)
set #sql = #sql + 'select piv.COL as CustomerNumber, ' + #columns
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [CustomerNumber], col, val, ord '
set #sql = #sql + ' from [dbo].[Test] '
set #sql = #sql + ' CROSS APPLY ('
set #sql = #sql + ' VALUES (''FirstName'' ,FirstName , 1), '
set #sql = #sql + ' (''MiddleName'',MiddleName, 2), '
set #sql = #sql + ' (''LastName'' ,LastName, 3) '
set #sql = #sql + ' )CS (COL,VAL,ORD) '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot ( max(val) for [CustomerNumber] in ('+#columns+') ) piv'
set #sql = #sql + ' order by ord'
exec(#sql)
Sample input and output with one row:
Sample input and output with three rows:

Related

Replacing more than 1 period to 1 period in sql

I have the following code to convert more than one period to one period in a column of a table.
alter proc replace_characters_1
#COLUMN_NAME varchar(30),
#TABLE_NAME varchar(30)
as
declare #SQL varchar(MAX)
while #COLUMN_NAME like '%..%'
begin
set #SQL= 'update [' +#TABLE_NAME+ '] set [' +#COLUMN_NAME+ '] = replace([' +#COLUMN_NAME+ '],''..'',''.'')';
exec(#SQL)
end
I want to change the Anna...Amal to Anna.Amal with one go, but the loop is not working. What should I do?`
One possible approach is to use nested REPLACE()s:
SET ColumnName = REPLACE(REPLACE(REPLACE(ColumnName, '.', '<>'), '><', ''), '<>', '.')
After the first REPLACE() the part from the text that contains periods (.) looks like <><><>. After the second REPLACE() the result is only <> and the final REPLACE() returns single period (.). If the characters < and > exist in the input text, you can choose another pair of characters.
Table:
CREATE TABLE Data (Name varchar(100))
INSERT INTO Data (Name)
VALUES
('ANNA..Amal'),
('ANNA..Amal.'),
('ANNA.Amal.'),
('ANNA...........Amal.'),
('ANNA.....Amal')
Procedure:
CREATE PROC replace_characters_1
#COLUMN_NAME sysname,
#TABLE_NAME sysname
AS
BEGIN
DECLARE #SQL nvarchar(MAX)
DECLARE #RC int
SET #SQL =
N'UPDATE ' + QUOTENAME(#TABLE_NAME) + N' ' +
N'SET ' + QUOTENAME(#COLUMN_NAME) + N' = ' +
N'REPLACE(REPLACE(REPLACE(' + QUOTENAME(#COLUMN_NAME) + ', ''.'', ''<>''), ''><'', ''''), ''<>'', ''.'') ' +
N'WHERE ' + QUOTENAME(#COLUMN_NAME) + N' LIKE ''%..%'''
EXEC #RC = sp_executesql #SQL
RETURN #RC
END
Result:
EXEC replace_characters_1 N'Name', N'Data'
SELECT * FROM Data
Name
ANNA.Amal
ANNA.Amal.
ANNA.Amal.
ANNA.Amal.
ANNA.Amal
Here is an approach that will reduce repeating characters.
Example
Declare #YourTable Table ([SomeCol] varchar(50))
Insert Into #YourTable Values
('Anna...Amal')
,('Anna........Amal')
,('Anna.Amal')
,('Anna Amal')
Select *
,NewVal = replace(replace(replace(SomeCol,'.','†‡'),'‡†',''),'†‡','.')
from #YourTable
Returns
SomeCol NewVal
Anna...Amal Anna.Amal
Anna........Amal Anna.Amal
Anna.Amal Anna.Amal
Anna Amal Anna Amal
Please check Zhorov's answer as it avoids multiple operations like this one.
CREATE PROCEDURE replace_characters_1
#COLUMN_NAME varchar(30),
#TABLE_NAME varchar(30)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX) = N'
UPDATE T SET
' + QUOTENAME(#COLUMN_NAME) + N' = REPLACE(' + QUOTENAME(#COLUMN_NAME) + N',''..'',''.'')
FROM
' + QUOTENAME(#TABLE_NAME) + N' AS T
WHERE
T.' + QUOTENAME(#COLUMN_NAME) + N' LIKE ''%..%'';
SET #UpdatedRowsOut = ##ROWCOUNT;';
DECLARE #UpdatedRows INT = 1;
WHILE #UpdatedRows > 0
BEGIN
EXECUTE sp_executesql
#SQL,
N'#UpdatedRowsOut INT OUTPUT',
#UpdatedRowsOut = #UpdatedRows OUTPUT;
END
END
Dynamic SQL is now returning the amount of rows that were updated, so it keeps going as long as there are values with .. for that column (note that there is a WHERE filter, you don't want to update all rows every time!).
SQL Server isn't the best for regex expressions, maybe you want to consider using a CLR function if you need to do different kind of stuff with regex.
I am using CHARINDEX and STUFF to derive the resultset
DECLARE #sqlstring Varchar(max) = 'Anna...Amal'
-- Getting first index of .
DECLARE #firstidx INT = CHARINDEX('.',#sqlstring)
-- Getting last index of .
Declare #lastidx int = (LEN(#sqlstring) - CHARINDEX('.',REVERSE(#sqlstring))) + 1
-- Stuffing the gap with emptystring
SELECT STUFF(#sqlstring,#firstidx+1,(#lastidx-#firstidx),'') as result
Result
+-----------+
| result |
+-----------+
| Anna.Amal |
+-----------+
UPDATE: If there are multiple comma separated values
DECLARE #sqlstring Varchar(max) = 'Anna...Amal,Vimal...Mathew'
SELECT STRING_agg(removedvalues,',') as values
FROM
(SELECT STUFF(value, CHARINDEX('.',value)+1
,(LEN(value) - CHARINDEX('.',REVERSE(value)) + 1) - CHARINDEX('.',value),'') AS removedvalues
FROM string_split(#sqlstring,',') ) AS t
+------------------------+
| Values |
+------------------------+
| Anna.Amal,Vimal.Mathew |
+------------------------+

Using a distinct value from table as an Alias

I've got a view that is a bunch of survey responses. The structure of which is something like the below (excluding the Q1Text and Q2Text).
Q1 | Q1Text | Q2 | Q2Text
Value1 | Q1Header | Value1 | Q2Header
Value1 | Q1Header | Value2 | Q2Header
Value3 | Q1Header | Value3 | Q2Header
What I would like is for the Q1Text, which is a grabbing it's values from a seperate field, to be the alias for Q1.
The values for Q1Text is the same value regardless of how many rows there are, and the same for Q2Text
I was told by a colleague to try dynamic SQL, but not sure if my lack of understanding wasn't allowing me to do it or what, but one of my many attempts of this is below. Might give you a better idea if what i'm after.
DECLARE #table NVARCHAR(128)
DECLARE #SQL NVARCHAR(MAX)
DECLARE #Q1 NVARCHAR(128)
SET #table = '[MyTable]'
SET #Q1 = 'Select distinct P1Q1Qtext from' + #table
SET #sql = 'Select P1Q1 as ' + #Q1 + ' FROM ' + #table
EXEC sp_executesql #sql
Thanks in advance.
You were storing query in variable #Q1 but not executing it.
DECLARE #table NVARCHAR(128)
DECLARE #SQL NVARCHAR(MAX)
DECLARE #Q1 NVARCHAR(128)
SET #table = '[MyTable]'
SET #Q1 = (Select distinct top 1 P1Q1Qtext from MyTable)
SET #sql = 'Select P1Q1 as [' + #Q1 + '] FROM ' + #table
EXEC sp_executesql #sql

Turn Columns into Rows partitioned

I have a table like this:
CFL52_ID CFL52_PRICE CFL51_MILEAGE
------------ --------------- -----------------
1 100000.00 10000
1 200000.00 20000
2 800000.00 10000
2 900000.00 20000
I want to pivot the columns into rows. Mileage was the column title and Price the value. Turning into something like this:
CFL52_ID [10000] [20000]
------------ --------------- -----------------
1 100000.00 200000.00
2 800000.00 900000.00
Notice that id was grouping, prices are pivoted and mileage turning into columns title, Mileage are dynamic -could increment- .
I've tried with no success with this:
SELECT [10000],[20000]
FROM ( SELECT
CFL52_PRICE as indicatorvalue,
CFL51_MILEAGE as indicatorname
FROM [TFL52_PRICES_M] p
INNER JOIN [TFL51_PARAM_MILEAGE] k ON CFL52_CFL51_CODIGO = CFL51_CODIGO
WHERE CFL52_DATES = '2018-07-01 00:00:00.000' AND CFL52_DATEEN= '2018-07-02 00:00:00.000') as a
pivot
(
max(indicatorvalue) for indicatorname in ([10000],[20000])
) p
just use conditional aggregation instead. You also should use aliases to indicate which table a column belongs to.
select CFL52_ID
, [10000] = MAX(case when CFL51_MILEAGE = 10000 then CFL52_PRICE end)
, [20000] = MAX(case when CFL51_MILEAGE = 20000 then CFL52_PRICE end)
FROM [TFL52_PRICES_M] p
INNER JOIN [TFL51_PARAM_MILEAGE] k ON CFL52_CFL51_CODIGO = CFL51_CODIGO
WHERE CFL52_DATES = '2018-07-01 00:00:00.000'
AND CFL52_DATEEN= '2018-07-02 00:00:00.000'
group by CFL52_ID
To manage dynamic titles you need dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(CFL52_ID varchar(50), CFL52_PRICE decimal(18,2), CFL51_MILEAGE int)
--populate test table
insert into dbo.test values
(1, 100000.00, 10000),
(1, 200000.00, 20000),
(2, 800000.00, 10000),
(2, 900000.00, 20000)
declare #columns nvarchar(max)='' --holds all the numbers that will become column names
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
--generate dynamic column names
select #columns = #columns + ', [' + cast(CFL51_MILEAGE as varchar(max))+ ']'
from dbo.test
group by CFL51_MILEAGE
--remove first (unnecessary) comma
set #columns = RIGHT(#columns, len(#columns)-2)
--build dynamic TSQL query
set #sql = #sql + ' select piv.[CFL52_ID], ' + #columns
set #sql = #sql + ' from ( '
set #sql = #sql + ' select [CFL52_ID], [CFL52_PRICE], [CFL51_MILEAGE] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot ( '
set #sql = #sql + ' max([CFL52_PRICE]) '
set #sql = #sql + ' for [CFL51_MILEAGE] in ('+#columns+') '
set #sql = #sql + ' ) piv '
--execute dynamic TSQL query
exec(#sql)
output:
If you add more rows to your input table with CFL51_MILEAGE values of 10000 and 20000, then input and output tables will be:
If you add more rows to your input table introducing new CFL51_MILEAGE values (other than 10000 or 20000), then input and output tables will be:

SQL Columns to Rows

From my database table(Customer) I need to select one record and display the result by interchanging columns to rows.
EG:
actual result
| ID | Name | Age |
| 1 | Tom | 25 |
expected output
| Name | Value|
| ID | 1 |
| Name | Tom |
| Age | 25 |
Other details:
Customer table has different number of colums in different databases
I need to do this inside a function (So I cannot use dynamic queries, UNPIVOT)
Please advice me.
This uses CROSS APPLY with VALUES to perform unpivot
--Set up test data
CREATE TABLE dbo.TEST(ID INT IDENTITY (1,1),Name VARCHAR(20),Age TINYINT)
INSERT INTO dbo.TEST VALUES
('Shaggy',32)
,('Fred',28)
,('Velma',26)
,('Scooby',7)
DECLARE #table VARCHAR(255) = 'Test'
DECLARE #schema VARCHAR(255) = 'dbo'
DECLARE #ID INT = 2
--Create a VALUES script for the desired table
DECLARE #col VARCHAR(1000)
SELECT
#col = COALESCE(#col,'') + '(''' + c.name + ''' ,CAST(A.[' + c.name + '] AS VARCHAR(20))),'
FROM
sys.objects o
INNER JOIN sys.columns c
ON
o.object_id = c.object_id
WHERE
o.name = #table
AND
SCHEMA_NAME(o.schema_id) = #schema
ORDER BY
c.column_id
--Remove trailing ,
SET #col = LEFT(#col,LEN(#col)-1)
--Build Script for unpivoting data.
DECLARE #str VARCHAR(2000) = '
SELECT
CAST(C.Col AS VARCHAR(20)) AS [Name]
,CAST(C.Val AS VARCHAR(20)) AS [Value]
FROM
[' + #schema + '].[' + #table + '] A
CROSS APPLY (VALUES ' + #col + ') C(Col,Val)
WHERE
A.ID = ''' + CAST(#ID AS VARCHAR(8)) + ''''
--Run Script
EXEC (#str)

Selecting columns from a query

I'm using a request to get a collection of columns name:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE [...]
From this collection, I'd like to count every not null, not empty value from the original table group by column name.
Let's say I have a table containing
COL1 | COL2 | COL3
------------------
VAL1 | VAL2 | NULL
VAL3 | | VAL4
VAL5 | |
I'm looking for a request to get:
COL1 | 3
COL2 | 1
COL2 | 1
It's for analytics purpose.
Thanks for your help!
Here is a simple process. Run the following query:
SELECT 'SELECT ''' + COLUMN_NAME + ''', COUNT(['+COLUMN_NAME']) as NotNull FROM [' +SCHEMA_NAME+ '].['+TABLE_NAME+ '] union all '
FROM INFORMATION_SCHEMA.COLUMNS
WHERE [...]
Copy the results into a query window, remove the final union all, and run the query.
The below code seems to work for your issue
create table sample
(
col1 varchar(10),
col2 varchar(10),
col3 varchar(10)
)
INSERT INTO sample (COL1,COL2,COL3) VALUES ('VAL1 ',' VAL2 ',NULL);
INSERT INTO sample (COL1,COL2,COL3) VALUES ('VAL3 ',' ',' VAL4');
INSERT INTO sample (COL1,COL2,COL3) VALUES ('VAL5 ',' ',' ');
DECLARE #cols1 NVARCHAR(MAX);
DECLARE #sql NVARCHAR(MAX);
SELECT #cols1 = STUFF((
SELECT ', COUNT(CASE WHEN len(['+ t1.NAME + '])!=0 THEN 1 END) AS ' + t1.name
FROM sys.columns AS t1
WHERE t1.object_id = OBJECT_ID('sample')
--ORDER BY ', COUNT([' + t1.name + ']) AS ' + t1.name
FOR XML PATH('')
), 1, 2, '');
SET #sql = '
SELECT ' + #cols1 + '
FROM sample
'
EXEC(#sql)
Hereis my little longer take on this:
declare #cols table (colID integer, colName varchar(50))
declare #results table (colName nvarchar(50), valueCount bigint)
-- table name
declare #tableName nvarchar(50) = 'INSERT TABLE NAME HERE'
-- select column names from table
insert into #cols
select column_id, name from sys.columns where object_id = object_id(#tableName) order by column_id
declare #currentColID int = 0
declare #currentName nvarchar(50) = ''
declare #currentCount bigint = 0
declare #sql nvarchar(max) -- where the dynamic sql will be stored
-- go through all columns
while (1 = 1)
begin
-- step by id
select top 1 #currentColID = c.colID, #currentName = c.colName from #cols c
where c.colid > #currentColID order by c.colID
if ##ROWCOUNT = 0 break;
-- dynamic query to get non-empty, not-null counts
select #sql = 'select #p1=COUNT(' + #currentName + ') from ' + #tableName +
' where ' + #currentName + ' is not null or LEN(' + #currentName + ') > 0'
exec sp_executesql #sql, N'#p1 bigint output', #p1 = #currentCount output
-- insert result to buffer
insert into #results values (#currentName, #currentCount)
end
-- print the buffer
select * from #results
Have fun :)