Suppose that I have table that holds some data for individuals and companies, and I want to retrieve default value if no data found for individuals or companies, for example, suppose that I have a table
CustomerAccountId CustomerAccountType DueDays IsAdjustable isDefaultForIndividual isDefaultForCompany
1 Individual 10 true false false
2 Company 20 false false false
null null 5 false true false
null null 30 true false true
I want to create a function that takes two parameters IndividualCustomerAccountId and CompanyCustomerAccountId if IndividualCustomerAccountId found in the table retrieve it, if not found retrieve the default value for individuals which is the third row in this case , the same for companies, if theCompanyCustomerAccountIdis found retrieve it, if not get the default value for the companies which isfourth row` in this case.
suppose that we created a funtion which accepts IndividualCustomerAccountId as the first parameter and CompanyCustomerAccountId as the second parameter
sample input
MyFunc(1 , 2) should return first and second rows
MyFunc(1 , 50) should return first and fourth rows because no company with CustomerAccountId 50 found in the table so retrieve the default value for companies which is the fourth row
MyFunc(100 , 2) should return second and third rows because no individual with customer account Id 100 is found so we get the default value for individuals which is the third row, and we have a company with customerAccountId 2, so we simply can retrive it from the table.
I want to create either a LINQ query or SQL function to achieve these results
You could try SQL function
CREATE TABLE Customer
(
CustomerAccountId int,
CustomerAccountType varchar(20),
DueDays int,
IsAdjustable bit,
isDefaultForIndividual bit,
isDefaultForCompany bit
)
INSERT INTO Customer VALUES
(1, 'Individual', 10, 1,0,0),
(2, 'Company', 20, 0,0,0),
(null, null, 5, 0,1,0),
(null, null, 30, 1,0,1)
GO
CREATE FUNCTION MyFunc
(
#IndividualCustomerAccountId int,
#CompanyCustomerAccountId int
)
RETURNs #result TABLE
(
CustomerAccountId int,
CustomerAccountType varchar(20),
DueDays int,
IsAdjustable bit,
isDefaultForIndividual bit,
isDefaultForCompany bit
)
BEGIN
INSERT INTO #result
SELECT CustomerAccountId , CustomerAccountType, DueDays, IsAdjustable, isDefaultForIndividual,isDefaultForCompany
FROM Customer c
WHERE (CustomerAccountId = #IndividualCustomerAccountId AND CustomerAccountType = 'Individual')
OR (CustomerAccountId = #CompanyCustomerAccountId AND CustomerAccountType = 'Company')
IF(NOT EXISTS (SELECT 1 FROM Customer c
WHERE CustomerAccountId = #IndividualCustomerAccountId AND CustomerAccountType = 'Individual' ))
BEGIN
INSERT INTO #result
SELECT CustomerAccountId , CustomerAccountType, DueDays, IsAdjustable, isDefaultForIndividual,isDefaultForCompany
FROM Customer c
WHERE CustomerAccountId IS NULL AND isDefaultForIndividual = 1
END
IF(NOT EXISTS (SELECT 1 FROM Customer c
WHERE CustomerAccountId = #CompanyCustomerAccountId AND CustomerAccountType = 'Company' ))
BEGIN
INSERT INTO #result
SELECT CustomerAccountId , CustomerAccountType, DueDays, IsAdjustable, isDefaultForIndividual,isDefaultForCompany
FROM Customer c
WHERE CustomerAccountId IS NULL AND isDefaultForCompany = 1
END
RETURN;
END
GO
SELECT * from dbo.MyFunc(1,2)
SELECT * from dbo.MyFunc(1,50)
SELECT * from dbo.MyFunc(100,2)
SELECT * from dbo.MyFunc(100,50)
--DROP TABLE Customer
Demo link: Rextester
Basically, you can run an IF Exists with your query to see if there is going to be any data. If so, go ahead and run your query. If not, do a select default row.
If Exists (your query)
(your query)
Else
SELECT 'query for default row'
I hope its clear your problem.
Happy Coding.Thanks
Related
I have to iterate a array with for loop to find it contains specific words in it and add that in listbox
String[] result= ["vicky","vinay#","google#","hello"]
For (l=0 ; l<= result.length; l++)
{
If(result[l].contains("#")
{
Listbox.items.add(result[l]);
}
}
What this does is it gets only first found value I am not getting second value?
You need an ordering column for your data. Let me assume that you have one.
First add the new column:
alter table t add column id int;
Note: id is a really bad name for a column that can be null. Then:
with toupdate as (
select t.*,
row_number() over (partition by col1 order by <ordering col>) as seqnum
from t
)
update toupdate
set id = (case when col1 = 1 then seqnum end);
Strictly speaking, you don't need to update the values when col1 = 0, because the default value is NULL. However, in case you want a different value there, I am leaving out the where col1 = 1.
You can simulate a partial identity column, but you won't be able to incorporate an actual IDENTITY column to the table that works conditionally.
If you just need to update a new column with an incremental value, you can just use a ROW_NUMBER() over a filtered SELECT:
;WITH CTE AS
(
SELECT
T.Col1,
T.ID,
GeneratedID = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -- Determine your order here
FROM
YourTable AS T
WHERE
T.Col1 IS NOT NULL
)
UPDATE C SET
ID = C.GeneratedID
FROM
CTE AS C
Your query should be like below :
update <table_name> set id=1 where Col1=1;
update <table_name> set id=null where Col1=0;
I have a stored procedure which looks like following:
alter procedure [dbo].[zsp_deleteEndedItems]
(
#ItemIDList nvarchar(max)
)
as
delete from
SearchedUserItems
WHERE EXISTS (SELECT 1 FROM dbo.SplitStringProduction(#ItemIDList,',') S1 WHERE ItemID=S1.val)
The parameter IDList is passed like following:
124125125,125125125...etc etc
And the split string function look like following:
ALTER FUNCTION [dbo].[SplitStringProduction]
(
#string nvarchar(max),
#delimiter nvarchar(5)
) RETURNS #t TABLE
(
val nvarchar(500)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#string,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(500)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
This is supposed to delete all items from table "SearcheduserItems" under the IDs:
124125125 and 125125125
But for some reason after I do a select to check it out:
select * from SearchedUserItems
where itemid in('124125125','125125125')
The records are still there...
What am I doing wrong here? Can someone help me out?
As mentioned in the comments, a different option would be to use a table type parameter. This makes a couple of assumptions (some commented), however, should get you on the right path:
CREATE TYPE dbo.IDList AS TABLE (ItemID int NOT NULL); --Assumed int datatype;
GO
ALTER PROC dbo.zsp_deleteEndedItems #ItemIDList dbo.IDList READONLY AS
DELETE SUI
FROM dbo.SearchedUserItems SUI
JOIN #ItemIDList IDL ON SUI.ItemID = IDL.ItemID;
GO
--Example of usage
DECLARE #ItemList dbo.IDList;
INSERT INTO #ItemList
VALUES(123456),(123457),(123458);
EXEC dbo.zsp_deleteEndedItems #ItemList;
GO
In regards to the question of an inline table value function, one such example is the below, which I quickly wrote up, that provides a tally table of the next 1000 numbers:
CREATE FUNCTION dbo.NextThousand (#Start int)
RETURNS TABLE
AS RETURN
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 + #Start AS I
FROM N N1 --10
CROSS JOIN N N2 --100
CROSS JOIN N N3; --1,000
GO
The important thing about an iTVF is that it has only one statement, and that is the RETURN statement. Declaring the table as a return type variable, inserting data into it, and returning that variable turns it into a multi-line TVF; which perform far slower.
I'm new to SQL Server Management Studio, let's say I have 3 columns in my tbl_product, column1 (nchar(60)), column2 (nchar(60)), and in my column3 I want its value to be the result of (column1 * column2), how can I do that?
I saw a "Formula" in the column properties, but I'm not sure how to set a formula there, here is the screenshot:
Note: let's just set the column1 & column2's datatype to nchar.
Presuming both columns are strings that represent decimals set your Formula to this:
CONVERT(decimal,column1) * CONVERT(decimal,column2)
To change how the column is displayed simply change your SELECT statements. The column data will always be "stored" as accurate as possible.
Try running this:
SELECT CAST(column3 as decimal(10,2)) As ComputedColumn FROM tbl_product
In the above statement the cast means "show me 10 digits in total, and 2 digits after the decimal point". You can change those numbers to whatever you'd like.
You can sum two columns using transact sql while defining your table.
CREATE TABLE dbo.Products
(
ProductID int IDENTITY (1,1) NOT NULL
, QtyAvailable smallint
, UnitPrice money
, InventoryValue AS QtyAvailable * UnitPrice
);
-- Insert values into the table.
INSERT INTO dbo.Products (QtyAvailable, UnitPrice)
VALUES (25, 2.00), (10, 1.5);
-- Display the rows in the table.
SELECT ProductID, QtyAvailable, UnitPrice, InventoryValue
FROM dbo.Products;
Microsoft Docs link for Specify Computed Columns in a Table
CREATE TABLE [dbo].[Test]
( [Test1] [nchar(10)] NULL,
[Test2] [nchar(10)] NULL,
[Total] AS (cast ([a] as int)+ cast( [b] as int))
) ON [PRIMARY]
GO
INSERT INTO dbo.Test ( test1, test2 ) VALUES
( '1', -- test1 - int
'2' -- test2 - int
)
SELECT * FROM dbo.Test
Results:
Test1 test2 total
1 2 3
I'm currently doing a banking website for my project, using C# and asp.net. One of the function is 'Remaining Daily Limit'. I need the database value to change back to default once I changed the system date to another day. Example : If a user had $500 (default value) as a daily limit, and he used all up. The next day, he will have $500 again. May I know how should I go about it?
Here is a SQL script that will create how I would setup your db with some sample data:
CREATE TABLE tblBankCustomer
(
BankCustomerId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, FirstName NVARCHAR(100) NOT NULL
, LastName NVARCHAR(100) NOT NULL
, DailySpendingLimit DECIMAL(38, 2) NOT NULL CONSTRAINT DF_tblBankCustomer_DailySpendingLimit DEFAULT(500)
)
CREATE TABLE tblTransactionType
(
TransactionTypeId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, TransactionType VARCHAR(50) NOT NULL
)
INSERT tblTransactionType (TransactionType)
VALUES ('Deposit')
, ('Withdrawal')
CREATE TABLE tblTransaction
(
TransactionId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, BankCustomerId INT NOT NULL
, TransactionDate DATE NOT NULL
, Amount DECIMAL(38, 2) NOT NULL
, TransactionTypeId INT NOT NULL
)
ALTER TABLE tblTransaction
ADD CONSTRAINT FX_tblTransaction_tblBankCustomer
FOREIGN KEY (BankCustomerId)
REFERENCES tblBankCustomer(BankCustomerId)
ALTER TABLE tblTransaction
ADD CONSTRAINT FX_tblTransaction_tblTransactionType
FOREIGN KEY (TransactionTypeId)
REFERENCES tblTransactionType(TransactionTypeId)
INSERT tblBankCustomer
(
FirstName
, LastName
)
VALUES ('Jeremy', 'Pridemore')
, ('K', 'YQ')
INSERT tblTransaction
(
BankCustomerId
, TransactionDate
, Amount
, TransactionTypeId
)
VALUES
(1, CURRENT_TIMESTAMP, 48.50, 2) -- Jeremy, Today, $48.50, Withdrawal
, (1, CURRENT_TIMESTAMP, 300.00, 2) -- Jeremy, Today, $300, Withdrawal
, (1, CURRENT_TIMESTAMP, -200.00, 1) -- Jeremy, Today, $200, Deposit
, (2, CURRENT_TIMESTAMP, 285.00, 2) -- K, Today, $285, Withdrawal
, (2, CURRENT_TIMESTAMP, 215.00, 2) -- K, Today, $215, Withdrawal
GO
CREATE FUNCTION fGetRemainingSpendingLimit
(
#BankCustomerId INT
, #Date DATE
)
RETURNS DECIMAL(38, 2)
BEGIN
SET #Date = ISNULL(#Date, CURRENT_TIMESTAMP)
DECLARE #RemainingLimit DECIMAL(38, 2) =
(SELECT
SUM([Transaction].Amount)
FROM tblBankCustomer Customer
INNER JOIN tblTransaction [Transaction]
ON [Transaction].BankCustomerId = Customer.BankCustomerId
AND [Transaction].TransactionDate = #Date
INNER JOIN tblTransactionType TransactionType
ON TransactionType.TransactionTypeId = [Transaction].TransactionTypeId
AND TransactionType.TransactionType = 'Withdrawal'
WHERE Customer.BankCustomerId = #BankCustomerId)
RETURN #RemainingLimit
END
GO
-- Some sample selects
SELECT dbo.fGetRemainingSpendingLimit(1, NULL)
SELECT dbo.fGetRemainingSpendingLimit(2, NULL)
Then in C# you should know the customer's ID for the customer you're working with. If you're using somethiing like ADO.NET you can call this function directly and use the value in code.
Default values are usually stay in some config file. In your specific case, I would say that you can
or have a table with default values
or in the tables where is possible to have restore functionality have default column with corresponding values.
after, can restore them using some stored procedure.
You can create SQL job and schedule it to run every midnight, within the job you can execute a stored procedure reseting values to $500.
In my opinion, based on that there may be other requirements in the future, I'd have a table that looks like:
UserID AppliedOn Limit
1 1/1/2012 500
1 2/1/2012 750
This give you a historic view of limits, that you can give to the user or data-mine. In the same way, that is how I would apply current daily limits.
UserID AppliedOn Withdrawn
1 1/10/2012 125
1 1/10/2012 225
Now on 1/1/2012 it would be easy to determine what the amount left in the limit is without any jobs or triggers. And again you'd have historic values that can be data-mined for other features.
SELECT
ul.Limit - uw.Sum as LimitLeft
FROM
UserLimit ul
INNER JOIN (
SELECT
UserID,
AppliedOn,
SUM(Limit) as Sum
FROM
UserLimit
Group by
UserID,
AppliedOn) uw on ul.UserID = uw.UserID
and ul.AppliedOn = uw.AppliedOn
WHERE
ul.UserID = #userID
AND ul.AppliedOn = #dateInQuestion
(my raw SQL skills might be a bit rusty due to Entity Framework here).
I used the ANTS profiler to identify the remaining bottleneck in my C# application: the SQL Server stored procedure. I am using SQL Server 2008. Can anybody here help me increase performance, or give me pointers as to what I can do to make it better or more performant?
First, here's the procedure:
PROCEDURE [dbo].[readerSimilarity]
-- Add the parameters for the stored procedure here
#id int,
#type int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
IF (#type=1) --by Article
SELECT id1, id2, similarity_byArticle FROM similarity WHERE (id1 = #id OR id2 = #id)
AND similarity_byArticle != 0
ELSE IF (#type=2) --by Parent
SELECT id1, id2, similarity_byParent FROM similarity WHERE (id1 = #id OR id2 = #id)
AND similarity_byParent != 0
ELSE IF (#type=3) --by Child
SELECT id1, id2, similarity_byChild FROM similarity WHERE (id1 = #id OR id2 = #id)
AND similarity_byChild != 0
ELSE IF (#type=4) --combined
SELECT id1, id2, similarity_combined FROM similarity WHERE (id1 = #id OR id2 = #id)
AND similarity_combined != 0
END
The table 'similarity' consists of two ids (id1 and id2) and a number of columns that store double values. The constraint is that id1 < id2.
Column Data
----- ----
ID1 PK, Indexed
ID2 PK, Indexed
The table contains 28.5 million entries.
Stored Procedure Background
The job of the stored procedure is to get all the rows that have the parameter id in either id1 or id2. Additionally, the column specified by the type-parameter cannot be zero.
The stored procedure is called multiple times for different ids. Although only taking ~1.6 ms per call, it sums up, when calling it 17,000 times.
The processor is running at only 25%, which seems to be because the application is waiting for the procedure call to return.
Do you see any way to speed things up?
Calling the Stored Procedure C# Code Snippet
private HashSet<NodeClustering> AddNeighbourNodes(int id)
{
HashSet<NodeClustering> resultSet = new HashSet<NodeClustering>();
HashSet<nodeConnection> simSet = _graphDataLoader.LoadEdgesOfNode(id);
foreach (nodeConnection s in simSet)
{
int connectedId = s.id1;
if (connectedId == id)
connectedId = s.id2;
// if the corresponding node doesn't exist yet, add it to the graph
if (!_setNodes.ContainsKey(connectedId))
{
NodeClustering nodeToAdd = CreateNode(connectedId);
GraphAddOuter(nodeToAdd);
ChangeWeightIntoCluster(nodeToAdd.id, s.weight);
_bFlowOuter += s.weight;
resultSet.Add(nodeToAdd);
}
}
// the nodes in the result set have been added
to the outernodes -> add to the outernodes count
_setNodes[id].countEdges2Outside += resultSet.Count;
return resultSet;
}
C# Code Background Information
This method is called each time a new id is added to the cluster. It gets all the connected nodes of that id (they are connected, when there is an entry in the db with id1=id or id2=id) via
_graphDataLoader.LoadEdgesOfNode(id);
Then it checks all the connected ids and if they are not loaded yet:
if (!_setNodes.ContainsKey(connectedId))
It Loads them:
CreateNode(connectedId);
The Method:
_graphDataLoader.LoadEdgesOfNode(id);
is called again, this time with the connectedId.
I need this to get all the connections of the new nodes with those nodes that are already in the set.
I probably could collect the ids of all nodes i need to add and call my stored procedure only once with a list of the ids.
Ideas
I could probably load the connected ids connection at once via something like
SELECT id1, id2, similarity_byArticle FROM similarity WHERE
(id1 = #id OR id2 = #id OR
id1 IN (SELECT id1 FROM similarity WHERE id2 = #id) OR
id2 IN (SELECT id1 FROM similarity WHERE id2 = #id) OR
id1 IN (SELECT id2 FROM similarity WHERE id1 = #id) OR
id2 IN (SELECT id2 FROM similarity WHERE id1 = #id))
AND similarity_byArticle != 0
but then I would get more entries than I'd need, because I would get them for already loaded nodes too (which from my tests would make up around 75% of the call).
Questions
How can I speed up the Stored Procedure?
Can I do it differently, is there a more performant way?
Can I use a List<int> as a SP-Parameter?
Any other thoughts?
If it runs that quickly, your problem is probably in the sheer number of repeated calls to the procedure. Is there a way that you could modify the stored procedure and code to return all the results the app needs in a single call?
Optimizing a query that runs in less than 2ms is probably not a fruitful effort. I doubt you will be able to shave more than fractions of a millisecond with query tweaks.
I'd try to change the application to only call this one time per ID, but if that is not possible, try this (make sure that there is an index on similarity.id1 and another index on similarity.id2):
PROCEDURE [dbo].[readerSimilarity]
-- Add the parameters for the stored procedure here
#id int,
#type int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
IF #type=1 --by Article
BEGIN
SELECT
id1, id2,similarity_byArticle
FROM similarity
WHERE id1 = #id AND similarity_byArticle!=0
UNION
SELECT
id1, id2,similarity_byArticle
FROM similarity
WHERE id2 = #id AND similarity_byArticle!=0
END
ELSE IF #type=2 --by Parent
BEGIN
SELECT
id1, id2,similarity_byParent
FROM similarity
WHERE id1 = #id AND similarity_byParent!=0
UNION
SELECT
id1, id2,similarity_byParent
FROM similarity
WHERE id2 = #id AND similarity_byParent!=0
END
ELSE IF #type=3 --by Child
BEGIN
SELECT
id1, id2,similarity_byChild
FROM similarity
WHERE id1 = #id AND similarity_byChild!=0
UNION
SELECT
id1, id2,similarity_byChild
FROM similarity
WHERE id2 = #id AND similarity_byChild!=0
END
ELSE IF #type=4 --combined
BEGIN
SELECT
id1, id2,similarity_combined
FROM similarity
WHERE id1 = #id AND similarity_combined!=0
UNION
SELECT
id1, id2,similarity_combined
FROM similarity
WHERE id2 = #id AND similarity_combined!=0
END
END
GO
EDIT based on OP's latest comment:
The whole graph is stored in the
MSSQL-Database and I load it
successively with the procedure into
some Dictionary structures
You need to redesign your load process. You should call the database just one time to load all of this data. Since the IDs are already in a Database table, you can use a join in this query to get the proper IDs from the other table. edit your question with the table schema that contain the IDs to graph, and how they relate to the already posted code. Once you get a single query to return all the data, it will be much faster that 17,000 calls for a single row each time.
Pass all the ids into the stored proc at once, using a delimited list (Use a comma or a slash or whatever, I use a pipe character [ | ]..
Add the User defined function (UDF) listed below to your database. It will convert a delimited list into a table which you can join to your similarity table. Then in your actual stored proc, you can write...
Create Procedure GetSimilarityIDs
#IdValues Text -- #IdValues is pipe-delimited [|] list of Id Values
As
Set NoCount On
Declare #IDs Table
(rowNum Integer Primary Key Identity Not Null,
Id Integer Not Null)
Insert Into #IDs(Id)
Select Cast(sVal As Integer)
From dbo.ParseString(#IdValues, '|') -- specify delimiter
-- ---------------------------------------------------------
Select id1, id2, similarity_byArticle
From similarity s Join #IDs i On i.Id = s.Id
Where similarity_byArticle <> 0
Return 0
-- ***********************************************************
The below code is to create the generic function UDF that can parse any text string into a table of string values...:
Create FUNCTION [dbo].[ParseTextString] (#S Text, #delim VarChar(5))
Returns #tOut Table
(ValNum Integer Identity Primary Key,
sVal VarChar(8000))
As
Begin
Declare #dLLen TinyInt -- Length of delimiter
Declare #sWin VarChar(8000) -- Will Contain Window into text string
Declare #wLen Integer -- Length of Window
Declare #wLast TinyInt -- Boolean to indicate processing Last Window
Declare #wPos Integer -- Start Position of Window within Text String
Declare #sVal VarChar(8000) -- String Data to insert into output Table
Declare #BtchSiz Integer -- Maximum Size of Window
Set #BtchSiz = 7900 -- (Reset to smaller values to test routine)
Declare #dPos Integer -- Position within Window of next Delimiter
Declare #Strt Integer -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
If #delim is Null Set #delim = '|'
If DataLength(#S) = 0 Or
Substring(#S, 1, #BtchSiz) = #delim Return
-- ---------------------------
Select #dLLen = Len(#delim),
#Strt = 1, #wPos = 1,
#sWin = Substring(#S, 1, #BtchSiz)
Select #wLen = Len(#sWin),
#wLast = Case When Len(#sWin) = #BtchSiz
Then 0 Else 1 End,
#dPos = CharIndex(#delim, #sWin, #Strt)
-- ------------------------------------
While #Strt <= #wLen
Begin
If #dPos = 0 -- No More delimiters in window
Begin
If #wLast = 1 Set #dPos = #wLen + 1
Else
Begin
Set #wPos = #wPos + #Strt - 1
Set #sWin = Substring(#S, #wPos, #BtchSiz)
-- ----------------------------------------
Select #wLen = Len(#sWin), #Strt = 1,
#wLast = Case When Len(#sWin) = #BtchSiz
Then 0 Else 1 End,
#dPos = CharIndex(#delim, #sWin, 1)
If #dPos = 0 Set #dPos = #wLen + 1
End
End
-- -------------------------------
Set #sVal = LTrim(Substring(#sWin, #Strt, #dPos - #Strt))
Insert #tOut (sVal) Values (#sVal)
-- -------------------------------
-- Move #Strt to char after last delimiter
Set #Strt = #dPos + #dLLen
Set #dPos = CharIndex(#delim, #sWin, #Strt)
End
Return
End
First create a view
CREATE VIEW ViewArticles
AS
SELECT id1, id2, similarity_byArticle
FROM similarity
WHERE (id1 = #id or id2 = #id)
and similarity_byArticle != 0
In your code populate all the needed ids into a table.
Create a function which takes all the ids table as parameter.
CREATE FUNCTION
SelectArticles
(
#Ids TABLE
)
RETURNS TABLE
AS
RETURN
(
SELECT id1, id2, similarity_byArticle FROM ViewArticles
INNER JOIN #Ids I ON I.Id = id1
UNION
SELECT id1, id2, similarity_byArticle FROM ViewArticles
INNER JOIN #Ids I ON I.Id = id2
)