Declare and Valued Table Functions - sql-server

I need to put my Declare Section into a Table-valued function.
CREATE FUNCTION [Manual_COUNTRIES_2019](
#risk_1,
#risk_2,
#risk_3,
#risk_4,
#risk_5)
RETURNS (#risk_1, #risk_2,#risk_3,#risk_4,#risk_5) TABLE (Code VARCHAR(100))
AS
BEGIN
DECLARE #risk_1 TABLE (Code VARCHAR(100));
INSERT INTO #risk_1 (Code) VALUES ('AA'),('AB'),('AC');
DECLARE #risk_2 TABLE (Code VARCHAR(100));
INSERT INTO #risk_2 (Code) VALUES ('AX'),('AY'),('AZ');
DECLARE #risk_3 TABLE (Code VARCHAR(100));
INSERT INTO #risk_3 (Code) VALUES ('BB'),('BC'),('BD'),('BE');
DECLARE #risk_4 TABLE (Code VARCHAR(100));
INSERT INTO #risk_4 (Code) VALUES ('DA'),('DB'),('DC');
DECLARE #risk_5 TABLE (Code VARCHAR(100));
INSERT INTO #risk_5 (Code) VALUES ('YY'),('XZ');
RETURN
END
I took this as a template: Declare variable in table valued function
However, it is not happy with my table suggestion, nor with the list of #risks.
What am I missing?

As you can't return multiple tables, I suggest adding another column which specifies the table you wanted to add it to, which can then be used to filter it on when you call it.
CREATE FUNCTION [Manual_COUNTRIES_2019]()
RETURNS #Risk TABLE (RiskNum int, Code VARCHAR(100))
AS
BEGIN
INSERT INTO #risk (RiskNum, Code)
VALUES (1, 'AA'),(1, 'AB'),(1, 'AC'),
(2, 'AX'),(2, 'AY'),(2, 'AZ'),
(3, 'BB'),(3, 'BC'),(3, 'BD'),(3, 'BE'),
(4, 'DA'),(4, 'DB'),(4, 'DC'),
(5, 'YY'),(4, 'XZ');
RETURN;
END;
However as you now don't need the variables I would suggest using an Inline Table Valued Function e.g.
CREATE FUNCTION [Manual_COUNTRIES_2019]()
RETURNS TABLE
RETURN
SELECT *
FROM (
VALUES (1, 'AA'),(1, 'AB'),(1, 'AC'),
(2, 'AX'),(2, 'AY'),(2, 'AZ'),
(3, 'BB'),(3, 'BC'),(3, 'BD'),(3, 'BE'),
(4, 'DA'),(4, 'DB'),(4, 'DC'),
(5, 'YY'),(4, 'XZ')
) AS X (RiskNum, Code);
You then pull the values you want as follows:
select Code from dbo.Manual_COUNTRIES_2019() where RiskNum = 1; -- For the first table, change the number to change the table.

Related

Use array as bind variable Snowflake

Using Snowflake scripting I need to bind the input parameter of the stored procedure which is an array and it will be part of array_contains function in dynamic sql however its throwing bind error
It is possible to use ARRAY data type as a bind argument inside Snowflake Scripting:
Sample data:
CREATE OR REPLACE TABLE t(i INT, j TEXT);
INSERT INTO t(i, j) VALUES (1,'a'), (2, 'b'), (3, 'c'), (4, 'd');
Stored procedure:
CREATE OR REPLACE PROCEDURE sp_array_arg(ARG ARRAY)
RETURNS TABLE(i INT, j TEXT)
LANGUAGE SQL
AS
$$
DECLARE
res RESULTSET default (SELECT i, j
FROM t
WHERE ARRAY_CONTAINS(t.i::VARIANT, :ARG));
BEGIN
RETURN TABLE(res);
END;
$$;
Procedure call:
CALL sp_array_arg([1,3]);
/*
I J
1 a
3 c
*/

insert foreign key values in store procedure sql server

I've created a insert stored procedure with two tables like in the exapmle:
reference table :
create table customerdetails (eid int,
dsgid int Foreign Key Referencesdesg_d(d_id),
ename varchar(90),
dob datetime,addr varchar(100),pincode int,salary,int,dojdatetime)
insert into customerdetails values (1,1,'ahalyaa','05.13.1993','chennai',600024,345,'06.02.2014')
source table:
create table desg_d(d_id int primary key,desg varchar(90))
insert into desg_d values(1,'programmer')
insert into desg_d values(2,'manager')
my store procedure:
create procedure sp_i #iid int,#iname varchar(90),#idobdatetime,
#iaddr varchar(100),#ipincode int, #isalary int,#iDoj datetime
as
begin
declare #idesg int
set #idesg=1
insert into customerdetails(eid,dsgid,ename,dob,addr,pincode,salary,doj)
values(#iid,#idesg,#iname,#idob,#iaddr,#ipincode,#isalary,#iDoj)
end
if i give set=1,then always idesg value should be 1, but i need to insert idesg value randomly, pls help me.
Foreign key values are also the same with the normal insert. The difference is that foreign key values to be inserted should exist on the main table.
Also, please reconsider on naming your variable in your stored procedure. Please see sample below.
create procedure sp_i
#eid int
,#dsgid int
,#ename varchar(90)
,#dob datetime
,#addr varchar(100)
,#pincode int
, #salary int
,#Doj datetime
as
begin
declare #idesg int
insert into customerdetails
(eid,dsgid,ename,dob,addr,pincode,salary,doj)
values
(#eid,#dsgid,#ename,#dob,#addr,#pincode,#salary,#Doj)
end
Use following format in your stored procedure:
DECLARE #DesgId int
INSERT INTO Desg(COLUMN) VALUES(#VALUES)
SET #DesgId = SCOPE_IDENTITY()
INSERT INTO customerdetails ( ..., Dsgid, ...)
VALUES (..., #DesgId, ...)
You can also use following format:
INSERT INTO Desg(COLUMN) VALUES(#VALUES)
INSERT INTO customerdetails ( ..., Dsgid, ...)
VALUES (..., (Select Top(1) d_id from desg_d where desg = #Desg), ...)

insert a multiple rows and a common value using stored procedures?

I have custom type (Table type) in sql server 2008.
CREATE TYPE RequestingDatesType AS TABLE
(
LeaveDate datetime,
IsHoliday bit
)
I have a store procedure
CREATE PROCEDURE CreateLeaveRequest
#EmployeeId int,
#LeaveTypeId int,
#LeaveDescription varchar(50)
#TableVariable RequestingDatesType READONLY
AS
DECLARE #LeaveId INT
//1st insert command
INSERT INTO tblLeaveMaster(EmployeeId,LeaveTypeId,LeaveDescription)
VALUES(#EmployeeId,#LeaveTypeId,#LeaveDescription)
SET #LeaveId = SCOPE_IDENTITY()
//2nd insert command
INSERT INTO tblLeaveDetails(LeaveId,LeaveDate,IsHoliday)
VALUES(#LeaveId, LeaveDate(*from custom type*), IsHoliday(*from custom type*))
My question is how can i insert values for 2nd insert command
You must use something like this:
//2nd insert command
INSERT INTO tblLeaveDetails(LeaveId,LeaveDate,IsHoliday)
SELECT #LeaveId, LeaveDate, IsHoliday FROM #TableVariable

How can I avoid calling a stored procedure from a UDF in SQL Server

Before I start, I know you can't call a stored procedure from a UDF and I know that there are various "reasons" for this (none that make much sense to me though, tbh it just sounds like laziness on Microsoft's part).
I am more interested in how I can design a system to get round this flaw in SQL Server.
This is a quick overview of the system I currently have:
I have a dynamic report generator where users specify data items, operators (=, <, !=, etc.) and filter values. These are used to build up "rules" with one or more filters, e.g. I might have a rule that has two filters "Category < 12" and "Location != 'York'";
there are thousands and thousands of these "rules", some of them have many, many filters;
the output from each of these rules is a statuory report that always has exactly the same "shape", i.e. the same columns/ data types. Basically these reports produce lists of tonnages and materials;
I have a scalar-valued function that generates Dynamic SQL for a specified rule, returning this as a VARCHAR(MAX);
I have a stored procedure that is called to run a specific rule, it calls the UDF to generate the Dynamic SQL, runs this and returns the results (this used to just return the results but now I store the output in process-keyed tables to make the data easier to share and so I return a handle to this data instead);
I have a stored procedure that is called to run all the rules for a particular company, so it makes a list of the rules to run, runs them sequentially and then merges the results together as output.
So this all works perfectly.
Now I want one final thing, a report that runs the company summary and then applies costs to the tonnages/ materials to result in a cost report. This seemed such a simple requirement when I started on this last week :'(
My report has to be a table-valued function for it to work with the report broker system I have already written. If I write it as a stored procedure then it will not be run through my report broker which means that it will not be controlled, i.e. I won't know who ran the report and when.
But I can't call a stored procedure from within a table-valued function and the two obvious ways to handle this are as follows:
Get the SQL to create the output, run it and suck up the results.
--Method #1
WHILE #RuleIndex <= #MaxRuleIndex
BEGIN
DECLARE #DSFId UNIQUEIDENTIFIER;
SELECT #DSFId = [GUID] FROM NewGUID; --this has to be deterministic, it isn't but the compiler thinks it is and that's good enough :D
DECLARE #RuleId UNIQUEIDENTIFIER;
SELECT #RuleId = DSFRuleId FROM #DSFRules WHERE DSFRuleIndex = #RuleIndex;
DECLARE #SQL VARCHAR(MAX);
--Get the SQL
SELECT #SQL = DSF.DSFEngine(#ServiceId, #MemberId, #LocationId, #DSFYear, NULL, NULL, NULL, NULL, #DSFId, #RuleId);
--Run it
EXECUTE(#SQL);
--Copy the data out of the results table into our local copy
INSERT INTO
#DSFResults
SELECT
TableId, TableCode, TableName, RowId, RowName, LocationCode, LocationName, ProductCode, ProductName, PackagingGroupCode, PackagingGroupName, LevelName, WeightSource, Quantity, Paper, Glass, Aluminium, Steel, Plastic, Wood, Other, 0 AS General
FROM
DSF.DSFPackagingResults
WHERE
DSFId = #DSFId
AND RuleId = #RuleId;
SELECT #RuleIndex = #RuleIndex + 1;
END;
Call the report directly
--Method #2
WHILE #RuleIndex <= #MaxRuleIndex
BEGIN
DECLARE #DSFId UNIQUEIDENTIFIER;
SELECT #DSFId = [GUID] FROM NewGUID; --this has to be deterministic, it isn't but the compiler thinks it is :D
DECLARE #RuleId UNIQUEIDENTIFIER;
SELECT #RuleId = DSFRuleId FROM #DSFRules WHERE DSFRuleIndex = #RuleIndex;
DECLARE #SQL VARCHAR(MAX);
--Run the report
EXECUTE ExecuteDSFRule #ServiceId, #MemberId, #LocationId, #DSFYear, NULL, NULL, NULL, #RuleId, #DSFId, 2;
--Copy the data out of the results table into our local copy
INSERT INTO
#DSFResults
SELECT
TableId, TableCode, TableName, RowId, RowName, LocationCode, LocationName, ProductCode, ProductName, PackagingGroupCode, PackagingGroupName, LevelName, WeightSource, Quantity, Paper, Glass, Aluminium, Steel, Plastic, Wood, Other, 0 AS General
FROM
DSF.DSFPackagingResults
WHERE
DSFId = #DSFId
AND RuleId = #RuleId;
SELECT #RuleIndex = #RuleIndex + 1;
END;
I can think of the following workarounds (none of which are particularly satisfactory):
rewrite some of this in CLR (but this is just a whole lot of hassle to break the rules);
use a stored procedure to produce my report (but this means I lose control of the execution unless I develop a new system for this SINGLE report, different to the dozens of existing reports that all work fine);
split execution from reporting, so I have one process to execute the report and another that just picks up the output (but no way to tell when the report has completed without more work);
wait until Microsoft see sense and allow execution of stored procedures from UDFs.
Any other ideas out there?
Edit 3-May-2013, here is a (very) simple example of how this hangs together:
--Data to be reported
CREATE TABLE DataTable (
MemberId INT,
ProductId INT,
ProductSize VARCHAR(50),
Imported INT,
[Weight] NUMERIC(19,2));
INSERT INTO DataTable VALUES (1, 1, 'Large', 0, 5.4);
INSERT INTO DataTable VALUES (1, 2, 'Large', 1, 6.2);
INSERT INTO DataTable VALUES (1, 3, 'Medium', 0, 2.3);
INSERT INTO DataTable VALUES (1, 4, 'Small', 1, 1.9);
INSERT INTO DataTable VALUES (1, 5, 'Small', 0, 0.7);
INSERT INTO DataTable VALUES (1, 6, 'Small', 1, 1.2);
--Report Headers
CREATE TABLE ReportsTable (
ReportHandle INT,
ReportName VARCHAR(50));
INSERT INTO ReportsTable VALUES (1, 'Large Products');
INSERT INTO ReportsTable VALUES (2, 'Imported Small Products');
--Report Detail
CREATE TABLE ReportsDetail (
ReportHandle INT,
ReportDetailHandle INT,
DatabaseColumn VARCHAR(50),
DataType VARCHAR(50),
Operator VARCHAR(3),
FilterValue VARCHAR(50));
INSERT INTO ReportsDetail VALUES (1, 1, 'ProductSize', 'VARCHAR', '=', 'Large');
INSERT INTO ReportsDetail VALUES (2, 1, 'Imported', 'INT', '=', '1');
INSERT INTO ReportsDetail VALUES (2, 1, 'ProductSize', 'VARCHAR', '=', 'Small');
GO
CREATE FUNCTION GenerateReportSQL (
#ReportHandle INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #SQL VARCHAR(MAX);
SELECT #SQL = 'SELECT SUM([Weight]) FROM DataTable WHERE 1=1 ';
DECLARE #Filters TABLE (
FilterIndex INT,
DatabaseColumn VARCHAR(50),
DataType VARCHAR(50),
Operator VARCHAR(3),
FilterValue VARCHAR(50));
INSERT INTO #Filters SELECT ROW_NUMBER() OVER (ORDER BY DatabaseColumn), DatabaseColumn, DataType, Operator, FilterValue FROM ReportsDetail WHERE ReportHandle = #ReportHandle;
DECLARE #FilterIndex INT = NULL;
SELECT TOP 1 #FilterIndex = FilterIndex FROM #Filters;
WHILE #FilterIndex IS NOT NULL
BEGIN
SELECT TOP 1 #SQL = #SQL + ' AND ' + DatabaseColumn + ' ' + Operator + ' ' + CASE WHEN DataType = 'VARCHAR' THEN '''' ELSE '' END + FilterValue + CASE WHEN DataType = 'VARCHAR' THEN '''' ELSE '' END FROM #Filters WHERE FilterIndex = #FilterIndex;
DELETE FROM #Filters WHERE FilterIndex = #FilterIndex;
SELECT #FilterIndex = NULL;
SELECT TOP 1 #FilterIndex = FilterIndex FROM #Filters;
END;
RETURN #SQL;
END;
GO
CREATE PROCEDURE ExecuteReport (
#ReportHandle INT)
AS
BEGIN
--Get the SQL
DECLARE #SQL VARCHAR(MAX);
SELECT #SQL = dbo.GenerateReportSQL(#ReportHandle);
EXECUTE (#SQL);
END;
GO
--Test
EXECUTE ExecuteReport 1;
EXECUTE ExecuteReport 2;
SELECT dbo.GenerateReportSQL(1);
SELECT dbo.GenerateReportSQL(2);
GO
--What I really want
CREATE FUNCTION RunReport (
#ReportHandle INT)
RETURNS #Results TABLE ([Weight] NUMERIC(19,2))
AS
BEGIN
INSERT INTO #Results EXECUTE ExecuteReport #ReportHandle;
RETURN;
END;
--Invalid use of a side-effecting operator 'INSERT EXEC' within a function
If I was in your situation, I wouldn't try to hack anything. I would setup the objects like this:
CREATE TABLE [dbo].[ReportCollection] (
[ReportCollectionID] int,
[ReportID] int
)
CREATE TABLE [dbo].[ReportResult] (
[ReportID] int,
[LocationCode] int,
[LocationName] nvarchar(max)
)
CREATE PROCEDURE [dbo].[usp_ExecuteReport] (
#ReportID int
)
AS
INSERT [dbo].[ReportResult]
SELECT #ReportID, 1, N'StackOverflow'
END
CREATE FUNCTION [dbo].[udf_RetrieveReportCollectionResults] (
#ReportCollectionID int
)
RETURNS #Results TABLE ([ReportID], [LocationCode], [LocationName])
AS
BEGIN
SELECT *
FROM [dbo].[ReportResult] rr
JOIN [dbo].[ReportCollection] rc
ON rr.ReportID = rc.ReportID
WHERE rc.ReportCollectionID = #ReportCollectionID
END
And use them like this:
INSERT [dbo].[ReportCollection] VALUES (1, 1)
INSERT [dbo].[ReportCollection] VALUES (1, 2)
EXEC [dbo].[usp_ExecuteReport] #ReportID = 1
EXEC [dbo].[usp_ExecuteReport] #ReportID = 2
SELECT * FROM [dbo].[udf_RetrieveReportCollectionResults](1)
Each time you run your reports, start a new collection. Your application should kick off all of the reports and consolidate the results afterward.
--
If you really wanted to call a stored procedure from a udf (please don't), do a search on xp_cmdshell.
If you really want this working as a function then the least hacky way would be CLR integration.
You don't have to redo everything - just write a CLR wrapper function that calls the stored procedure & returns the stored procs result set as it's own.1
This way all your current SQL development is untouched.

Got error like Only one expression can be specified in the select list when the subquery is not introduced with EXISTS

i am exporting some date to sql server using string builder.and here i assign all the values to particular tables.but i don't know i got some error.i can't resolve.help me to resolve this.
ALTER PROCEDURE [dbo].[usp_CreateEmployeDetails]
#nEmployeeDetailsInXML nvarchar(max)=''
As
DECLARE #iTree INTEGER
declare #empid int
BEGIN
SET NOCOUNT ON;
create table #TempRelation(RowNo int identity(1,1),RelationShipId int,RelativeName nvarchar(100),DOB date,IsNominee bit)
create table #Temp_Present_Address(RowNo int identity(1,1),Street1 nvarchar(200),Street2 nvarchar(200),CountryId int,StateId int,CityId int,AddressTypeId int)
create table #Temp_Permanent_Address(RowNo int identity(1,1),Street1 nvarchar(200),Street2 nvarchar(200),CountryId int,StateId int,CityId int,AddressTypeId int)
create table #TempDetails(RowNo int identity(1,1),EmployeeName nvarchar(100),DOB date,DOJ date,Email nvarchar(100),Phone bigint,BoodGroup nchar(10),
PAN_No nvarchar(15),PF_No nvarchar(100),Sex char(10),AccountNo nvarchar(100),BankName nvarchar(100),BranchId int,ManagerId int,HrId int,DesigId int)
exec sp_xml_preparedocument #iTree output,#nEmployeeDetailsInXML
insert into #TempRelation(RelationShipId,RelativeName,DOB,IsNominee)
select RelationShipId,RelativeName,DOB,IsNominee
from openxml (#iTree,'EmployeeDetails/EmployeeRelation',1)
with(RelationShipId int,RelativeName nvarchar(100),DOB date,IsNominee bit)
insert into #Temp_Permanent_Address(Street1,Street2,CountryId,StateId,CityId,AddressTypeId)
select Street1,Street2,CountryId,StateId,CityId,AddressTypeId
from openxml (#iTree,'EmployeeDetails/EmployeePermanentAdress',1)
with(Street1 nvarchar(200),Street2 nvarchar(200),CountryId int,StateId int,CityId int,AddressTypeId int)
insert into #Temp_Present_Address(Street1,Street2,CountryId,StateId,CityId,AddressTypeId)
select Street1,Street2,CountryId,StateId,CityId,AddressTypeId
from openxml (#iTree,'EmployeeDetails/EmployeePresentAdress',1)
with(Street1 nvarchar(200),Street2 nvarchar(200),CountryId int,StateId int,CityId int,AddressTypeId int)
insert into #TempDetails(EmployeeName,DOB,DOJ,Email,Phone,BoodGroup,PAN_No,PF_No,Sex,AccountNo,BankName,BranchId,ManagerId,HrId,DesigId)
select EmployeeName,DOB,DOJ,Email,Phone,BoodGroup,PAN_No,PF_No,Sex,AccountNo,BankName,BranchId,ManagerId,HrId,DesigId
from openxml (#iTree,'EmployeeDetails/Employee',1)
with(EmployeeName nvarchar(100),DOB date,DOJ date,Email nvarchar(100),Phone bigint,BoodGroup nchar(10),PAN_No nvarchar(15),PF_No nvarchar(100),Sex char(10),
AccountNo nvarchar(100),BankName nvarchar(100),BranchId int,ManagerId int,HrId int,DesigId int)
if((select COUNT(RowNo) from #TempDetails)>0)
begin
insert into Employee(EmployeeName,DOB,DOJ,Email,Phone,BoodGroup,PAN_No,PF_No,Sex)output inserted.EmployeeId select EmployeeName,DOB,DOJ,Email,Phone,BoodGroup,PAN_No,PF_No,Sex from #TempDetails
set #empid=SCOPE_IDENTITY()
if((select COUNT(EmployeeName) from Employee where EmployeeId=#empid)>0)
begin
insert into EmployeeAccountDetls(EmployeeId,AccountNo,BankName)values(#empid,(select AccountNo,BankName from #TempDetails))
insert into EmployeeLink(EmployeeId,BranchId,ManagerId,HrId,DesigId)values(#empid,(select BranchId,ManagerId,HrId,DesigId from #TempDetails))
if((select COUNT(RowNo) from #Temp_Permanent_Address)>0)
begin
insert into EmployeeAddress(EmployeeId,Street1,Street2,CountryId,StateId,CityId,AddressTypeId)values(#empid,(select Street1,Street2,CountryId,StateId,CityId,AddressTypeId from #Temp_Permanent_Address))
end
if((select COUNT(RowNo) from #Temp_Present_Address)>0)
begin
insert into EmployeeAddress(EmployeeId,Street1,Street2,CountryId,StateId,CityId,AddressTypeId)values(#empid,(select Street1,Street2,CountryId,StateId,CityId,AddressTypeId from #Temp_Present_Address))
end
if((select COUNT(RowNo) from #TempRelation)>0)
begin
insert into EmployeeRelationDetls(EmployeeId,RelationShipId,RelativeName,DOB,isNominee)values(#empid,(select RelationShipId,RelativeName,DOB,IsNominee from #TempRelation))
end
end
end
EXEC sp_xml_removedocument #iTree
drop table #Temp_Present_Address
drop table #Temp_Permanent_Address
drop table #TempDetails
drop table #TempRelation
END
why its happening i checked but i didn't get the result
For the specific error you mention, your problem is here:
insert into EmployeeAddress(EmployeeId,Street1,Street2,CountryId,StateId,CityId,AddressTypeId)
values (#empid, (select Street1,Street2,CountryId,StateId,CityId,AddressTypeId from #Temp_Permanent_Address))
You probably want:
insert into EmployeeAddress(EmployeeId,Street1,Street2,CountryId,StateId,CityId,AddressTypeId)
SELECT #empid,Street1,Street2,CountryId,StateId,CityId,AddressTypeId from #Temp_Permanent_Address
However, you're inserting multiple rows and then giving the last rows IDENTITY value to every address row doing that. You need to find a better way of relating keys - which can either be done using MERGE's output clause or by using a stronger key to reference back to the table you just inserted into in order to find the newly inserted employee ID.

Resources