Sql Query for unknown number of keywords - c#

I want to write sql Query for unknown number of keywords. The keywords (tags) are stored in table like this
column1 column2
item1 tag1
item1 tag2
item1 tag3
. .
. .
. .
Now the user can enter any number of keywords to search against the table. if the and is used it will do strict search. if I use or it will search items that match only one keyword. I want query that dynamically shape itself and use maximum keywords given in the search if not all of them.
Like a Vehicle is the item and It has the keywords. Car, Vehicle, conveyance, Cycle, Bike, truck. Now I want to enter the keywords Bike Cycle in the textbox so it should form the query to search the vehicle item.

You can do a search with an OR operators or an equivalent IN (...) expression, group the rows by the item column, and compare row counts. The row with the highest count has the highest number of keywords from your search list:
SELECT TOP 1
column1, COUNT(*)
FROM mytable
WHERE column2 IN ('tag1', 'tag3')
GROUP BY column1
ORDER BY COUNT(*) DESC
To deal with lots of keywords without exposing your code to SQL injection you need to either generate your SQL dynamically, or use table-valued parameters.
If you take the first approach, the IN expression becomes IN (#tag0, #tag1, #tag2) up to the number of tags in your search string. Create a SQL command, and add individual tags as parameters. See this answer for more details on the dynamic query approach.
If the list of tags grows significantly, an alternative approach with a table-valued parameter could improve performance of your query. This answer explains how to do that.

Well with linq that would be something like (assuming you have a model class called Products) and the user has send an array of keywords
IQueryable<Product> SearchProducts (params string[] keywords)
{
IQueryable<Product> query = dataContext.Products;
foreach (string keyword in keywords)
{
string temp = keyword;
query = query.Where (p => p.Description.Contains (temp));
}
return query;
}
For more elaborate scenarios look at
http://www.albahari.com/nutshell/predicatebuilder.aspx

Did you want this?
SELECT * FROM TABLE
WHERE x.Keywords in (Select * FROM ListOfWantedKeywords) --The list of wanted keywords is your dynamic search.

Are you looking for this?
And version:
SELECT
SRC.*
FROM SRC
WHERE NOT EXISTS (
SELECT TOP(1)
1
FROM (
VALUES
('xxx')
, ('yyy')
) AS KEYWORDS(Word)
WHERE SRC.col NOT LIKE '%' + Word + '%'
)
Or version:
SELECT
SRC.*
FROM SRC
WHERE EXISTS (
SELECT TOP(1)
1
FROM (
VALUES
('xxx')
, ('yyy')
) AS KEYWORDS(Word)
WHERE SRC.col LIKE '%' + Word + '%'
)

Related

Use case with a variable to determine whether a count has to be returned or column list

I have a situation where on a dashboard, for pending approvals I am trying to show certain items as follows
Item 1 [Count]
Item 2 [Count]
Item 3 [Count]
The [Count] shows a numeric value of items pending approval. On click of each of these items, there is an associated table where the records are being shown.
The way of deriving these counts is very complex and I wish to avoid making duplicate queries for count for example query #1 as
SELECT COUNT(*)
FROM tableName
and then query #2 as
SELECT ColumnA, ColumnB, ColumnC
FROM tableName
Since these queries are being read into my C# application, until now I've been doing the following
var onlyCount = true;
var subQuery = onlyCount? "COUNT(*)": "ColumnA, ColumnB, ColumnC";
var query = $"SELECT {subQuery} FROM tableName";
But with an ever-growing list of columns that needs to be managed, this makes the code look ugly. With calculated data in the select list, Case(s), IIF(s) in the statement the above-said solution is no longer a "maintainable" solution. With the select query is something as demonstrated below even possible?
DECLARE #CountOnly AS BIT = 1
SELECT
CASE
WHEN #CountOnly = 1
THEN COUNT(*)
ELSE ColumnA, ColumnB, ColumnC
END
FROM
tableName
Have any one ever faced such a scenario? Or if you could point me in a direction where this can be handled better?
Side note: The above query is being passed into a SqlDataReader to fetch the data and show to the end user.
You may want to use something like this:
DECLARE #CountOnly AS BIT = 1
IF (#CountOnly = 1)
BEGIN
SELECT ColumnA, ColumnB, ColumnC
FROM MyTable
ELSE
SELECT COUNT(*)
FROM MyTable
END

How to find highest value from database matching a pattern using EF Core 2.1?

I am using Entity Framework Core 2.1 to store a table called "Equipment" in SQL. That table has a column called "Label". I have a class called Equipment with Label as string property.
That Label property sometimes can have a pattern of #[0-9][0-9][0-9][0-9][0-9] from user input.
How do I find the highest "Label" value matching the pattern above from the "Equipment" table using EF Core 2.1 fast and clean from SQL table?
var myContext = CreateDbContext();
string resultIdentifier;
// if there is any item in the equipment table
if (myContext.equipment.Any()) {
var regexStr = #"^[#]+(0-9{5})$"; //TODO: how to create this regex string correctly?
// find for any matching pattern in label column using regex
var listFound = myContext.equipment.Where(mp => Regex.IsMatch(mp.Label, regexStr)).ToList();
if (listFound.Any()) {
//TODO: how to find the maximum from the pattern?
}
else {
_logger.Trace("No highest label is found because no matched pattern is found.");
resultIdentifier = null;
}
}
else {
_logger.Trace("No highest label is found because no entry in equipment table.");
resultIdentifier = null;
}
1) How to create a correct Regex search string matching pattern #[0-9][0-9][0-9][0-9][0-9]?
2) Is Regex the best approach to look for the highest label pattern?
3) How to find the highest label pattern from SQL table using EF Core method?
Thank you
Using the regex will be inefficient. EF cannot convert the Regex.IsMatch to a corresponding SQL expression, so it will first fetch the entire equipment table, then apply the regex to each row in the table.
Instead, SQL Server has regex-like patterns and wildcards which may be used within a query using LIKE.
Consider the following:
DECLARE #foo TABLE (
label varchar(10) null
)
INSERT #foo
SELECT '#12345' -- 'Lowest'
UNION
SELECT '#99999' -- 'Highest'
UNION
SELECT '#999999' -- 'Too Many Characters
UNION
SELECT '#123' -- 'Not Enough Characters'
UNION
SELECT '#abcde1' -- 'Not the Right Characters'
UNION
SELECT '12345' -- 'No Leading #'
SELECT label
FROM #foo
WHERE label LIKE '[#][0-9][0-9][0-9][0-9][0-9]'
will return rows matching your pattern (the # character itself is a wildcard, so it is wrapped in brackets to indicate it is a literal):
label
=====
#12345
#99999
SELECT TOP 1 label
FROM #foo
WHERE label LIKE '[#][0-9][0-9][0-9][0-9][0-9]'
ORDER BY label desc
will order your results from highest to lowest:
label
=====
#99999
#12345
and adding TOP 1 will return the first row:
label
=====
#99999
There are a few ways to use this in EF Core, but the easiest is to simply use a raw SQL query:
string sql = #"SELECT TOP 1 *
FROM #foo
WHERE label LIKE '[#][0-9][0-9][0-9][0-9][0-9]'
ORDER BY label desc";
var foundRows = myContext.equipment
.FromSql(sql)
.ToList();
if (foundRows.FirstOrDefault() != null)
{
//...do something...
}

While Selecting based on ROWID - Distinct or Group By doesn't work in oracle

I have query like below , I tried to filter out duplicate columns by using Group BY
SELECT contacts.rowid AS ROW_PASS,
duty_rota.rowid AS ROW_PASS_ROTA,
duty_rota.duty_type AS DUTY_TYPE
FROM duty_rota,
duty_types,
contacts
WHERE duty_rota.duty_type = duty_types.duty_type
AND duty_rota.duty_officer = contacts.duty_id
AND sname IS NOT NULL
GROUP BY contacts.rowid,
duty_rota.rowid,
duty_rota.duty_type
ORDER BY duty_date
After playing with the query little bit I came to know we can't filter out distinct using group by while using ROWID. So can somebody please help me to write code (in SQL) with a logic that
if (any row is completely identical with another row of the query o/p)
{
then display only one column
}
I will be using the output as gridview's data source in C#, so if not in SQL - can you help me whether somehow in C# I can achieve to display only identical columns?
If you want to filter duplicate rows, you can use this query:
SELECT Max(duty_rota.rowid) AS ROW_PASS_ROTA,
duty_rota.duty_type AS DUTY_TYPE
FROM duty_rota,
duty_types,
contacts
WHERE duty_rota.duty_type = duty_types.duty_type
AND duty_rota.duty_officer = contacts.duty_id
AND sname IS NOT NULL
GROUP BY duty_rota.duty_type
ORDER BY DUTY_TYPE
Here you go: http://sqlfiddle.com/#!2/2a038/2
Take out the ROWID's. Example: If your table has 3 columns (colA, colB, colC) you could find exact row dups this way...
select a.* from
(
select count(*) dupCnt, colA, colB, colC from myTable
group by colA, colB, colC
) a
where dupCnt > 1
First, the ROWID is a unique field for each row, so using this field you will never have duplicates. The only solution here is to not use it. It's data does not hold anything you would want to display anyway.
Simply put, if you want no duplicates, you need the DISTINCT keyword:
SELECT DISTINCT field1,
field2
FROM table1,
table2
WHERE table1.key1 = table2.key1;
This will select all Field1, Field2 combinations from the two tables. Due to the DISTINCT keyword, each line will only be in the result list once. Duplicates will not be in the result list.
SELECT DISTINCT duty_rota.duty_type AS DUTY_TYPE
FROM duty_rota,
duty_types,
contacts
WHERE duty_rota.duty_type = duty_types.duty_type
AND duty_rota.duty_officer = contacts.duty_id
AND sname IS NOT NULL
ORDER BY duty_date
You will only need to GROUP BY if you need further operations on the result set, like counting the duplicates. If all you need is "no duplicates", the DISTINCT keyword is exactly what you are looking for.
Edit:
In case I misread your question and you want to see only those, that are duplicates, you need to group and you need to filter based on the groups criteria. You can do that using the HAVING clause. It's kind of an additional WHERE of the groups criteria:
SELECT FIELD1, FIELD2, COUNT(*)
FROM TABLE1, TABLE2
WHERE TABLE1.KEY1 = TABLE2.KEY1
GROUPB BY FIELD1, FIELD2
HAVING COUNT(*) > 1

special select query

I have 3 tables in my sql database like these :
Documents : (DocID, FileName) //list of all docs that were attached to items
Items : (ItemID, ...) //list of all items
DocumentRelation : (DocID, ItemID) //the relation between docs and items
In my winform application I have showed all records of Items table in a grid view and let user to select several rows of it and then if he press EditAll button another grid view should fill by file name of documents that are related to these selected items but not all of them,
Just each of documents which have relation with ALL selected items
Is there any query (sql or linq) to select these documents?
Try something like:
string query;
foreach (Item in SelectedItems)
{
query += "select DocID from DocumentRelation where ItemID =" + Item.Id;
query += "INTERSECT";
}
query -= "INTERSECT";
And exec the Query;
Take one string and keep on adding itemid comma separated in that,like 1,2,3 and then write query like
declare ItemID varchar(50);
set ItemID='1,2,3';
select FileName
from documents
Left Join DocumentRelation on Documents.DocId = DocumentRelation.DocId
where
DocumentRelation.ItemID in (select * from > dbo.SplitString(ItemID))
and then make one function in database like below
ALTER FUNCTION [dbo].[SplitString] (#OrderList varchar(1000))
RETURNS #ParsedList table (OrderID varchar(1000) )
AS BEGIN
IF #OrderList = ''
BEGIN
set #OrderList='Null'
end
DECLARE #OrderID varchar(1000), #Pos int
SET #OrderList = LTRIM(RTRIM(#OrderList))+ ','
SET #Pos = CHARINDEX(',', #OrderList, 1)
IF REPLACE(#OrderList, ',', '') <''
BEGIN
WHILE #Pos 0
BEGIN
SET #OrderID = LTRIM(RTRIM(LEFT(#OrderList, #Pos - 1)))
IF #OrderID < ''
BEGIN
INSERT INTO #ParsedList (OrderID)
VALUES (CAST(#OrderID AS varchar(1000)))
--Use Appropriate conversion
END
SET #OrderList = RIGHT(#OrderList, LEN(#OrderList) - #Pos)
SET #Pos = CHARINDEX(',', #OrderList, 1)
END
END
RETURN
END
Linq
var td =
from s in Items
join r in DocumentRelation on s.ItemID equals r.ItemID
join k in Documents on k.DocID equals r.DocID
where Coll.Contains (s.ItemID) //Here Coll is the collection of ItemID which you can store when the users click on the grid view row
select new
{
FileName=k.FileName,
DocumentID= k.DocId
};
You can loop through td collection and bind to your grid view
SQL
create a stored proc to get the relevant documents for the itemID selected from the grid view and paramterize your in clause
select k.FileName,k.DocId from Items as s inner join
DocumentRelation as r on
s.ItemID=r.ItemID and r.ItemId in (pass the above coll containing selected ItemIds as an input the SP)
inner join Documents as k
on k.DocId=r.DocIk
You can get the information on how to parametrize your sql query
Here's one approach. I'll let you figure out how you want to supply the list of items as arguments. And I also assume that (DocID, ItemID) is a primary key in the relations table. The having condition is what enforces your requirement that all select items are related to the list of documents you're seeking.
;with ItemsSelected as (
select i.ItemID
from Items as i
where i.ItemID in (<list of selected ItemIDs>)
)
select dr.DocID
from DocumentRelation as dr
where dr.ItemID in (select ItemID from ItemsSelected)
group by dr.DocID
having count(dr.ItemID) = (select count(*) from ItemsSelected);
EDIT
As far as I can tell, the accepted answer is equivalent to the solution here despite OP's comment below.
I did some quick tests with a very long series of intersect queries and confirmed that you can indeed expect that approach to become gradually slower with an increasing number of selected items. But a much worse problem was the time taken just to compile the queries. I tried this on a very fast server and found that that step took about eight seconds when roughly one hundred intersects were concatenated.
SQL Fiddle didn't let me do anywhere near as many before producing this error (and taking more than ten seconds in the process): The query processor ran out of internal resources and could not produce a query plan. This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions. Please simplify the query. If you believe you have received this message in error, contact Customer Support Services for more information.
There are several possible methods of passing a list of arguments to SQL Server. Assuming that you prefer the dynamic query solution I'd argue that this version is still better while also noting that there is a SQL Server limit on the number of values inside the in.
There are plenty of ways to have this stuff blow up.

ASP .NET SQL Query SUM, TOTAL & NEW COLUMN

I have dwelling on this for hours and still cannot find out how to do this. How do you get the total sales of an item for every year.
What I have got so far is this:
SQLCommand.CommandText = #"SELECT SUM SalesNo AS TotalYearSales FROM SalesTable WHERE Product = " + ddItems.SelectedItem + "";.
The table headings are as follows Product, Year, SalesNo.
SQLCommand.CommandText = #"SELECT DatePart( YY, SalesDate ), SUM( SalesNo ) AS TotalYearSales FROM SalesTable WHERE Product = ? GROUP BY DatePart( YY, SalesDate ) ";
SQLCommand.Parameters.Add( "parmForProduct", ddItems.SelectedItem );
As Daniel pointed out, you would be wide open to sql-injection attacks and should parameterized queries.
To get total by year, you have to add the year as a basis of the query too. I don't know the "date" column in your table the transactions are based on, so you'll have to adjust. The "?" in the query is a "place-holder" for the parameter added immediately after.. And parameters should be added in the same order as listed in the query. Some SQL engines allow "named" parameters and use something like "#ProductIWant", and your parameter would use the same...
Since you are querying for only a specific product, that doesn't need to be in the list.. unless you wanted ALL products grouped by year.
Something Like
Select Year(SalesDate),Sum(SalesNo) From SalesTable where Product = "Navel Defluffer"
Group By Year(SalesDate)
and as #Daniel alluded to, use parameterised queries not string concatenation.

Categories

Resources