How do you select an unique id in an DB2 table that was generated using DB2 generate unique() function? - sql

I would like to select the information from a DB2 table, using that unique id that was generated using DB2 generate unique() function as key in a simple select query.
Using the field directly, gives an SQL code of -333, using HEX(unique id) in select gives SQL code of +100.
How can i get the information from the table?

This depends on the data type of your parameter.
If you know its hex representation, then:
DECLARE GLOBAL TEMPORARY TABLE SESSION.GEN_UNIQUE (U) AS (VALUES GENERATE_UNIQUE())
WITH DATA WITH REPLACE ON COMMIT PRESERVE ROWS NOT LOGGED;
SELECT HEX(U) AS HEX_VAL
FROM SESSION.GEN_UNIQUE
WHERE U = HEXTORAW ('20210531172108210028000000');
|HEX_VAL |
|--------------------------|
|20210531172108210028000000|
If you have it as CHAR(13) FOR BIT DATA, then it can be used as usual:
--#SET TERMINATOR #
BEGIN
DECLARE L_VAR CHAR(13) FOR BIT DATA;
DECLARE L_DUMMY INT;
DECLARE EXIT HANDLER FOR NOT FOUND
BEGIN
RESIGNAL SQLSTATE '70001' SET MESSAGE_TEXT = 'Oops!';
END;
SET L_VAR = HEXTORAW('20210531172108210028000000');
SELECT 1 INTO L_DUMMY
FROM SESSION.GEN_UNIQUE
WHERE U = L_VAR;
END
#

Related

how can I print results from sql procedure?

