I have a List of UserID's and a open connection to SQL Server. How can I loop through this List and Select matching UserID with First_Name and Last_Name columns? I assume the output can be in a datatable?
many thanks
It varies slightly depending on which type of SQL you're running, but this and this should get you started.
The most expedient way of doing this would be to:
Turn the List into a string containing a comma separated list of the userid values
Supply that CSV string into an IN clause, like:
SELECT u.first_name,
u.last_name
FROM USER_TABLE u
WHERE u.userid IN ([comma separated list of userids])
Otherwise, you could insert the values into a temp table and join to the users table:
SELECT u.first_name,
u.last_name
FROM USER_TABLE u
JOIN #userlist ul ON ul.userid = u.userid
Write a function in your SQL database named ParseIntegerArray. This should convert a comma delimited string into a table of IDs, you can then join to this in your query. This also helps to avoid any SQL injection risk you could get from concatenating strings to build SQL. You can also use this function when working with LINQ to SQL or LINQ to Entities.
DECLARE #itemIds nvarchar(max)
SET itemIds = '1,2,3'
SELECT
i.*
FROM
dbo.Item AS i
INNER JOIN dbo.ParseIntegerArray(#itemIds) AS id ON i.ItemId = id.Id
This article should help you: http://msdn.microsoft.com/en-us/library/aa496058%28SQL.80%29.aspx
I've used this in the past to create a stored procedure accepting a single comma delimited varchar parameter.
My source from the C# program was a checked list box, and I built the comma delimited string using a foreach loop and a StringBuilder to do the concatenation. There might be better methods, depending on the number of items you have in your list though.
To come back to the SQL part, the fn_Split function discussed in the article, enables you to transform the comma delimited string back to a table variable that SQL Server can understand... and which you can query in your stored procedure.
Here is an example:
CREATE PROCEDURE GetSelectedItems
(
#SelectedItemsID Varchar(MAX) -- comma-separated string containing the items to select
)
AS
SELECT * FROM Items
WHERE ItemID IN (SELECT Value FROM dbo.fn_Split(#SelectedItemsIDs,','))
RETURN
GO
Note that you could also use an inner join, instead of the IN() if you prefer.
If you don't have the fn_Split UDF on your SQL Server, you can find it here: http://odetocode.com/Articles/365.aspx
I hope this helps.
Related
I may have a slightly naive question, but I have never worked with databases before. I am a .NET engineer and I use Dapper to access the SQL Server database.
The situation is the following: I have a denormalized table for persisting several types of entities. Each has a composite key (type, id, owner_id) and each row of the key is of string type (but it's not important). And, let's say, I'm writing to the database many interests for different users (Bulk post). In order for them to not repeat, I need to make a query and determine which are already present in the database.
So, I have this code in my InterestService class:
private IEnumerable<Interest> GetAlreadyExistingInterestsFor(IEnumerable<Interest> interestsForCreating) =>
_interestRepository.GetInterests(interestsForCreating.Select(interest => interest.Id).ToList(),
interestsForCreating.Select(interest => interest.UserId).ToList());
After that I have some logic and so on. It's not important.
InterestRepository method GetInterests looks like this:
public GetInterests(IList<string> interestIds, IList<string> userIds)
{
var query = #"SELECT type, id, owner_id
FROM entities
WHERE type = 'interest'
AND id IN #InterestIds
AND owner_id IN #UserIds";
return _dbContext.ExecuteQuery(query, new { InterestIds = interestIds, UserIds = userIds });
}
The code may have mistakes because right now I don't have an ability to access a working environment but I think the idea is clear. So, the question is whether this is the best approach to making a query. And if there is a better, then what is it.
Essentially you can simply do exactly what was done in this post, but with two sets instead of 1 for the table valued parameter.
Using Dapper, how do I pass in the values for a sql type as param?
It uses a stored procedure and a sql table valued parameter.
If stored procedure is not an option then you can use one of following methods.
Convert your interestIds and userIds into strings
string interests = "(1, 2, 3, 4)" if lists contains numbers only or ('a1', 'b1', 'c2') if they are strings.
Then just inline them into your query
var query = #"SELECT type, id, owner_id
FROM entities
WHERE type = 'interest'
AND id IN "+ interests
+ " AND owner_id IN " + users;
This method is considered as bad practice and is an invitation to SQL injection attack (in the case of user input). Use it iff you are absolutely sure in your data.
SQL Server 2016+ has a built-in function string_split which can be used here. The function splits a string with separators into a table t(value).
Again convert lists into strings string interests="a1, a2, b3, c4"; (No single quot here)
and query
var query = #"SELECT type, id, owner_id
FROM entities
WHERE type = 'interest'
AND id IN (select value from string_split(#interests,','))
AND owner_id IN (select value from string_split(#users,','))";
For earlier versions you can create UDF with the same functionality.
create function dbo.split_string(#input varchar(max), #separator varchar(2))
returns table as
return
(with cte as(
select cast('<v>'+ REPLACE(#input, #separator, '</v><v>') +'</v>' as xml) x
)
select t.v.value('.[1]', 'varchar(max)') [value]
from cte
cross apply x.nodes('v') t(v))
I have a list of Id(string).
I want to delete records from like this:
DELETE FROM MyTable WHERE ID=:Id
But, I don't want to to call the DB repeatedly; instead want to pass the Id collection as comma separated string and execute the above query on the Oracle server at one shot!
Any help will be highly appreciated.
Thanks in advance.
With a comma-separated list you can use a function like this to split your IDs:
select regexp_substr(:id,'[^,]+',1,level) element
from mytable
connect by level <= length(regexp_replace(:id,'[^,]+')) + 1
So the final query :
DELETE FROM MyTable WHERE ID in ( select regexp_substr(:id,'[^,]+',1,level) element
from mytable
connect by level <= length(regexp_replace(:id,'[^,]+')) + 1)
I have 3 tables (for example 3, but in real over than 30 tables with this conditions) in my SQL Server database: post, user, person.
post: (post_id, post_text, user_id)
user: (user_id, user_name, person_id)
person: (person_id, person_phone, person_email)
Now, in C#, I want an algorithm that creates a query that get result like this:
post.post_id, post.post_text, post.user_id, user.user_id, user.user_name, user.person_id, person.person_id, person.person_email
and I use this method for fill a SqlDataReader in C# for reading and accessing all column values from these records.
I know that the common way to get that result directly and manually using of 'Join' statement, but it is waste time if tables count is very much. So, I want an algorithm that generates this query for manipulate in C# programming.
Thanks a lot.
To query all column names from your tables, you can use:
SELECT obj.Name + '.' + col.Name AS name
FROM sys.columns col
INNER JOIN sys.objects obj
ON obj.object_id = col.object_id
WHERE obj.Name IN ('post', 'user', 'person')
ORDER BY name
Then, for how to call this from C# using SqlDataReader, you can use this documentation: https://msdn.microsoft.com/en-us/library/haa3afyz(v=vs.110).aspx
select post1.post_id, post1.post_text, post1.user_id, user1.user_id, user1.user_name, user1.person_id, person1.person_id, person1.person_email from post post1 inner join user user1 on user1.user_id=post1.user_id inner join person person1 on person1.person_id=user1.person_id
I've the following tables:
"procedures":
code: bigint, primary key, auto-increment
caption: varchar(max), not-null
"worklist":
code: bigint, primary key, auto-increment
title: varchar(max), not-null
procedures: varchar(max), not-null, comma-separated string of procedure-code
...
I'm using Linq-to-SQL to query the table "worklist" with column "procedures" to be translated to comma-separated string of procedure-caption.
e.g. the sub-query for "worklist"."procedures":
procedures = string.Join(",", (
from pc in w.procedures.Split(',').Select(cs => long.Parse(cs)).ToList()
join ps in db.procedures.AsEnumerable() on pc equals ps.code
select ps.caption
).ToArray()),
However, I'm facing the Exception of "Split() has no supported translation in SQL".
Please kindly advise how to do that.
Many Thanks!
you will not be able to solve the query as such. You need to fetch your raw data to c# and afterwards process the split.
This is because in your current code you are asking sql server to do the split and it has no implementation for that. Hence the error...
The following will pass in each item in procList as a separate sql parameter so that it can put it in a sql IN clause. If there's not too many of them, this should work fine.
var procList = w.procedures.Split(',').Select(cs => long.Parse(cs)).ToList();
var procedures = (from ps in db.procedures.AsEnumerable()
where procList.Contains(ps.Code)
select ps.Caption).ToArray()
In the query you have shown, you will have to first fetch the records from db using linq query like:
var abc = w.procedures.ToList();
or
var abc = w.procedures.ToList().Select(cs => cs).Split(...);
now on abc you can use any of your string functions.. Because once you do ToList it will fetch the records from database.
Hope this Helps..
I have an arraylist that holds a subset of names found in my database. I need to write a query to get a count of the people in the arraylist for certain sections i.e.
There is a field "City" in my database from the people in the arraylist of names I want to know how many of them live in Chicago, how many live in New York etc.
Can someone help me how I might set up an sql statement to handle this. I think somehow I have to pass the subset of names to sql somehow.
Here is a sample how I am writing my sql in my code
Public Shared Function GetCAData(ByVal employeeName As String) As DataTable
Dim strQuery As String = "SELECT EMPLID, EMPLNME, DISP_TYPE, BEGIN_DTE FROM Corr WHERE (EMPLNME = #name)"
Dim cmd As New SqlCommand(strQuery)
cmd.Parameters.Add("#name", SqlDbType.VarChar)
cmd.Parameters("#name").Value = employeeName
Dim dt As DataTable = GenericDataAccess.GetData(cmd)
Return dt
End Function
I need a way to create a function using your sql statement to return a datatable object of names and the parameters would be city, and the List of names.
The above example isnt the sql I am looking for its just a skeleton of what the function would look like that I want to create.
So then you would use the function by iterating through all the cities passing in the same set of names each time in the fron end.
Your Sql statement will need to look like:
select city, count(*)
from table
where city in ('Chicago', 'New York')
group by city
Where the list of cities is your arraylist. You could pas this into a stored procedure as a variable or you could build the Sql string dynamically within your code.
If you are using SQL Server 2008, you can create a stored proc with a table variable as the input parameter. Then inside the proc you can just join to the input parameter table to get what you want.
You could use table valued parameters, but that's SQL Server 2008 and up only.
If the number of names is limited and you need to support older versions of SQL Server, you can use multiple parameters. That way you're still safe from SQL injection. I'm not entirely sure on what query you want to do, so I'll give an example based on your code:
Public Shared Function GetCAData(ByVal employeeName() As String) As DataTable
Dim sql As StringBuilder = New StringBuilder()
sql.Append("SELECT EMPLID, EMPLNME, DISP_TYPE, BEGIN_DTE FROM Corr WHERE EMPLNME IN (")
Dim cmd As New SqlCommand()
For I As Integer = 0 To employeeName.Length - 1
sql.Append("#name").Append(I).Append(",")
cmd.Parameters.AddWithValue("#name" & I, employeeName(I))
Next
sql.Remove(sql.Length - 1, 1).Append(")")
cmd.CommandText = sql.ToString()
Return GenericDataAccess.GetData(cmd)
End Function
(I'm sorry if my VB looks a little odd, I never use it anymore)
It actually builds a SQL statement dynamically, but the "dynamic" part is just a bunch of generated parameter names, which you then set. The maximum number of allowed parameters is 2000-something.
The best approach would depend on how big the subset of names is.
The very simplest, and probably worst approach would be to just create some dynamic sql like this..
"select city,count(*) from table where user in ("name1","name2","name3") GROUP BY city"
Where the in section is generated from the arraylist, perhaps like the following
private string CreateSQL(List<string> names)
{
var sb = new StringBuilder();
sb.Append("select city,count(*) from table where user in ('");
foreach ( var name in name)
{
sb.Append("'");
sb.Append(name);
sb.Append("',");
}
sb.Remove(sb.Length-1,1); //remove trailing ,
sb.Append(") GROUP BY city");
return sb.ToString();
}
The where in clause has a 100 item limit I believe. If your subset is anything like that length, then you really need a better approach, like putting the users in another table and doing a join. In fact if the subset of users is taken from the same database by another query, then post that and we'll right a single query.
edit: I actually don't know how to do where user in type queries as parameterised commands, be wary of sql injection !
One way of doing this is to pass it in as an XML parameter. The XML would be something like
#Cities = '<xml><city>Chicago</city><city>New York</city></xml>'
And this can be selected against as a table. This would have a slightly different behaviour than Macros's in that it will return rows for cities where the population is zero.
SELECT
tempTable.item.value('.', 'varchar(50)') AS City,
COUNT(DISTINCT people) AS [Population]
FROM #Cities.nodes('//city') tempTable(item)
LEFT OUTER JOIN peopleTable
ON tempTable.item.value('.', 'varchar(50)') = peopleTable.City
The above is the first time I have attempted that approach though so I'd be glad to have it critiqued!
My habitual approach is to pass it in as a comma delimited list and use a split function to get it into table format that can be joined against. An example split function is here