0

I have the following table:

CREATE TABLE NZQA_Unit(
    NZQAUnitID                          int IDENTITY(1,1) PRIMARY KEY NOT NULL,
    NZQAUnitNumber                      int NOT NULL,
    Title                               nvarchar(255) NOT NULL,
    Level                               smallint NOT NULL,
    Credits                             smallint NOT NULL,
    Classification                      nvarchar(255) NULL,
    AvailableGrade                      int NULL DEFAULT 1,
    Purpose                             nvarchar(max) NULL,
    Validity                            int NOT NULL DEFAULT 1, -- by default the unit will be 'Current'
    Document                            VARBINARY(MAX) NULL, -- for being able to upload a PDF or Word document
    DocumentExtension                   varchar(5) NULL, -- for storing the file extension
    CONSTRAINT AK_NZQA_Unit_Title           UNIQUE(Title),
    CONSTRAINT AK_NZQA_Unit_Number          UNIQUE(NZQAUnitNumber),
    CONSTRAINT FK_NZQA_Unit_Validity        FOREIGN KEY (Validity) REFERENCES NZQA_Unit_Validity (ValidityID),
    CONSTRAINT FK_NZQA_Unit_AvailableGrade  FOREIGN KEY (AvailableGrade) REFERENCES NZQA_Unit_Assessment_Grade (AssessmentGradeID),
    CONSTRAINT CK_NZQA_Unit_Title           CHECK ((len(ltrim(rtrim(Title)))>(2))),
    CONSTRAINT CK_NZQA_Unit_ID_Range        CHECK (NZQAUnitNumber >= 1000 AND NZQAUnitNumber <= 99999), --Inclusive
    CONSTRAINT CK_NZQA_Unit_Level           CHECK (Level >= 1 AND Level <= 10), -- Level must be between 1 and 10 https://www.nzqa.govt.nz/studying-in-new-zealand/understand-nz-quals/
    CONSTRAINT CK_NZQA_Unit_Credits         CHECK (Credits >= 1 AND Credits <= 999), -- 999 has been set arbitrarily but it's high enough to fit an Engineering Degree
    CONSTRAINT CK_NZQA_Unit_DocumentSize    CHECK (DATALENGTH(Document) <= 524288), -- Maximum size 500 KB https://stackoverflow.com/questions/34741079/can-i-set-2-mb-for-maximum-size-of-varbinary      https://www.gbmb.org/mb-to-bytes
    CONSTRAINT CK_NZQA_Unit_DocumentExtension   CHECK (DocumentExtension IN ('.pdf', '.doc', '.docx')) -- this check is not case sensitive, i.e. '.DOCX' won't trigger an error
    );
GO

And I'm writing the following stored procedure:

DROP PROCEDURE  IF EXISTS  Modify_NZQA_Unit
GO

CREATE PROCEDURE Modify_NZQA_Unit
    @NZQAUnitID                 int,
    @NZQAUnitNumber             int,
    @Title                      nvarchar(255),
    @Level                      smallint,
    @Credits                    smallint,
    @Classification             nvarchar(255) NULL,
    @AvailableGrade             int,
    @Purpose                    nvarchar(max),
    @Validity                   int,
    @Document                   VARBINARY(MAX),
    @DocumentExtension          varchar(5),
    @overwriteFile              bit -- 1 to overwrite, 0 to no overwrite