I'm writing a procedure to count rows in every table in my database. It so far looks like this:
create or replace procedure count_database_rows()
dynamic result sets 1
P1: begin atomic
DECLARE stmt CHAR(40);--
FOR v1 AS
c1 CURSOR FOR
SELECT TABLE_SCHEMA, TABLE_NAME FROM sysibm.tables
DO
SET stmt = 'SELECT COUNT(*) FROM '||TABLE_SCHEMA||'.'||TABLE_NAME;--
PREPARE s FROM stmt;--
EXECUTE s;--
END FOR;--
end P1
~
however, when I run it:
db2 -ntd~ -f script.sql > dump.csv
all I'm getting is:
DB20000I The SQL command completed successfully.
how can I print all results instead?
Just for demonstration. I assume, that it's some educational task, and it's Db2 for LUW.
For non-DPF Db2 for LUW systems only
--#SET TERMINATOR #
CREATE OR REPLACE FUNCTION COUNT_DATABASE_ROWS()
RETURNS TABLE (P_TABSCHEMA VARCHAR(128), P_TABNAME VARCHAR(128), P_ROWS BIGINT)
BEGIN
DECLARE L_STMT VARCHAR(256);
DECLARE L_ROWS BIGINT;
FOR V1 AS
SELECT TABSCHEMA, TABNAME
FROM SYSCAT.TABLES
WHERE TYPE IN ('T', 'S')
FETCH FIRST 10 ROWS ONLY
DO
SET L_STMT = 'SET ? = (SELECT COUNT(*) FROM "'||V1.TABSCHEMA||'"."'||V1.TABNAME||'")';
PREPARE S FROM L_STMT;
EXECUTE S INTO L_ROWS;
PIPE(V1.TABSCHEMA, V1.TABNAME, L_ROWS);
END FOR;
RETURN;
END#
SELECT * FROM TABLE(COUNT_DATABASE_ROWS())#
For any Db2 for LUW systems
A little bit tricky for DPF systems, but doable as well. We have to wrap the code which is not allowed in the inlined compound statement into the stored procedure.
--#SET TERMINATOR #
CREATE OR REPLACE PROCEDURE COUNT_DATABASE_ROWS_DPF(OUT P_DOC XML)
READS SQL DATA
BEGIN
DECLARE L_STMT VARCHAR(256);
DECLARE L_ROWS BIGINT;
DECLARE L_NODE XML;
SET P_DOC = XMLELEMENT(NAME "DOC");
FOR V1 AS
SELECT TABSCHEMA, TABNAME
FROM SYSCAT.TABLES
WHERE TYPE IN ('T', 'S')
FETCH FIRST 10 ROWS ONLY
DO
SET L_STMT = 'SET ? = (SELECT COUNT(*) FROM "'||V1.TABSCHEMA||'"."'||V1.TABNAME||'")';
PREPARE S FROM L_STMT;
EXECUTE S INTO L_ROWS;
SET L_NODE = XMLELEMENT
(
NAME "NODE"
, XMLELEMENT(NAME "TABSCHEMA", V1.TABSCHEMA)
, XMLELEMENT(NAME "TABNAME", V1.TABNAME)
, XMLELEMENT(NAME "ROWS", L_ROWS)
);
SET P_DOC = XMLQUERY
(
'transform copy $mydoc := $doc modify do insert $node as last into $mydoc return $mydoc'
passing P_DOC as "doc", L_NODE as "node"
);
END FOR;
END#
CREATE OR REPLACE FUNCTION COUNT_DATABASE_ROWS_DPF()
RETURNS TABLE (P_TABSCHEMA VARCHAR(128), P_TABNAME VARCHAR(128), P_ROWS BIGINT)
BEGIN ATOMIC
DECLARE L_DOC XML;
CALL COUNT_DATABASE_ROWS_DPF(L_DOC);
RETURN
SELECT *
FROM XMLTABLE ('$D/NODE' PASSING L_DOC AS "D" COLUMNS
TYPESCHEMA VARCHAR(128) PATH 'TABSCHEMA'
, TABNAME VARCHAR(128) PATH 'TABNAME'
, LENGTH BIGINT PATH 'ROWS'
);
END#
-- Usage. Either CALL or SELECT:
CALL COUNT_DATABASE_ROWS_DPF(?)#
SELECT * FROM TABLE(COUNT_DATABASE_ROWS_DPF())#
If your Db2-server runs on Linux/Unix/Windows then you can use the DBMS_OUT.PUT_LINE function to send diagnostic output from SQL routines to the console. The idea is that in your routine, you assign to a variable some text (example, the table name and its count), then call DBMS_OUTPUT.PUT_LINE(...) to cause that text to appear on the console. The disadvantage of this approach is that the output will only appear once the routine has completed. This is often not what you want, sometimes you want to see the row-counts as they become available, so consider instead alternative approaches, as shown below.
To see DBMS_OUTPUT.PUT_LINE output with the Db2 CLP (or db2cmd.exe) you first need to use set serveroutput on before calling the procedure.
But for simple stuff like this, a stored procedure might be unsuitable, because you can use the CLP to do the work in two steps after connecting to the database. This is often more convenient for scripting purposes. The idea is that you make a file to generate the queries, which when you run with CLP creates a second file, and you execute the second file to get the desired results.
Example
Create file gen_counts.sql containing the query that generates the real queries, for example gen_counts.sql might contain
select 'select count(*) from '||rtrim(tabschema)||'.'||rtrim(tabname)||' with ur;'
from syscat.tables;
Then you can do these steps:
db2 connect to $database
db2 -txf gen_counts.sql > count_queries.sql
db2 -tvf count_queries.sql > count_results.txt
Note that the output file (in this case count_results.txt) is readable via another shell session while the script continues to run. You can also pipe the output to concurrent jobs if required.
However, experienced DBAs might avoid row-counting all tables in this manner, and might choose instead to ensure that the runstats are always up-to-date for all tables, and accept recent estimates of row counts, which are visible in SYSCAT.TABLES.CARD once runstats are completed. If the stats are up to date, the CARD count is often good enough for many purposes. If exact counts are required, they are often valid only for a specific timestamp if the database is live.

SQL Update Statement based on Procedure in SAP HANA

