0

I am writing the following dynamic SQL, and getting an error around the FROM keyword.

Incorrect syntax near the keyword 'FROM'.

  ' + @columnList + '
FROM [History] 

I know why, its because there shouldn't be a comma that precedes it. However, since the column before it (@columnList) is a result of dynamic SQL, how do I go about resolving this?

Basically, I need a way to make

SELECT @columnList =....

not append a comma at the end to the LAST column/Account selected.

The comma is added at the end at this part:

quotename(AccTbl.Account), ',',

Full Query:

DECLARE @sqlCommand NVARCHAR(MAX) = '',
        @columnList NVARCHAR(MAX) = '',
        @pivotColumns NVARCHAR(MAX) = '';

SELECT @columnList =
  (SELECT DISTINCT concat(CHAR(9), 'COALESCE(', quotename(AccTbl.Account), ', 0)', quotename(AccTbl.Account), ',', CHAR(10)) --CHAR(9) & CHAR(10) for indentation/formatting
   FROM [Accounts] AccTbl
   WHERE AccTbl.Account NOT IN ('WS')
   FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)');

SELECT @pivotColumns = STUFF(
   (SELECT DISTINCT concat(CHAR(9), ',', quotename(AccTbl.Account), CHAR(10))
    FROM [Accounts] AccTbl
    WHERE AccTbl.Account NOT IN ('WS')
    FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 1, '');

/*
EXEC the sqlCommand as separate batches to prevent this error: 'CREATE VIEW' must be the first statement in a query batch.
https://stackoverflow.com/a/39135516/8397835
*/

SET @sqlCommand = '
    USE [ABC_DB]
    --GO
    DROP VIEW IF EXISTS [dbo].[Piv];
    --GO
    SET ANSI_NULLS ON
    --GO
    SET QUOTED_IDENTIFIER ON
    --GO
';

Execute sp_executesql @sqlCommand;

SET @sqlCommand = '
    CREATE VIEW [dbo].[Piv]
    AS
    (SELECT
      [Style Code],
      ' + @columnList + '
    FROM [History] 
        PIVOT (SUM([Value]) FOR [Accounts] IN (
            ' + @pivotColumns + '
            )
        ) 
    AS Piv);
';

PRINT @sqlCommand;
Execute sp_executesql @sqlCommand;

In other words, whats happening right now is something like this:

expl

UPDATE:

@columnList was fixed with leading comma instead of trailing comma, but leading comma nor trailing comma would work for @pivotColumns because we don't have a pre-existing column in the PIVOT part of the query like we do in the SELECT statement with Style Code.

issue

Dale K
  • 25,246
  • 15
  • 42
  • 71
Cataster
  • 3,081
  • 5
  • 32
  • 79
  • You are appending a tab as well, so you need your `STUFF` parameters to be `, 1, 2, '')` I strongly suggest using `STRING_AGG` instead if you can. Also those `USE` and `SET` commands need to be repeated in the next query, they only hold for that scope. – Charlieface Mar 31 '21 at 20:45
  • @Charlieface I considered using `STRING AGG` but i have sql server 2016 :( also, do I even need to have those USE and SET? I find that they automatically get generated whoever I create a view manually. However, the reason why I have them in separate scopes is because I got an error that CREATE VIEW needs to be first statement in the batch, and an SO answer suggested splitting them into separate batches – Cataster Mar 31 '21 at 20:56

2 Answers2

3

Don't put the commas at the end, put them at the start, and then strip the first character, using STUFF, that's far easier in T-SQL.

So instead of {Expression} + ',' do ',' + {Expression}. Then you can simply do STUFF(@columnList,1,1,'') to remove the leading comma instead.

Thom A
  • 88,727
  • 11
  • 45
  • 75
  • *"oh thats a good idea!"* You *do* realise that's what you are already doing for `@pivotColumns` though, right...? Don't blindly copy and paste solutions from the internet (I assume that's why you have mixed logic), take the time to understand what they are doing. – Thom A Mar 31 '21 at 17:48
  • i did realize that thats why i said good idea, cause I literally had `@pivotColumns` doing that. I think the reason I opted for a different way for `@columnList` is because the current view i have has the commas at the end so thats why i added the comma at the end for the `@columnList` ones instead. Its one of those moments where i had a brain freeze for a moment. I know exactly whats happening though, as i did lots of research around STUFF to understand what the heck is happening.Actually, I know theres a better way to do this `STRING AGG` but requires 2017, i only have 2016 at the moment tho – Cataster Mar 31 '21 at 17:51
  • ok now i am facing issue with `@pivotColumns`. the first column has a leading comma, in this part `FOR [Accounts] IN ( ' + @pivotColumns + '`, which should not be the case, and if i make the comma trailing like i had it originally for `@columnList`, its gonna be an issue at the end as well – Cataster Mar 31 '21 at 18:03
  • I don't understand. You put the comma at the start, and the remove the very first one. That is the solution, @Cataster . I can't run your SQL, so I can't replicate your problem; I don't have access to your instance. If you have a comma at the start and end, did you forget to remove the one at the end? – Thom A Mar 31 '21 at 19:46
  • So basically, the issue i had with the `@columnList` has been resolved thanks to leading comma suggestion. however, now that that part of the view query is correct, im getting error for second part of the query, also due to comma. please check my post update – Cataster Mar 31 '21 at 20:12
  • since the 2nd issue diverts away from the original ask of this post, i marked your answer as solution to remain within SO guidelines. I appreciate your quick assistance Larnu! If you have further ideas about the 2nd issue i reported, please see new question here : https://stackoverflow.com/questions/66895759/remove-leading-comma-from-first-column – Cataster Mar 31 '21 at 21:50
1

Part of the problem is your 'formatting' of the code. Generally I would agree that formatting the dynamic code can help in debugging - here it is getting in the way.

SELECT @columnList = STUFF(
  (SELECT DISTINCT concat(', ', quotename(AccTbl.Account))
   FROM [Accounts] AccTbl
   WHERE AccTbl.Account NOT IN ('WS')
   FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 1, '');

SELECT @pivotColumns = STUFF(
   (SELECT DISTINCT concat(', ', quotename(AccTbl.Account))
    FROM [Accounts] AccTbl
    WHERE AccTbl.Account NOT IN ('WS')
    FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 1, '');

Removing those - we see that both statements are exactly the same. Assume the results from the Accounts table are: Acct1, Acct2

You would get the result as '[Acct1], [Acct2]' for both @columnList and @pivotColumns. So - if you want to expand on the column list portion, for example add the table alias (which is what I would do):

SELECT @columnList = STUFF(
  (SELECT DISTINCT concat(', ', 'h.', quotename(AccTbl.Account))
   FROM [Accounts] AccTbl
   WHERE AccTbl.Account NOT IN ('WS')
   FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 1, '');

I would not bother with formatting these columns in the final result, since you will be recreating the view as needed using dynamic SQL.

SET @sqlCommand = '
    CREATE VIEW [dbo].[Piv]
    AS
    (SELECT
      h.[Style Code],
      ' + @columnList + '
    FROM [History] h
        PIVOT (SUM(h.[Value]) FOR [Accounts] IN (
            ' + @pivotColumns + '
            )
        ) 
    AS Piv);
';
Jeff
  • 512
  • 2
  • 8
  • the lists are not exactly the same because `@columnList` prepends `COALESCE`as well. btw, welcome to stackoverflow! – Cataster Mar 31 '21 at 21:26
  • even with the table alias though, the leading comma issue isnt resolved in the PIVOT portion – Cataster Mar 31 '21 at 21:32