AS
BEGIN
    IF (@NZQAUnitID IS NULL)
    BEGIN
        THROW 51006, 'You must input the NZQA identifier (NZQAUnitID)', 1;  
    END
    
    SET @Title = Replace(@Title, '''', '''''') -- singles quotes must be escaped
    SET @Classification = Replace(@Classification, '''', '''''')
    SET @Purpose = Replace(@Purpose, '''', '''''')
    SET @DocumentExtension = Replace(@DocumentExtension, '''', '''''')

    DECLARE @updateStatement AS NVARCHAR(1000);
    SET @updateStatement = 'UPDATE NZQA_Unit SET NZQAUnitNumber = '+CONVERT(VARCHAR, @NZQAUnitNumber)+', Title = '''+@Title+''', Level = '+CONVERT(VARCHAR, @Level)+', Credits = '+CONVERT(VARCHAR, @Credits)+', Classification = '''+@Classification+''',  AvailableGrade = '+CONVERT(VARCHAR, @AvailableGrade)+', Purpose = '''+@Purpose+''', Validity = '+CONVERT(VARCHAR, @Validity)
    
    IF (@overwriteFile IS NULL) 
    BEGIN
        THROW 51007, 'Variable @overwriteFile cannot be null', 1;  
    END
    ELSE
    BEGIN
        IF (@overwriteFile = 1)
        BEGIN
            IF (@Document IS NULL)
            BEGIN
                THROW 51008, 'If the variable @overwriteFile is set to 1, a file (@Document) must be provided', 1;  
            END 
            IF (@DocumentExtension IS NULL)
            BEGIN
                THROW 51009, 'If the variable @overwriteFile is set to 1, the document extension (@DocumentExtension) must be provided', 1;     
            END 

            SET @updateStatement = @updateStatement + ', Document = '+'HERE WILL COME THE VARBINARY'+', DocumentExtension = '''+@DocumentExtension + ''' '

            --DOESN'T WORK: EXEC('UPDATE NZQA_Unit SET NZQAUnitNumber = '+@NZQAUnitNumber+', Title = '''+@Title+''', Level = '+@Level+', Credits = '+@Credits+', Classification = '''+@Classification+''',  AvailableGrade = '+@AvailableGrade+', Purpose = '''+@Purpose+''', Validity = '+@Validity + ', Document = ' + @document + ', DocumentExtension = '''+@DocumentExtension + ''' ' +' WHERE NZQAUnitID = '+@NZQAUnitID)
            UPDATE NZQA_Unit SET NZQAUnitNumber = @NZQAUnitNumber, Title = @Title, Level = @Level, Credits = @Credits, Classification = @Classification,  AvailableGrade = @AvailableGrade, Purpose = @Purpose, Validity = @Validity, Document = @document, DocumentExtension = @DocumentExtension WHERE NZQAUnitID = @NZQAUnitID
        END
        ELSE
        BEGIN
            UPDATE NZQA_Unit SET NZQAUnitNumber = @NZQAUnitNumber, Title = @Title, Level = @Level, Credits = @Credits, Classification = @Classification,  AvailableGrade = @AvailableGrade, Purpose = @Purpose, Validity = @Validity WHERE NZQAUnitID = @NZQAUnitID
        END
    END

    SET @updateStatement = @updateStatement +' WHERE NZQAUnitID = '+CONVERT(VARCHAR, @NZQAUnitID)
    PRINT @updateStatement  
END
GO

How could I insert the varbinary data (@Document) into the @updateStatement variable? That way I could simply do a EXEC(@updateStatement)?

Code to execute the procedure below:

DECLARE @current    AS INT = 1
DECLARE @expiring   AS INT = 2
DECLARE @expired    AS INT = 3
DECLARE @achieved   AS INT = 1
DECLARE @datos      AS VARBINARY(30) = CONVERT(varbinary(30), N'this IS a test')
INSERT NZQA_Unit (NZQAUnitNumber, [Title], [Level], [Credits], [Classification], [AvailableGrade], [Purpose], Validity) VALUES (6401, N'Provide first aid', 2, 1, N'Health Studies > First Aid', @achieved, N'People credited with this unit standard are able to provide first aid.', @current)

EXEC Modify_NZQA_Unit 1, 6424, N'Provide first aid', 2, 1, N'Health Studies > First Aid', @achieved, N'People credited with this unit standard are able to provide first aid.', @current, @datos, '.pdf', 1
Dale K
  • 25,246
  • 15
  • 42
  • 71
Álvaro
  • 1,351
  • 1
  • 10
  • 14
  • 2
    Why are you trying to use dynamic SQL at all here? There's no need for it. And the Dynamic SQL you do have is open to injection. Just use a normal normal, **parametrised** query. – Thom A Feb 15 '21 at 09:09
  • 1
    If by some chance you actually do have a good reason to use dynamic SQL (which doesn't appear to be the case) then you need to explore [`sp_executesql`](https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql?view=sql-server-ver15) – Dale K Feb 15 '21 at 09:13
  • The reason to use a stored procedure is because it will be called from a C# program – Álvaro Feb 15 '21 at 09:32
  • We understand the reason for using a stored procedure, we don't understand the reason for using dynamic SQL, why not use a static update statement? i.e. the uncommented update - why do you want to convert that to dynamic SQL? – Dale K Feb 15 '21 at 09:36
  • Just to be more flexible, in case I wanted to process some of the parameters if they are null for example – Álvaro Feb 15 '21 at 09:42
  • 1
    You can do all that with static SQL, as best practice you should **always** use static SQL if you can. Dynamic SQL creates technical debt that isn't usually worth it unless you have no choice. But if you choose to, the answer is in my earlier comment - you should always use `sp_executesql` for dynamic SQL over `exec`. – Dale K Feb 15 '21 at 09:43
  • But what about if the number of parameters in the UPDATE change? – Álvaro Feb 15 '21 at 22:18

0 Answers0