I'm creating an update statement that generate SHA256 for table columns based on table's name
1st Step: I created a procedure that get the table columns, concatenate it all in one columns, then format to a desired format.
-- Procedure code : Extract table's columns list, concatenate it and format it
Create procedure SHA_PREP (in inp1 nvarchar(20))
as
begin
SELECT concat(concat('hash_sha256(',STRING_AGG(A, ', ')),')') AS Names
FROM (
SELECT concat('to_varbinary(IFNULL("',concat(COLUMN_NAME,'",''0''))')) as A
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = 'SCHEMA_NAME' AND TABLE_NAME = :inp1
AND COLUMN_NAME not in ('SHA')
ORDER BY POSITION
);
end;
/* Result of this procedures :
hash_sha256(
to_varbinary("ID"),to_varbinary(IFNULL("COL1",'0')),to_varbinary(IFNULL("COL2",'0')) )
*/
-- Update Statement needed
UPDATE "SCHEMA_NAME"."TABLE_NAME"
SET "SHA" = CALL "SCHEMA_NAME"."SHA_PREP"('SCHEMA_NAME')
WHERE "ID" = 99 -- a random filter
The solution by #SonOfHarpy technically works but has several issues, namely:
unnecessary use of temporary tables
overly complicated string assignment approach
use of fixed system table schema (SYS.TABLE_COLUMNS) instead of PUBLIC synonym
wrong data type and variable name for the input parameter
An improved version of the code looks like this:
create procedure SHA_PREP (in TABLE_NAME nvarchar(256))
as
begin
declare SQL_STR nvarchar(5000);
SELECT
'UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA"= hash_sha256(' || STRING_AGG(A, ', ') || ')'
into SQL_STR
FROM (
SELECT
'TO_VARBINARY(IFNULL("'|| "COLUMN_NAME" ||'",''0''))' as A
FROM TABLE_COLUMNS
WHERE
"SCHEMA_NAME" = 'SCHEMA_NAME'
AND "TABLE_NAME" = :TABLE_NAME
AND "COLUMN_NAME" != 'SHA'
ORDER BY POSITION
);
-- select :sql_str from dummy; -- this is for debugging output only
EXECUTE IMMEDIATE (:SQL_STR);
end;
By changing the CONCAT functions to the shorter || (double-pipe) operator, the code becomes a lot easier to read as the formerly nested function calls are now simple chained concatenations.
By using SELECT ... INTO variable the whole nonsense with the temporary table can be avoided, again, making the code easier to understand and less prone to problems.
The input parameter name now correctly reflects its meaning and mirrors the HANA dictionary data type for TABLE_NAME (NVARCHAR(256)).
The procedure now consists of two commands (SELECT and EXECUTE IMMEDIATE) that each performs an essential task of the procedure:
Building a valid SQL update command string.
Executing the SQL command.
I removed the useless line-comments but left a debugging statement as a comment in the code, so that the SQL string can be reviewed without having to execute the command.
For that to work, obviously, the EXECUTE... line needs to be commented out and the debugging line has to be uncommented.
What's more worrying than the construction of the solution is its purpose.
It looks as if the SHA column should be used as a kind of shorthand row-data fingerprint. The UPDATE approach certainly handles this as an after-thought activity but leaves the "finger-printing" for the time when the update gets executed.
Also, it takes an essential part of the table design (that the SHA column should contain the fingerprint) away from the table definition.
An alternative to this could be a GENERATED COLUMN:
create table test (aaa int, bbb int);
alter table test add (sha varbinary (256) generated always as
hash_sha256(to_varbinary(IFNULL("AAA",'0'))
, to_varbinary(IFNULL("BBB",'0'))
)
);
insert into test (aaa, bbb) values (12, 32);
select * from test;
/*
AAA BBB SHA
12 32 B6602F58690CA41488E97CD28153671356747C951C55541B6C8D8B8493EB7143
*/
With this, the "generator" approach could be used for table definition/modification time, but all the actual data handling would be automatically done by HANA, whenever values get changed in the table.
Also, no separate calls to the procedure will ever be necessary as the fingerprints will always be current.
I find a solution that suits my need, but maybe there's other easier or more suitable approchaes :
I added the update statement to my procedure, and inserted all the generated query into a temporary table column, the excuted it using EXECUTE IMMEDIATE
Create procedure SHA_PREP (in inp1 nvarchar(20))
as
begin
/* ********************************************************** */
DECLARE SQL_STR VARCHAR(5000);
-- Create a temporary table to store a query in
create local temporary table #temp1 (QUERY varchar(5000));
-- Insert the desirable query into the QUERY column (Temp Table)
insert into #temp1(QUERY)
SELECT concat('UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA" =' ,concat(concat('hash_sha256(',STRING_AGG(A, ', ')),')'))
FROM (
SELECT concat('to_varbinary(IFNULL("',concat(COLUMN_NAME,'",''0''))')) as A
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = 'SCHEMA_NAME' AND TABLE_NAME = :inp1
AND COLUMN_NAME not in ('SHA')
ORDER BY POSITION
);
end;
/* QUERY : UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA" =
hash_sha256(to_varbinary("ID"),to_varbinary(IFNULL("COL1",'0')),to_varbinary(IFNULL("COL2",'0'))) */
SELECT QUERY into SQL_STR FROM "SCHEMA_NAME".#temp1;
--Excuting the query
EXECUTE IMMEDIATE (:SQL_STR);
-- Dropping the temporary table
DROP TABLE "SCHEMA_NAME".#temp1;
/* ********************************************************** */
end;
Any other solution or improvement are well welcomed
Thank you

