I've been trying for a while to use SQL Server pivot but I just don't seem to be getting it right. I've read a bunch of SO answers, but don't understand how pivot works.
I'm writing a stored procedure. I have Table 1 (received as a TVP), and need to make it look like Table 2 (see this image for tables).
Important: the values in Table1.valueTypeID cannot be hard coded into the logic because they can always change. Therefore, the logic must be super dynamic.
Please see the code below. The pivot is at the end of the stored procedure.
-- Create date: 12/10/2013
-- Description: select all the contacts associated with received accountPassport
-- =============================================
ALTER PROCEDURE [dbo].[selectContactsPropsByAccountPassport]
-- Add the parameters for the stored procedure here
#accountPassport int,
#valueTypeFiltersTVP valueTypeFiltersTVP READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #accountID int;
DECLARE #contactsAppAccountPassport int;
DECLARE #searchResults TABLE
(
resultContactID int
);
DECLARE #resultContactID int;
DECLARE #contactsPropsForReturn TABLE
(
contactID int,
valueTypeID int,
value varchar(max)
);
create table #contactsPropsForReturnFiltered(contactID int,valueTypeID int, value varchar(max))
/*
DECLARE #contactsPropsForReturnFiltered TABLE
(
contactID int,
valueTypeID int,
value varchar(max)
);
*/
--2. get #contactsAppAccountPassport associated with recieved #accountPassport
-- go into dbo.accounts and get the #accountID associated with this #accountPassport
SELECT
#accountID = ID
FROM
dbo.accounts
WHERE
passport = #accountPassport
-- go into dbo.accountsProps and get the value (#contactsAppAccountPassport) where valueType=42 and accountID = #accountID
SELECT
#contactsAppAccountPassport = value
FROM
dbo.accountsProps
WHERE
(valueTypeID=42) AND (accountID = #accountID)
--3. get all the contact ID's from dbo.contacts associated with #contactsAppAccountPassport
INSERT INTO
#searchResults
SELECT
ID
FROM
dbo.contacts
WHERE
contactsAppAccountPassport = #contactsAppAccountPassport
--4. Get the props of all contact ID's from 3.
--start for each loop....our looping object is #resultContactID row. if there are more rows, we keep looping.
DECLARE searchCursor CURSOR FOR
SELECT
resultContactID
FROM
#searchResults
OPEN searchCursor
FETCH NEXT FROM searchCursor INTO #resultContactID
WHILE (##FETCH_STATUS=0)
BEGIN
INSERT INTO
#contactsPropsForReturn
SELECT
contactID,
valueTypeID,
value
FROM
dbo.contactsProps
WHERE
contactID = #resultContactID
FETCH NEXT FROM searchCursor INTO #resultContactID
END --end of WHILE loop
--end of cursor (both CLOSE and DEALLOCATE necessary)
CLOSE searchCursor
DEALLOCATE searchCursor
-- select and return only the props that match with the requested props
-- (we don't want to return all the props, only the ones requested)
INSERT INTO
#contactsPropsForReturnFiltered
SELECT
p.contactID,
p.valueTypeID,
p.value
FROM
#contactsPropsForReturn as p
INNER JOIN
#valueTypeFiltersTVP as f
ON
p.valueTypeID = f.valueTypeID
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(ValueTypeId)
FROM #contactsPropsForReturnFiltered
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = 'SELECT contactid, ' + #cols + ' from
(
select contactid
, Value
,ValueTypeId
from #contactsPropsForReturnFiltered
) x
pivot
(
min(Value)
for ValueTypeId in (' + #cols + ')
) p ';
execute(#query);
END
You need to use dynamic pivot in your case. Try the following
create table table1
(
contactid int,
ValueTypeId int,
Value varchar(100)
);
insert into table1 values (56064, 40, 'Issac');
insert into table1 values (56064, 34, '(123)456-7890');
insert into table1 values (56065, 40, 'Lola');
insert into table1 values (56065, 34, '(123)456-7832');
insert into table1 values (56068, 40, 'Mike');
insert into table1 values (56068, 41, 'Gonzalez');
insert into table1 values (56068, 34, '(123)456-7891');
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(ValueTypeId)
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = 'SELECT contactid, ' + #cols + ' from
(
select contactid
, Value
,ValueTypeId
from table1
) x
pivot
(
min(Value)
for ValueTypeId in (' + #cols + ')
) p ';
execute(#query);
drop table table1
Why do you need to present the data in this way?
In many cases, clients are better at pivoting than the database engine. For example, SQL Server Reporting Services easily does this with the matrix control. Similarly, if you are coding a web page in, say, Asp.Net, you can run through the recordset quickly to pass your data into a new data representation (meanwhile collecting unique values) and then in a single pass through the new data object spit out the HTML to render the result.
If at all possible, have your client do the pivoting instead of the server.
UPDATE:
If you really want to use table variables in dynamic SQL, you can just fine in SQL Server 2008 and up. Here's an example script:
USE tempdb
GO
CREATE TYPE IDList AS TABLE (
ID int
);
GO
DECLARE #SQL nvarchar(max);
SET #SQL = 'SELECT * FROM #TransactionIDs WHERE ID >= 4;'
DECLARE #TransactionIDs IDLIst;
INSERT #TransactionIDs VALUES (1), (2), (4), (8), (16);
EXEC sp_executesql #SQL, N'#TransactionIDs IDList READONLY', #TransactionIDs;
GO
DROP TYPE IDList;
Related
I'm trying to build a stored procedure that will query multiple database depending on the databases required.
For example:
SP_Users takes a list of #DATABASES as parameters.
For each database it needs to run the same query and union the results together.
I believe a CTE could be my best bet so I have something like this at the moment.
SET #DATABASES = 'DB_1, DB_2' -- Two databases in a string listed
-- I have a split string function that will extract each database
SET #CURRENT_DB = 'DB_1'
WITH UsersCTE (Name, Email)
AS (SELECT Name, Email
FROM [#CURRENT_DB].[dbo].Users),
SELECT #DATABASE as DB, Name, Email
FROM UsersCTE
What I don't want to do is hard code the databases in the query. The steps I image are:
Split the parameter #DATABASES to extract and set the #CURRENT_DB Variable
Iterate through the query with a Recursive CTE until all the #DATABASES have been processed
Union all results together and return the data.
Not sure if this is the right approach to tackling this problem.
Using #databases:
As mentioned in the comments to your question, variables cant be used to dynamically select a database. Dynamic sql is indicated. You can start by building your template sql statement:
declare #sql nvarchar(max) =
'union all ' +
'select ''#db'' as db, name, email ' +
'from [#db].dbo.users ';
Since you have sql server 2016, you can split using the string_split function, with your #databases variable as input. This will result in a table with 'value' as the column name, which holds the database names.
Use the replace function to replace #db in the template with value. This will result in one sql statement for each database you passed into #databases. Then, concatenate the statements back together. Unfortunately, in version 2016, there's no built in function to do that. So we have to use the famous for xml trick to join the statements, then we use .value to convert it to a string, and finally we use stuff to get rid of the leading union all statement.
Take the results of the concatenated output, and overwrite the #sql variable. It is ready to go at this point, so execute it.
I do all that is described in this code:
declare #databases nvarchar(max) = 'db_1,db_2';
set #sql = stuff(
(
select replace(#sql, '#db', value)
from string_split(#databases, ',')
for xml path(''), type
).value('.[1]', 'nvarchar(max)')
, 1, 9, '');
exec(#sql);
Untested, of course, but if you print instead of execute, it seems to give the proper sql statement for your needs.
Using msForEachDB:
Now, if you didn't want to have to know which databases had 'users', such as if you're in an environment where you have a different database for every client, you can use sp_msForEachDb and check the structure first to make sure it has a 'users' table with 'name' and 'email' columns. If so, execute the appropriate statement. If not, execute a dummy statement. I won't describe this one, I'll just give the code:
declare #aggregator table (
db sysname,
name int,
email nvarchar(255)
);
insert #aggregator
exec sp_msforeachdb '
declare #sql nvarchar(max) = ''select db = '''''''', name = '''''''', email = '''''''' where 1 = 2'';
select #sql = ''select db = ''''?'''', name, email from ['' + table_catalog + ''].dbo.users''
from [?].information_schema.columns
where table_schema = ''dbo''
and table_name = ''users''
and column_name in (''name'', ''email'')
group by table_catalog
having count(*) = 2
exec (#sql);
';
select *
from #aggregator
I took the valid advice from others here and went with this which works great for what I need:
I decided to use a loop to build the query up. Hope this helps someone else looking to do something similar.
CREATE PROCEDURE [dbo].[SP_Users](
#DATABASES VARCHAR(MAX) = NULL,
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)
)
BEGIN
SET NOCOUNT ON;
--Local variables
DECLARE
#COUNTER INT = 0,
#SQL NVARCHAR(MAX) = '',
#CURRENTDB VARCHAR(50) = NULL,
#MAX INT = 0,
#ERRORMSG VARCHAR(MAX)
--Check we have databases entered
IF #DATABASES IS NULL
BEGIN
RAISERROR('ERROR: No Databases Provided,
Please Provide a list of databases to execute procedure. See stored procedure:
[SP_Users]', 16, 1)
RETURN
END
-- SET Number of iterations based on number of returned databases
SET #MAX = (SELECT COUNT(*) FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i)X)
-- Build SQL Statement
WHILE #COUNTER < #MAX
BEGIN
--Set the current database
SET #CURRENTDB = (SELECT X.Value FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i
ORDER BY RowNumber OFFSET #COUNTER
ROWS FETCH NEXT 1 ROWS ONLY) X);
SET #SQL = #SQL + N'
(
SELECT Name, Email
FROM [' + #CURRENTDB + '].[dbo].Users
WHERE
(Name = #PARAM1 OR #PARAM1 IS NULL)
(Email = #PARAM2 OR #PARAM2 IS NULL)
) '
+ N' UNION ALL '
END
PRINT #CURRENTDB
PRINT #SQL
SET #COUNTER = #COUNTER + 1
END
-- remove last N' UNION ALL '
IF LEN(#SQL) > 11
SET #SQL = LEFT(#SQL, LEN(#SQL) - 11)
EXEC sp_executesql #SQL, N'#CURRENTDB VARCHAR(50),
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)',
#CURRENTDB,
#PARAM1 ,
#PARAM2
END
Split Variable Function
CREATE FUNCTION [dbo].[udf_SplitVariable]
(
#List varchar(8000),
#SplitOn varchar(5) = ','
)
RETURNS #RtnValue TABLE
(
Id INT IDENTITY(1,1),
Value VARCHAR(8000)
)
AS
BEGIN
--Account for ticks
SET #List = (REPLACE(#List, '''', ''))
--Account for 'emptynull'
IF LTRIM(RTRIM(#List)) = 'emptynull'
BEGIN
SET #List = ''
END
--Loop through all of the items in the string and add records for each item
WHILE (CHARINDEX(#SplitOn,#List)>0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT Value = LTRIM(RTRIM(SUBSTRING(#List, 1, CHARINDEX(#SplitOn, #List)-1)))
SET #List = SUBSTRING(#List, CHARINDEX(#SplitOn,#List) + LEN(#SplitOn), LEN(#List))
END
INSERT INTO #RtnValue (Value)
SELECT Value = LTRIM(RTRIM(#List))
RETURN
END
I have a scenario that #PRIVILEGEID will have multiple value and #ROLEID will be same every time for those privilege Id.
So I have to insert the data into table.
Below is the Code For Procedure:-
ALTER PROCEDURE [dbo].[ADD_ROLE] (
#ROLENAME varchar(50),
#PRIVILEGEID int )
AS
BEGIN
DECLARE #QUERY varchar(1000);
DECLARE #ROLEID int;
SET #ROLEID = ( SELECT Max(ROLEID)
FROM ( SELECT
Max(CREATED_DATE) AS MAX_DATE,
ROLEID
FROM ROLE
WHERE ROLENAME = #ROLENAME
--(ROlename can be changed dynamically, take 'Manager' as example as of now.)
AND CREATED_DATE IS NOT NULL
GROUP BY ROLEID ) X );
--PRINT #ROLEID
SET #QUERY = 'INSERT INTO [ROLES_PRIVILEGES] (ROLEID,PRIVILEGEID) VALUES (''' + Cast(#ROLEID AS varchar) + ''',''' + Cast(#PRIVILEGEID AS varchar) + ''')';
EXECUTE ( #QUERY );
END;
Now #PRIVILEGEID will have a dynamic list of multiple values for the fixed #ROLEID and My issue is I can pass only one #PRIVILEGEID at one time.
Exec ADD_ROLE 'BACKOFFICE',#PRIVILEGEID #PRIVILEGEID=[2, 3, 4, 5, 6]
-- (How to pass multiple value for PRIVILEGEID )
I have tried While loop also but not sure how to implement it.
Please help.
Here is an example where I take a comma delimited string and dump into a table. This process works by converting it into xml and using xmlqry to parse. I like it because I feel its easier to read and looks less convoluted.
Let me know if it works for you.
USE yourDatabase
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[_parsedelimited]
(
#str varchar(max)
)
AS
BEGIN
if object_id('tempdb..#Emails') is not null drop table #Emails;
CREATE TABLE #Emails (Email varchar(2500));
Declare #x XML;
select #x = cast('<A>'+ replace(#str,',','</A><A>')+ '</A>' as xml);
INSERT INTO #Emails (Email)
select
t.value('.', 'varchar(50)') as inVal
from #x.nodes('/A') as x(t);
SELECT * FROM #Emails;
drop table #Emails;
END
GO
Consider the following:
declare #role varchar(20) = 'BACKOFFICE',
#PRIVILEGEID varchar(100) = '2,3,4,5,6';
select #role, *
from string_split(#PRIVILEGEID, ',');
Using that select, you should be able to insert it into whatever table you'd like.
The question is quite extensive, please bear with me. I have a single mapping table with the following structure:
This particular table is used in the process of generating a hierarchy. The order and position of the columns in the table indicate the order of hierarchy (Organization, Category, Continent, Country.. etc.) Each entity in this hierarchy has a related table with associated Id and Name. For example, there is a Country table with CountryId and CountryName. Note that since the MappingTable's values are all nullable there are no foreign key constraints.
I want to generate a procedure that will do the following:
Based on conditions provided, retrieve values of the next entity in the hierarchy. For example, if the OrganizationId and CategoryId are given, the values of ContinentId that satisfy said condition need to be retrieved.
Also, if the value of ContinentId is NULL, then the values of CountryId need to be retrieved. Here, given the condition OrganizationId = 1 and CategoryId = 1 the procedure should return the list of RegionId.
In addition to retrieving the RegionId, the corresponding RegionName should be retrieved from the Region Table.
So far, the procedure looks something like this - just a few things to explain here.
ALTER PROCEDURE [dbo].[GetHierarchy]
(
#MappingTableName VARCHAR(30),
#Position VARCHAR(5),
-- Given in the form of Key-value pairs 'OrganizationId:1,CategoryId:1'
#InputData VARCHAR(MAX),
#Separator CHAR(1),
#KeyValueSeperator CHAR(1)
)
AS
BEGIN
DECLARE #Sql NVARCHAR(MAX)
DECLARE #Result NVARCHAR(MAX)
DECLARE #Sql1 NVARCHAR(MAX);
DECLARE #TableName NVARCHAR(30)
DECLARE #Exists bit
SELECT #TableName = COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #MappingTableName AND ORDINAL_POSITION = #position
SET #TableName = SUBSTRING(#TableName,0,LEN(#TableName) - 1)
-- Returns a dynamic query like "SELECT ContinentId from Continent WHERE OrganizationId = 1 and CategoryId = 1".
SELECT #Sql = [dbo].[KeyValuePairs](#TableName, #InputData, #Separator, #KeyValueSeperator)
SET #Sql1 = N'SET #Exists = CASE WHEN EXISTS(' + #Sql + N' AND ' + #TableName + N'Id IS NOT NULL) THEN 1 ELSE 0 END'
PRINT #Sql
EXEC sp_executesql #Sql1,
N'#Exists bit OUTPUT',
#Exists = #Exists OUTPUT
IF(#Exists = 1)
BEGIN
SET #Sql1 = 'SELECT ' + #TableName + 'Id, ' + #TableName + 'Name FROM '+ #TableName+' WHERE ' + #TableName +'Id IN (' + #Sql + ')';
PRINT #Sql1
EXECUTE sp_executesql #SQL1
END
ELSE
BEGIN
--PRINT 'NOT EXISTS'
DECLARE #nextPosition INT
SELECT #nextPosition = CAST(#position AS INT)
SET #nextPosition = #nextPosition + 1
SET #Position = CONVERT(VARCHAR(5), CAST(#position AS INT))
EXEC [dbo].[GetHierarchy] #MappingTableName, #Position, #InputData, #Separator, #KeyValueSeperator
END
END
The logic of this procedure is such that, I get the name of the column at a particular position (based on the conditions here, it is Continent) and generate the dynamic query to retrieve the next column's values based on the condition of the input condition (I am using a separate function to do this for me).
Once retrieved, I run the query to check if it returns any rows. If the query returns rows, then I retrieve the corresponding ContinentName from the Continent table. If no rows are returns, I recursively call the procedure again with the next position as the input.
On the business side of things, it seems like a two step process. But, as a procedure it is quite complex, extensive and - not to mention, recursive. Is there an easier way to do this? I am not familiar with CTEs - can the same logic be implemented using CTEs?
This is quite similar to what is asked here: Working with a dynamic hierarchy SQL Server
Might be the little lengthy approach. Try this
DECLARE #T TABLE
(
SeqNo INT IDENTITY(1,1),
CatId INT,
Country INT,
StateId INT,
DistId INT
)
DECLARE #State TABLE
(
StateId INT,
StateNm VARCHAR(20)
)
DECLARE #Country TABLE
(
CountryId INT,
CountryNm VARCHAR(20)
)
INSERT INTO #State
VALUES(3,'FL')
INSERT INTO #Country
VALUES(2,'USA')
INSERT INTO #T(CatId)
VALUES(1)
INSERT INTO #T(CatId,Country)
VALUES(1,2)
INSERT INTO #T(CatId,StateId)
VALUES(1,3)
;WITH CTE
AS
(
SELECT
*,
IdVal = COALESCE(Country,StateId,DistId),
IdCol = COALESCE('Country '+CAST(Country AS VARCHAR(50)),'StateId '+CAST(StateId AS VARCHAR(50)),'DistId '+CAST(DistId AS VARCHAR(50)))
FROM #T
WHERE CatId = 1
),C2
AS
(
SELECT
SeqNo,
CatId,
Country,
StateId,
DistId,
IdVal,
IdCol = LTRIM(RTRIM(SUBSTRING(IdCol,1,CHARINDEX(' ',IdCol))))
FROM CTE
)
SELECT
C2.SeqNo,
C2.CatId,
S.StateNm,
C.CountryNm
FROM C2
LEFT JOIN #State S
ON C2.IdCol ='StateId'
AND C2.IdVal = S.StateId
LEFT JOIN #Country C
ON C2.IdCol ='Country '
AND C2.IdVal = c.CountryId
Is there a way to directly store the results of a dynamic pivot query into a fixed table? As the result is dynamic I can't create the table by specifying the columnnames and methods like "create table MyTable as (pivot select statement)" seem to fail on SQL server ("Incorrect syntax near the keyword 'AS'").
I have tried to format the SQL below to get a SELECT - INTO - FROM structure but failed to do so. Any help is obviously greatly appreciated!
The SQL used for the pivot is (build thanks to this great website!):
declare #pivot varchar(max), #sql varchar(max)
create table pivot_columns (pivot_column varchar(100))
insert into pivot_columns
select distinct DateField from MyTable order by 1
select #pivot=coalesce(#pivot+',','')+'['+pivot_column+']'from pivot_columns
set #sql = 'SELECT * FROM (select DateField, RefCode, SumField from MyTable) p
PIVOT
(sum(SumField) FOR DateField IN ( ' + #pivot + ') )
AS pvl'
drop table pivot_columns
exec (#sql)
Unless I am not following what you are trying to do you should be able to add the INTO mynewTable to your sql that you are going to execute and you should get the new table.
declare #pivot varchar(max), #sql varchar(max)
create table pivot_columns (pivot_column varchar(100))
insert into pivot_columns
select distinct DateField from MyTable order by 1
select #pivot=coalesce(#pivot+',','')+'['+pivot_column+']'from pivot_columns
set #sql = 'SELECT * INTO mynewTable FROM (select DateField, RefCode, SumField from MyTable) p
PIVOT
(sum(SumField) FOR DateField IN ( ' + #pivot + ') )
AS pvl'
drop table pivot_columns
exec (#sql)
I just test creating a new table in the following script and it gives me a new table that is in the DB for use:
create table t
(
[month] int,
[id] nvarchar(20),
[cnt] int
)
insert t values (4,'TOTAL',214)
insert t values (5,'TOTAL',23)
insert t values (6,'TOTAL',23)
insert t values (4,'FUNC',47)
insert t values (5,'FUNC',5)
insert t values (6,'FUNC',5)
insert t values (4,'INDIL',167)
insert t values (5,'INDIL',18)
insert t values (6,'INDIL',18)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(month)
FROM t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT *
INTO tabletest from
(
select month, id, cnt
from t
) x
pivot
(
sum(cnt)
for month in (' + #cols + ')
) p '
execute(#query)
drop table t
I need to select from table where the the table name suffix are from another table, like this :
declare #value nvarchar(3),
#table nvarchar(1000),
#SQLST NVARCHAR(255);
set #value = N'select column1 from tableX';
EXEC #value
set #table ='partoftableY'
Set #SQLST ='select * from' +#tabel + #value -- here I create the table name
However there are multiple values in TableX (0-999) and so this doesn't work. Do I need a For Each type construct.
here in an example I created with two tables (partoftableY1 & partoftableY2) with different data in each
/*
create table tableX (column1 int);
insert into tablex
select 1
union all select 2;
create table partoftableY1 (data nvarchar(50));
create table partoftableY2 (data nvarchar(50));
insert into partoftableY1 select 'hey 1 here';
insert into partoftableY2 select 'hey 2 here';
*/
declare #sql nvarchar(max)
-- use the ability of SQL to build up string of all the sql you need to run
set #sql = 'select data from (select '''' as data'
select #sql = COALESCE(#sql + ' union all ', '')
+ 'select data from partoftableY'
+ cast(column1 as nvarchar(4)) from tableX
select #sql = #sql + ') X where data <>'''''
-- DEBUG for seeing what SQL you created
print #sql
-- Now execute the SQL
exec sp_executesql #sql= #sql
which gives me the results of
hey 1 here
hey 2 here
You will need to adjust it for types of your data, but this should give you the main idea
For reference here is the sql that was created and executed:
select data
from (
select '' as data
union all select data from partoftableY1
union all select data from partoftableY2
) X
where data <>''
N.B.
I put formatted it for easier reading, as it's actually created as one long line
I used selet data and not select * as the number of columns needs to be the same for each select in the union. You will need to select the columns you need and then make changes ensure that all the columns in the selects in the union are the same.
There is a dummy select at the top of the union to make the union code easy - no conditionals needed as whether the union all needs to present
I used the out select over the whole union to enable you to get sid of the dummy select
You can try this
DECLARE #SQLST NVARCHAR(max)='';
DECLARE #select nvarchar(max)=N'select * from partoftableY'
DECLARE #union nvarchar(max)=N'
UNION ALL
'
SELECT #SQLST=#select+column1+#union
FROM tablex
SELECT #SQLST=substring(#SQLST,1,LEN(#SQLST)-11)
EXEC sp_executesql #SQLST