passing list of name/value pairs to stored procedure - asp.net

I have a name/value pair in a List<T> and needing to find the best way to pass these to a stored procedure.
Id Name
1 abc
2 bbc
3 cnn
....
...
What is the best way to accomplish this?

One way to handle this in SQL Server 2005 (prior to the availability of table valued parameters) was to pass a delimited list and use a Split function. If you are using a two-column array, you would want to use two different delimiters:
Declare #Values varchar(max)
Set #Values = '1,abc|2,bbc|3,cnn'
With SplitItems As
(
Select S.Value As [Key]
, S2.Value
, Row_Number() Over ( Partition By S.Position Order By S2.Position ) As ElementNum
From dbo.Split(#Values,'|') As S
Outer Apply dbo.Split(S.Value, ',') As S2
)
Select [Key]
, Min( Case When S.ElementNum = 1 Then S.Value End ) As ListKey
, Min( Case When S.ElementNum = 2 Then S.Value End ) As ListValue
From SplitItems As S
Group By [Key]
Create Function [dbo].[Split]
(
#DelimitedList nvarchar(max)
, #Delimiter nvarchar(2) = ','
)
RETURNS TABLE
AS
RETURN
(
With CorrectedList As
(
Select Case When Left(#DelimitedList, Len(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
+ #DelimitedList
+ Case When Right(#DelimitedList, Len(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
As List
, Len(#Delimiter) As DelimiterLen
)
, Numbers As
(
Select Row_Number() Over ( Order By c1.object_id ) As Value
From sys.columns As c1
Cross Join sys.columns As c2
)
Select CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen As Position
, Substring (
CL.List
, CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen
, CharIndex(#Delimiter, CL.list, N.Value + 1)
- ( CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen )
) As Value
From CorrectedList As CL
Cross Join Numbers As N
Where N.Value < Len(CL.List)
And Substring(CL.List, N.Value, CL.DelimiterLen) = #Delimiter
)
Another way to handle this without table-valued parameters is to pass Xml as an nvarchar(max):
Declare #Values nvarchar(max)
Set #Values = '<root><Item Key="1" Value="abc"/>
<Item Key="2" Value="bbc"/>
<Item Key="3" Value="cnn"/></root>'
Declare #docHandle int
exec sp_xml_preparedocument #docHandle output, #Values
Select *
From OpenXml(#docHandle, N'/root/Item', 1)
With( [Key] int, Value varchar(10) )

Take a look at Arrays and Lists in SQL Server 2008 to get some ideas
SQL Server 2008 also supports this multi row values syntax
create table #bla (id int, somename varchar(50))
insert #bla values(1,'test1'),(2,'Test2')
select * from #bla

i endup using foreach <insert>

This could done through three ways.
User Defined Table Type
Json Object Parsing
XML Parsing
I tried with the first option and passed a list of pairs in User Defined Table Type. This works for me. I am posting here, it might help someone else.
The first challenge for me was to pass the list of key value pair data structure and second to loop through the list and insert the record in a table.
Step 1 : Create a User Defined Table Type. I have created with a name 'TypeMetadata'. As it is custom type, I created two attributes of type nvarchar. You can create one of type integer and second of type nvarchar.
-- Type: metadata ---
IF EXISTS(SELECT * FROM SYS.TYPES WHERE NAME = 'TypeMetadata')
DROP TYPE TypeMetadata
GO
CREATE TYPE TypeMetadata AS TABLE (
mkey nvarchar (50),
mvalue nvarchar (50)
);
GO
Step 2 : Then I created a stored procedure with name 'createfiled'
-- Procedure: createtext --
CREATE PROCEDURE [dbo].[createfield]
#name nvarchar(50),
#text nvarchar(50),
#order int,
#type nvarchar(50),
#column_id int ,
#tid int,
#metadataList TypeMetadata readonly
AS
BEGIN
--loop through metadata and insert records --
DECLARE #mkey nvarchar(max);
DECLARE #mvalue nvarchar(max);
DECLARE mCursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT mkey, mvalue
FROM #metadataList;
OPEN mCursor;
FETCH NEXT FROM mCursor INTO #mkey, #mvalue; -- Initial fetch attempt
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO template_field_metadata (name, value, template_field_id, isProperty) values (#mkey, #mvalue, 1, 0)
PRINT 'A new metadata created with id : ' + cast(SCOPE_IDENTITY() as nvarchar);
FETCH NEXT FROM mCursor INTO #mkey, #mvalue; -- Attempt to fetch next row from cursor
END;
CLOSE mCursor;
DEALLOCATE mCursor;
END
GO
Step 3: finally I executed the stored procedure like;
DECLARE #metadataToInsert TypeMetadata;
INSERT INTO #metadataToInsert VALUES ('value', 'callVariable2');
INSERT INTO #metadataToInsert VALUES ('maxlength', '30');
DECLARE #fid INT;
EXEC [dbo].[createfield] #name = 'prefagent', #text = 'Pref Agent', #order = 1 , #type= 'prefagent', #column_id = 0, #tid = 49, #metadataList =#metadataToInsert;

Related

display a record which contains a VARRAY column

I have a function which returns a RECORD.
One of the record's columns is VARRAY.
Can someone hint me how to display the RECORD, please? (my problem is related to the VARRAY column.
create or replace TYPE phone_list_typ AS VARRAY(5) OF VARCHAR2(25);
CREATE TABLE "CUSTOMERS"
("CUSTOMER_ID" NUMBER(6,0),
"CUST_FIRST_NAME" VARCHAR2(20 BYTE)
"PHONE_NUMBERS" "OE"."PHONE_LIST_TYP" ,
"CREDIT_LIMIT" NUMBER(9,2),
"CUST_EMAIL" VARCHAR2(40 BYTE));
TYPE r_cust_det IS RECORD( CUSTOMER_ID customers.CUSTOMER_ID%TYPE
, CUST_FIRST_NAME customers.CUST_FIRST_NAME%TYPE
, PHONE_NUMBERS customers.PHONE_NUMBERS%TYPE
, CREDIT_LIMIT customers.CREDIT_LIMIT%TYPE
, CUST_EMAIL customers.CUST_EMAIL%TYPE);
CREATE OR REPLACE FUNCTION show_customer_details (n_customer_id customers.customer_id%TYPE) RETURN r_cust_det
IS
v_return r_cust_det;
BEGIN
SELECT CUSTOMER_ID
, CUST_FIRST_NAME
, PHONE_NUMBERS
, CREDIT_LIMIT
, CUST_EMAIL
INTO v_return
FROM CUSTOMERS
WHERE CUSTOMER_ID = n_customer_id;
RETURN v_return;
END show_customer_details;
This may depend on how you want it to look and what the display medium is (text file, interactive web page etc), but one way might be to list the phone numbers as a comma-separated list.
select customer_id, cust_first_name, credit_limit, cust_email
, listagg(p.column_value,', ') within group (order by p.column_value) as phone_numbers
from customers c cross join table(c.phone_numbers) p
group by customer_id, cust_first_name, credit_limit, cust_email
order by customer_id;
I'm not sure what you expect out of your show_customer_details function, though.
(btw it's not a good idea to enclose identifiers in double-quotes unless you absolutely have to.)
CREATE OR REPLACE FUNCTION show_customer_details (n_customer_id customers.customer_id%TYPE) RETURN t_cust_det PIPELINED
IS
v_return t_cust_det;
BEGIN
SELECT t1.CUSTOMER_ID
, t1.CUST_FIRST_NAME
, t2.*
, t1.CREDIT_LIMIT
, t1.CUST_EMAIL
BULK COLLECT INTO v_return
FROM CUSTOMERS t1, table(t1.phone_numbers) t2
WHERE t1.CUSTOMER_ID = n_customer_id
AND column_value is not null;
FOR i IN 1 .. v_return.count
LOOP
PIPE ROW (v_return(i));
END LOOP;
END show_customer_details;
the function call is:
select * from table(SHOW_DETAILS.SHOW_CUSTOMER_DETAILS(101));
Another solution I found, without using PIPELINED is:
Define a object type
create or replace type customers_typ
is object
( CUSTOMER_ID number(6)
, CUST_FIRST_NAME varchar2(20)
, PHONE_NUMBERS varchar2(25) --phone_list_typ
, CREDIT_LIMIT number(9, 2)
, CUST_EMAIL varchar2(40)
);
Define a new type, table of previously defined object.
create or replace type t_customers_typ is table of customers_typ;
The function become
CREATE OR REPLACE FUNCTION show_customer_details (n_customer_id customers.customer_id%TYPE) RETURN t_customers_typ
IS
v_return t_customers_typ;
BEGIN
SELECT customers_typ(t1.CUSTOMER_ID
, t1.CUST_FIRST_NAME
, t2.column_value
, t1.CREDIT_LIMIT
, t1.CUST_EMAIL)
BULK COLLECT INTO v_return
FROM CUSTOMERS t1, table(t1.phone_numbers) t2
WHERE t1.CUSTOMER_ID = n_customer_id
AND t2.column_value is not null;
return v_return;
END show_customer_details;
The function is called as the same:
select * from table(SHOW_DETAILS.SHOW_CUSTOMER_DETAILS(101));

Change phone number format

I have phone number field in database. It has already data.
I want to change my phone number format to "XXX-XXX-XXXX"
Current database has no any phone format.
So there may be garbage data. I have already applied validation for new records but now I want to change my existing data also.
Is there any specific way through that I can change my existing data. And make all phone numbers to follow this format.
Please advice.
Create function to remove the non-numeric data and do the formatting
CREATE FUNCTION [UDF_STRIP_NONNUMERIC_DATA](#str VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
WHILE Patindex('%[^0-9]%', #str) > 0
BEGIN
SET #str = Stuff(#str, Patindex('%[^0-9]%', #str), 1, '')
END
RETURN #str
END
You can use STUFF function to inset the - between phone number
Select left(Stuff(Stuff(dbo.[UDF_STRIP_NONNUMERIC_DATA](Phone),4,0,'-'),8,0,'-'),12)
From yourtable
If you are using SQL SERVER 2012+ use can use FORMAT function (thanks to LukStorms, who mentioned it in comment)
SELECT Format(Cast(dbo.[Udf_strip_nonnumeric_data](Phone) AS BIGINT), '###-###-####')
FROM yourtable
To update
Update yourtable
SET phone = left(Stuff(Stuff(dbo.[UDF_STRIP_NONNUMERIC_DATA](Phone),4,0,'-'),8,0,'-'),12)
Demo
declare #str varchar(100)= '9225-123-4567'
select left(Stuff(Stuff(dbo.[UDF_STRIP_NONNUMERIC_DATA](#str),4,0,'-'),8,0,'-'),12)
Result : 922-512-3456
declare #phone varchar(24)
set #phone = '(334)789-4532'
--set #phone = '314789-4532'
--set #phone = '3457894532'
--set #phone = '534-789-4532'
SELECT
LEFT(N,3) + '-' + SUBSTRING(N,4,3) + '-' + RIGHT(N,4)
FROM
(SELECT CAST(CAST((
SELECT SUBSTRING(#phone, Number, 1)
FROM master..spt_values
WHERE Type='p' AND Number <= LEN(#phone) AND
SUBSTRING(#phone, Number, 1) LIKE '[0-9]' FOR XML Path(''))
AS xml) AS varchar(MAX)) as N) as N
Ok, to replace all non-numeric characters, look at this.
Here is a sample script (copied from that link) to show you how it works (You'll need to modify this to fit your table name and column names:
-- Step 1: creates table to use to hold every char in every phone number
if object_id('dbo.tally') is not null drop table dbo.tally
select top 10000 --change to fit max length of phone number
identity(int,1,1) as n
into dbo.tally
from master.dbo.syscolumns sc1,
master.dbo.syscolumns sc2
-- add pk to maximize performance
alter table dbo.tally
add constraint pk_tally_n
primary key clustered (n) with fillfactor = 100
-- Step 2: Create temporary table holding three bad phone numbers
declare #phonetable table
(uniqueid int identity(1,1),
phone_number varchar(500))
insert into #phonetable (phone_number)
select '01234-567-890' union
select '012345 6789ext' union
select 'n/a' union select '...12345.....';
-- Step 3: identify, for every character, whether it is a number or not,
and remove the non-numeric ones
with cte (uniqueid, phone_number, goodchar, badchar) as
( select uniqueid, phone_number,
case when substring(phone_number,N,1) not like '%[^0-9]%'
then substring(phone_number,N,1) end as goodchar,
case when substring(phone_number,N,1) like '%[^0-9]%'
then substring(phone_number,N,1) end as badchar
from #phonetable , Tally
where phone_number like '%[^0-9]%' and N <= len(phone_number) )
select distinct phone_number,
isnull( stuff (
( SELECT '' + goodchar
FROM cte t1
where t1.UniqueID = t2.UniqueID
FOR XML PATH ( '' ) ) , 1 , 0 , '' ) ,'')
as clean_phone_number from cte t2
to display the numbers with formatting, just extract the appropriate pieces and re-concatenate them with the dashes.
Select case len(phone)
When 10 then left(phone, 3) + '-' +
substring(phone, 4,3) + '-' +
substring(phone, 7,4)`
When 7 then left(phone, 3) + '-' +
substring(phone, 4,4)
Else '' end
To create a computed column
Alter table Add Column FormattedPhone as
case len(phone)
When 10 then left(phone, 3) + '-' +
substring(phone, 4,3) + '-' +
substring(phone, 7,4)`
When 7 then left(phone, 3) + '-' +
substring(phone, 4,4)
Else '' end
If you don't mind a UDF
Select [dbo].[udf-Str-Format-Phone]('334)789-4532')
Returns
334-789-4532
The UDF
CREATE FUNCTION [dbo].[udf-Str-Format-Phone] (#S varchar(max))
Returns varchar(25)
AS
Begin
Declare #Return varchar(25)
;with cte0(N) As (Select 1 From (Values(1),(1),(1),(1),(1)) N(N))
, cteN(N) As (Select Top (Len(#S)) Row_Number() over (Order By (Select NULL)) From cte0 N1, cte0 N2)
, cteS(S) As (Select Substring(#S,N,1) From cteN Where Substring(#S, N, 1) LIKE '[0-9]' FOR XML Path(''))
Select #Return = IIf(Len(S)>=10,Stuff(stuff(S,4,0,'-'),8,0,'-'),Stuff(S,4,0,'-')) From cteS
Return #Return
End
-- Syntax : Select [dbo].[udf-Str-Format-Phone]('(334)789-4532') -- Returns 334-789-4532
-- Syntax : Select [dbo].[udf-Str-Format-Phone]('Phone:7894532') -- Returns 789-4532

Simple Split function in SQL Server 2012 with explanation pls

I have two tables Procedures and ProcedureTypes.
Procedures has a column Type which is a varchar with the values (1, 2), (3, 4), (4, 5) etc...
ProcedureType has a primary key 'ID' 1 to 9.
ID Description
1 Drug
2 Other-Drug
etc...
ID is an integer value and Type is varchar value.
Now I need to join these two tables to show the values
ID in the Procedures table
ProcedureType in the Procedures table
Description in the ProceduresType table with the value separated by a "-".
For example if he value in Type is (1,2) the new table after join should show values in the description like (Drug-Other Drug)
I have used this query bot to no avail
SELECT * FROM dbo.[Split]((select RequestType from GPsProcedures), ',')
Can anyone tell me how to do it and why the above query is not working
with Procedures as (
select 1 as ID, '1,2,3' as Typ
),
ProcedureTypes as (
select 1 as TypeID, 'Drug' as Name
union select 2 , 'Other-Drug'
union select 3 , 'Test 3'
)
/*Get one extra column of type xml*/
,Procedures_xml as (
select id,CONVERT(xml,' <root> <s>' + REPLACE(Typ,',','</s> <s>') + '</s> </root> ') as Typ_xml
from Procedures
)
/*Convert the field string to multiple rows then join to procedure types*/
, Procdure_With_Type as (
select ID,T.c.value('.','varchar(20)') as TypeID,
ProcedureTypes.Name
from Procedures_xml
CROSS APPLY Typ_xml.nodes('/root/s') T(c)
INNER JOIN ProcedureTypes ON T.c.value('.','varchar(20)') = ProcedureTypes.TypeID
)
/*Finally, group the procedures type names by procedure id*/
select id,
STUFF((
SELECT ', ' + [Name]
FROM Procdure_With_Type inn
WHERE (Procdure_With_Type.ID = inn.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
from Procdure_With_Type
group by ID
You can't have a select statement as a parameter for a function, so instead of this:
SELECT * FROM dbo.[Split]((select RequestType from GPsProcedures), ',')
Use this:
select S.*
from GPsProcedures P
cross apply dbo.[Split](P.RequestType, ',') S

refer to stored proc output param in macro?

Is there a way to refer to an output parameter of a stored procedure in a macro?
My stored procedure is:
CREATE PROCEDURE db.ssis_load_nextID
(IN tbl VARCHAR(30), OUT nextID SMALLINT )
BEGIN
DECLARE maxID SMALLINT;
SELECT MAX(loadID) INTO maxID
FROM db.SSIS_Load
WHERE TABLENAME = tbl
GROUP BY TABLENAME;
IF maxID IS NULL THEN
SET nextID = 1;
ELSE
SET nextID = maxID + 1;
END if;
END;
I want to refer to this result in a macro like:
CREATE MACRO db.tbSTG_m AS (
INSERT INTO db.tbProd (ID1, ID2, f1, f2, ..., fn, loadID)
SELECT ID1, ID2, f1, f2,..., fn,
CALL db.ssis_Load_nextID('tbProd',nextID)
FROM db.tbstg
; );
because running CALL db.ssis_Load_nextID('tbProd',nextID) returns the result I want in the first (only) row of the first (only) column.
I tried storing the result in a variable in the macro, but apparently, that's unsupported.
Also, I'd like to start with an empty SSIS_load table, so it creates the first row when the first table is loaded, instead of pre-populating the load table before the automated load process starts.
All help appreciated,
-Beth
fyi, We got it to work by removing the 'group by tablename' clause and embedding the sp in the macro:
CREATE MACRO db.tbSTG_m AS (
INSERT INTO db.tbProd
SELECT ID1, ID2, f1, f2, ..., fn (
SELECT ZEROIFNULL(MAX(loadID))+1
FROM db.ssis_load
WHERE TABLENAME = 'tbStg') mx
FROM db.tbSTG;
);
You can't use a stored proc for that (you would have to use a UDF not a procedure)
however you can do it in your macro
syntax may not be 100% correct.. working from memory but should get you close
I am assuming tbl is a parameter passed in correct?
basically you join to the id table and use that in your insert...
then you update the id table with the maximum freshly inserted ids
CREATE MACRO db.tbSTG_m AS (
INSERT INTO db.tbProd (ID1, ID2, f1, f2, ..., fn, loadID)
SELECT ID1, ID2, f1, f2,..., fn, MAXloadID + SUM(1) OVER(ROWS UNBOUNDED PRECEDING)
FROM db.tbstg
cross join (SELECT MAX(loadID) as MAXloadID
FROM db.SSIS_Load
WHERE TABLENAME = tbl
GROUP BY TABLENAME) as IDGEN
;
update db.SSIS_Load from (select MAX(loadID) as MAXloadID from tbl) as upid
set loadID = upid.MAXloadID
where db.SSIS_Load.TABLENAME = tbl
);

SQL Stored Procedure run two selects and return a value

How would I go about modifying stored procedure to run two SQL statements
CREATE procedure [dbo].[hms_GetEmployeeSalaryRecordsByContractId]
(
#Id int
)
as
SELECT c.*
FROM contract c
where c.emp_no = #Id AND c.leave_date='1900-01-01 00:00:00.000' and c.main_contract=1
select * from salary s where s.contract_id = firstquery.contract_id
The above could find two salary records based on one contract
If multiple salaries are found, then I need to do a sum(s.salary) then return that as a decimal value in the stored procedure.
I have done this so far now
USE [pamsv83x]
GO
/****** Object: StoredProcedure [dbo].[hms_GetEmployeeSalaryRecordsByContractId] Script Date: 08/29/2013 10:45:05 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Create procedure [dbo].[hms_GetEmployeeSalaryRecordsByContractEmpNo]
(
#Id int,
#sallaryresult decimal(8,2) OUTPUT
)
as
DECLARE #contract_id int
DECLARE #totalsallary decimal(8,2)
set #contract_id=(SELECT c.contract_id
FROM contract c
where c.emp_no = #Id AND c.leave_date='1900-01-01 00:00:00.000' and c.main_contract=1)
SELECT *,SUM(salary)
from salary s
where s.emp_no=#contract_id
return
Amend 2-----
They can have multple contracts but can have multple sallarys
Create procedure [dbo].[hms_GetEmployeeSalaryRecordsByContractEmpNo]
(
#Id int,
#sallaryresult decimal(8,2) OUTPUT
)
as
DECLARE #contract_id int
DECLARE #totalsallary decimal(8,2)
set #contract_id=(SELECT c.contract_id
FROM contract c
where c.emp_no = #Id AND c.leave_date='1900-01-01 00:00:00.000')
SELECT *,SUM(salary + old_salary)
from salary s
where s.contract_id=#contract_id
return
A join of salary to contact would be the better way of doing this. Something like:
CREATE procedure [dbo].[hms_GetEmployeeSalaryRecordsByContractId]
(
#Id int
)
as
SELECT
c.contract_id
,SUM(s.salary + s.old_salary)
FROM
contract c
INNER JOIN
salary s
ON c.contract_id = s.contract_id
WHERE
c.emp_no = #Id
AND c.leave_date = '1900-01-01 00:00:00.000'
AND c.main_contract = 1
GROUP BY
c.contract_id
The above would accept an employee id and return an aggregated salary value by contact_id.

Resources