Db2 for i - dynamic FROM clause

Environment: Db2 for i, version 7.3
Library/table structure:
CORPORATE/TENANTS
LIB01/INVOICE
LIB02/INVOICE
LIB03/INVOICE
…
LIBxx/INVOICE
The CORPORATE/TENANTS table contains a list of libraries where information about each tenant is stored. It has this structure and data:
CREATE OR REPLACE TABLE TENANTS (
ID BIGINT GENERATED ALWAYS AS IDENTITY (START WITH 1),
TENANT CHAR(10) NOT NULL,
PRIMARY KEY(ID)
) RCDFMT TENANTSR;
RUNSQLSTM SRCFILE(HILLB/QDDLSRC) SRCMBR(TENANTS) DFTRDBCOL(CORPORATE)
+--+------+
|ID|TENANT|
+--+------+
| 1|LIB01 |
| 2|LIB02 |
|..|......|
|99|LIB99 |
+--+------+
The LIBxx/INVOICE tables are all identical to each other and have this structure:
CREATE OR REPLACE TABLE INVOICE (
ID BIGINT GENERATED ALWAYS AS IDENTITY (START WITH 1),
PAYDAT INTEGER(6,0) NOT NULL,
AMOUNT DECIMAL(15,2) NOT NULL DEFAULT 0,
PRIMARY KEY(ID)
) RCDFMT INVOICER;
+--+------+------+
|ID|PAYDAT|AMOUNT|
+--+------+------+
| 1|180701|100.00|
| 2|180801| 35.00|
|..|......|......|
+--+------+------+
I want to generate a list of invoice amounts for all tenants for a given date:
180701 LIB01 100.00
180701 LIB02 140.00
180701 LIB03 74.00
…
Conceptually what I want to do is this (yes, I know this is invalid SQL):
SELECT PAYDAT, TENANT, AMOUNT
FROM $X.INVOICE
WHERE PAYDAT = 180701;
I want to pull data from the INVOICE table for each TENANT but I know the FROM clause cannot be dynamic like this. I’m sure this kind of query has a name but I don’t know what it is so I’m unable to effectively use a search engine to find what I need.
This would be trivial to solve with an RPGLE program but I need a pure SQL solution.
Please note - the LIBxx values CANNOT be hardcoded in any way. These values can change at any time.
To do what you want, you can use a stored procedure with an EXECUTE IMMEDIATE in a loop to build a result set. Something like this:
Note: this is not a complete cut and paste solution, but you can modify it to do what you want.
CREATE OR REPLACE PROCEDURE GETINVOICEAMOUNTS ( )
DYNAMIC RESULT SETS 1
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
CALLED ON NULL INPUT
SET OPTION COMMIT = *NONE
BEGIN
DECLARE STMT VARCHAR(1024);
DECLARE RECORD_FOUND INTEGER DEFAULT 1;
DECLARE LIBRARY CHAR(10);
DECLARE C1 CURSOR FOR
SELECT TENANT FROM CORPORATE/TENANT;
DECLARE C2 CURSOR WITH RETURN FOR
SELECT * FROM SESSION.TMP ;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET RECORD_FOUND = 0;
DECLARE GLOBAL TEMPORARY TABLE TMP
(PAYDAT INTEGER(6,0),
TENANT CHAR(10),
AMOUNT DECIMAL(15,2))
WITH REPLACE;
OPEN C1;
LOOP
FETCH C1 INTO LIBRARY;
IF RECORD_FOUND = 0;
LEAVE LOOP;
END IF;
SET STMT = 'INSERT INTO SESSION.TMP SELECT PAYDAT, LIBRARY, AMOUNT FROM ' || RTRIM(LIBRARY) || '.INVOICE WHERE PAYDAT = 180701';
EXECUTE IMMEDIATE STMT
END LOOP;
CLOSE C1;
OPEN C2;
END;
I gave you more than I planned to. But, one specific modification you will invariably need is to parameterize the date that you want to retrieve.
This is how it works:
A global temporary table named TMP is used to collect the records to be returned in a result set. Once all the records are collected, a cursor is opened over TMP and the procedure ends. This causes the values collected in TMP to be returned as a result set.
To collect the values the CORPORATE/TENANT file is read, and the column TENANT is retrieved into the variable LIBRARY. For each record a statement is built that concatenates LIBRARY into an INSERT statement. This statement is executed which loads the record into TMP. I am using EXECUTE IMMEDIATE because I cannot use a parameter marker to replace the table reference in the INSERT statement, so a prepared statement is just extra work.
You could use UNION ALL:
SELECT sub.PAYDAT, t.TENANT, sub.AMOUNT
FROM (SELECT * FROM LIB01.INVOICE
UNION ALL
SELECT * FROM LIB02/INVOICE
...
SELECT * FROM LIB0n/INVOICE) sub
JOIN TENANTS t
ON sub.id = t.id
WHERE SUB.PAYDAT = 180701;
It is an SELECT * FROM sales + #yymm template.
EDIT:
More secure way is to create a view:
CREATE VIEW combined_invoice
AS
SELECT * FROM LIB01.INVOICE
UNION ALL
SELECT * FROM LIB02/INVOICE
...
SELECT * FROM LIB0n/INVOICE;
And query:
SELECT sub.PAYDAT, t.TENANT, sub.AMOUNT
FROM combined_invoice sub
JOIN TENANTS t
ON sub.id = t.id
WHERE SUB.PAYDAT = 180701;
Of course view should be altered after adding/removing tables.

DB2 Select Statement error after for loop in stored procedure

I've written a stored procedure which uses a for loop to execute a query for a list of views. It generates a dynamic sql statement for each view inside the for loop and then executes it, which inserts output into a declared temporary table.
The for loop works perfectly and it runs without errors, however if I add a select statement after the END FOR; to get the final output from the temporary table I get the error below. Does anyone have any ideas please?
Error 16/07/2018 10:43:41 0:00:00.007 DB2 Database Error: ERROR [42601] [IBM][DB2/AIX64] SQL0104N An unexpected token "select *" was found following "1; END FOR; ". Expected tokens may include: "<call>". LINE NUMBER=31. SQLSTATE=42601
SQL Code:
BEGIN
DECLARE SQLTEXT varchar(500);
DECLARE GLOBAL TEMPORARY TABLE SESSION.AS_USAGE_RESULTS(
temp table columns
);
FOR v as cur1 cursor for
select distinct viewname,viewschema
from syscat.VIEWS
DO
SET SQLTEXT = 'Dynamic Insert into temp table here'
PREPARE s1 FROM SQLTEXT;
EXECUTE s1;
END FOR;
select *
from SESSION.AS_USAGE_RESULTS;
DROP TABLE SESSION.AS_USAGE_RESULTS;
END
Your mistake is that if you wish to return a result-set from session.as_usage_results, then you must declare a cursor for its select, and open that cursor then end the sproc. This is a FAQ. There are examples in the IBM Db2 Server SAMPLES directory and in the Db2 Knowledge Center.
Inside the sproc, you can either use SELECT ... INTO, or use a select within a cursor, or use a SELECT as part of a SET statement.
You should not drop the session table in the procedure in case the result-set won't be consumed before the table gets dropped. Either drop the session table elsewhere or use an alternative design.
In your example you don't need cursor cur1, so below I show a stilted artificial example of what your might mean. It is artificial because you can see that the session table is also redundant for this example, but it shows the use of the cursor for the result-set.
--#SET TERMINATOR #
create or replace procedure dynproc1
language sql
specific dynproc1
dynamic result sets 1
BEGIN
DECLARE v_sqltext varchar(2000);
DECLARE c1 cursor with return to client for s1;
DECLARE GLOBAL TEMPORARY TABLE SESSION.AS_USAGE_RESULTS ( viewname varchar(128), viewschema varchar(128) );
insert into session.as_usage_results(viewname, viewschema) select viewname, viewschema from syscat.views;
set v_sqltext = 'select * from session.as_usage_results';
prepare s1 from v_sqltext;
open c1;
END
#

Create a function for generating random number in SQL Server trigger

I have to create a function in a SQL Server trigger for generating random numbers after insert. I want to update the column with that generated random number please help what I have missed in my code.
If you know other ways please suggest a way to complete my task.
This my SQL Server trigger:
ALTER TRIGGER [dbo].[trgEnquiryMaster]
ON [dbo].[enquiry_master]
AFTER INSERT
AS
declare #EnquiryId int;
declare #ReferenceNo varchar(50);
declare #GenReferenceNo NVARCHAR(MAX);
select #EnquiryId = i.enquiry_id from inserted i;
select #ReferenceNo = i.reference_no from inserted i;
BEGIN
SET #GenReferenceNo = 'CREATE FUNCTION functionRandom (#Reference VARCHAR(MAX) )
RETURNS VARCHAR(MAX)
As
Begin
DECLARE #r varchar(8);
SELECT #r = coalesce(#r, '') + n
FROM (SELECT top 8
CHAR(number) n FROM
master..spt_values
WHERE type = P AND
(number between ascii(0) and ascii(9)
or number between ascii(A) and ascii(Z)
or number between ascii(a) and ascii(z))
ORDER BY newid()) a
RETURNS #r
END
'
EXEC(#GenReferenceNo)
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
-- update statements for trigger here
UPDATE enquiry_master
SET reference_no ='updated'
WHERE enquiry_id = #EnquiryId
END
To generate random numbers, just call CRYPT_GEN_RANDOM which was introduced in SQL Server 2008:
SELECT CRYPT_GEN_RANDOM(5) AS [Hex],
CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(5), 2) AS [HexStringWithout0x],
CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(10)) AS [Translated-ASCII],
CONVERT(NVARCHAR(20), CRYPT_GEN_RANDOM(20)) AS [Translated-UCS2orUTF16]
returns:
Hex HexStringWithout0x Translated-ASCII Translated-UCS2orUTF16
0x4F7D9ABBC4 0ECF378A7A ¿"bü<ݱØï 붻槬㟰添䛺⯣왚꒣찭퓚
If you are ok with just 0 - 9 and A - F, then the CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(5), 2) is all you need.
Please see my answer on DBA.StackExchange on a similar question for more details:
Password generator function
The UPDATE statement shown in the "Update" section of that linked answer is what you want, just remove the WHERE condition and add the JOIN to the Inserted pseudo-table.
The query should look something like the following:
DECLARE #Length INT = 10;
UPDATE em
SET em.[reference_no] = rnd.RandomValue
FROM dbo.enquiry_master em
INNER JOIN Inserted ins
ON ins.enquiry_id = em.enquiry_id
CROSS APPLY dbo.GenerateReferenceNo(CRYPT_GEN_RANDOM((em.[enquiry_id] % 1) + #Length)) rnd;
And since the function is slightly different, here is how it should be in order to get both upper-case and lower-case letters:
CREATE FUNCTION dbo.GenerateReferenceNo(#RandomValue VARBINARY(20))
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
WITH base(item) AS
(
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL UNION ALL
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL
), items(item) AS
(
SELECT NULL
FROM base b1
CROSS JOIN base b2
)
SELECT (
SELECT TOP (LEN(#RandomValue))
SUBSTRING('1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm',
(CONVERT(TINYINT, SUBSTRING(#RandomValue, 1, 1)) % 62) + 1,
1) AS [text()]
FROM items
FOR XML PATH('')
) AS [RandomReferenceNo];
GO
And please follow the usage shown above, passing in CRYPT_GEN_RANDOM((em.[enquiry_id] % 1) + #Length), not: CRYPT_GEN_RANDOM(#RefferenceNOLength).
Other notes:
#marc_s already explained the one-row vs multiple-rows flaw and how to fix that.
not only is a trigger not the place to create a new object (i.e. the function), that function wouldn't have worked anyway since the call to newid() (in the ORDER BY) is not allowed in a function.
You don't need to issue two separate SELECTs to set two different variables. You could do the following:
SELECT #EnquiryId = i.enquiry_id,
#ReferenceNo = i.reference_no
FROM TableName i;
Passing strings into a function requires quoting those strings inside of single-quotes: ASCII('A') instead of ASCII(A).
UPDATE
The full Trigger definition should be something like the following:
ALTER TRIGGER [dbo].[trgEnquiryMaster]
ON [dbo].[enquiry_master]
AFTER INSERT
AS
BEGIN
DECLARE #Length INT = 10;
UPDATE em
SET em.[reference_no] = rnd.RandomValue
FROM dbo.enquiry_master em
INNER JOIN Inserted ins
ON ins.enquiry_id = em.enquiry_id
CROSS APPLY dbo.GenerateReferenceNo(
CRYPT_GEN_RANDOM((em.[enquiry_id] % 1) + #Length)
) rnd;
END;
A trigger should be very nimble and quick - it is no place to do heavy and time-intensive processing, and definitely no place to create new database objects since (a) the trigger is executed in the context of the code causing it to fire, and (b) you cannot control when and how often the trigger is fired.
You need to
define and create your function to generate that random value during database setup - once, before any operations are executed on the database
rewrite your trigger to take into account that multiple rows could be inserted at once, and in that case, the Inserted table will contain multiple rows which all have to be handled.
So your trigger will look something like this (with several assumptions by me - e.g. that enquiry_id is the primary key on your table - you need this to establish the INNER JOIN between your data table and the Inserted pseudo table:
ALTER TRIGGER [dbo].[trgEnquiryMaster]
ON [dbo].[enquiry_master]
AFTER INSERT
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
-- update statements for trigger here
UPDATE enq
SET reference_no = dbo.GenerateRandomValue(.....)
FROM enquiry_master enq
INNER JOIN inserted i ON enq.enquiry_id = i.enquiry_id