SQL - Syntax Error When Trying to Increment a Value - c#

I'm using SQL Server in a C# project for a troubleshooting program and I have a table that contains ID,Question,QuestionId,Solution and Rank. I previously asked a question and it worked for selecting, so I tried modifying that for an Update instead. I'm trying to increment the rank of the solution with the highest rank by 1.
sql = "UPDATE dbo.Questions SET Rank = Rank + 1 q WHERE Rank=(SELECT MAX(Rank) FROM dbo.Questions i where i.QuestionId = q.QuestionId) AND q.QuestionId = " + questionId;
I get the following error with this:
Incorrect syntax near 'q'
Incorrect syntax near the keyword 'AND'

The table alias is in the wrong place. In SQL Server, you want a FROM clause:
UPDATE q
SET Rank = Rank + 1
FROM dbo.Questions q
WHERE Rank = (SELECT MAX(Rank)
FROM dbo.Questions i
WHERE i.QuestionId = q.QuestionId
) AND
q.QuestionId = " + questionId;
You can also write this as:
UPDATE q
SET Rank = Rank + 1
FROM (SELECT TOP (1) q.*
FROM dbo.Questions q
WHERE q.QuestionId = " + questionId
ORDER BY rank DESC
);
This makes it obvious that you only want to update one row.
Let me also add . . . learn to use query parameters. Don't put questionId directly into the query string. That is just a way to introduce syntax errors that are really hard to debug.

Related

What query is needed to determine if any of a set of strings are contained in a column that also contains multiple values?

Perhaps this was a wonky design decision on my part, but I created a table that has a column that holds from 1 to N comma-separated values, and I need to query whether any of several values are contained within those values.
To make it more clear, I'm allowing the user to select an array of movie genres (comedy, drama, etc.) and have a "movies" table where these genres are all contained in one column. For example, the genres column for the movie "The Princess Bride" contains the composite csv value "Adventure, Family, Fantasy"
So if the user selects at least one of those genres, that movie should be included in the result set that is returned based on the search criteria that they choose. But the movie should only be returned once, even if the user selected more than one of those genres to search for.
A previous incarnation of the database contained lookup tables and many-to-many tables which obviated this problem, but in an attempt to make it "easier" and more straightforward by making it one big (wide) table, I have run into this conundrum (of how to craft the query).
The query is currently dynamically built this way:
string baseQuery = "SELECT * FROM MOVIES_SINGLETABLE ";
string imdbRatingFilter = "WHERE IMDBRating >= #IMDBMinRating ";
string yearRangeFilter = "AND YearReleased BETWEEN #EarliestYear AND #LatestYear ";
string genreFilterBase = "AND GENRE IN ({0}) ";
string mpaaRatingFilterBase = "AND MPAARating IN ({0}) ";
string orderByPortion = "ORDER BY IMDBRating DESC, YearReleased DESC ";
...and the strings containing the selected criteria built like so:
if (filterGenres)
{
if (ckbxAction.Checked) genresSelected = "\'Action\',"; // changed to "#" hereafter:
if (ckbxAdventure.Checked) genresSelected = genresSelected + #"'Adventure',";
. . .
if (ckbxWar.Checked) genresSelected = genresSelected + #"'War',";
if (ckbxWestern.Checked) genresSelected = genresSelected + #"'Western',";
LastCommaIndex = genresSelected.LastIndexOf(',');
genresSelected = genresSelected.Remove(LastCommaIndex, 1)
}
// the same situation holds for mpaaRatings as for Genres:
if (filterMPAARatings)
{
if (ckbxG.Checked) mpaaRatingsSelected = #"'G',";
if (ckbxPG.Checked) mpaaRatingsSelected = mpaaRatingsSelected + #"'PG',";
if (ckbxPG13.Checked) mpaaRatingsSelected = mpaaRatingsSelected + #"'PG13',";
if (ckbxNR.Checked) mpaaRatingsSelected = mpaaRatingsSelected + #"'NR',";
LastCommaIndex = mpaaRatingsSelected.LastIndexOf(',');
mpaaRatingsSelected = mpaaRatingsSelected.Remove(LastCommaIndex, 1);
}
. . .
//string genreFilterBase = "AND GENRES IN ({0}) ";
if (filterGenres)
{
genreFilter = string.Format(genreFilterBase, genresSelected);
completeQuery = completeQuery + genreFilter;
}
//string mpaaRatingFilterBase = "AND MPAARating IN ({0}) ";
if (filterMPAARatings)
{
mpaaRatingFilter = string.Format(mpaaRatingFilterBase, mpaaRatingsSelected);
completeQuery = completeQuery + mpaaRatingFilter;
}
Is my design salvageable? IOW, can I retrieve the appropriate data given these admittedly questionable table design decisions?
UPDATE
I tested GMB's SQL by incorporating it into my SQL, but I may be doing something wrong, because it won't compile:
I don't know why those commas are there, but I reckon GMB is more of a SQL expert than I am...
Nevertheless, this does work (sans the commas and pipes):
UPDATE 2
I tried using CONTAINS, also:
SELECT * FROM MOVIES_SINGLETABLE
WHERE IMDBRating >= 7.5
AND YearReleased BETWEEN '1980' AND '2020'
AND CONTAINS (genres, 'Adventure')
OR CONTAINS (genres,'Family')
OR CONTAINS (genres, 'Fantasy')
AND CONTAINS (MPAARating, 'G')
OR CONTAINS (MPAARating, 'PG')
OR CONTAINS (MPAARating, 'PG-13')
ORDER BY IMDBRating DESC, YearReleased DESC
...but got "Cannot use a CONTAINS or FREETEXT predicate on table or indexed view 'MOVIES_SINGLETABLE' because it is not full-text indexed."
The answer by Alex Aza here [https://stackoverflow.com/questions/6003240/cannot-use-a-contains-or-freetext-predicate-on-table-or-indexed-view-because-it] gives a solution, but apparently it's not available for SQL Server Express:
...and besides, this will eventually (soon) be migrated to a SQLite table, anyway, and I doubt SQLite would support CONTAINS if doing it in SQL Server (albeit Express) requires, even if possible at all, hurtling through hoops.
UPDATE 3
I incorporated Lukasz's idea for SQLite (as that's what I'm now querying), with a query string that ended up being:
SELECT MovieTitle, MPAARating, IMDBRating, DurationInMinutes,
YearReleased, genres, actors, directors, screenwriters FROM
MOVIES_SINGLETABLE WHERE IMDBRating >= #IMDBMinRating AND
(YearReleased BETWEEN #EarliestYear AND #LatestYear) AND
(INSTR(genres, #genre1) > 0 OR INSTR(genres, #genre2) > 0) AND
(MPAARating = #mpaaRating1) ORDER BY IMDBRating DESC, YearReleased
DESC LIMIT 1000
...but still get no results.
Using SQL Server CHARINDEX:
SELECT *
FROM MOVIES_SINGLETABLE
WHERE IMDBRating >= 7.5 AND YearReleased BETWEEN '1980' AND '2020'
AND (
CHARINDEX ('Adventure',genres) > 0
OR CHARINDEX ('Family',genres) > 0
OR CHARINDEX ( 'Fantasy',genres) > 0
)
AND (
CHARINDEX ('G', MPAARating) > 0
OR CHARINDEX ('PG', MPAARating) > 0
OR CHARINDEX ('PG-13', MPAARating) > 0
)
ORDER BY IMDBRating DESC, YearReleased DESC
Or SQLite INSTR:
SELECT *
FROM MOVIES_SINGLETABLE
WHERE IMDBRating >= 7.5 AND YearReleased BETWEEN '1980' AND '2020'
AND (
INSTR (genres, 'Adventure') > 0
OR INSTR (genres,'Family') > 0
OR INSTR (genres, 'Fantasy') > 0
)
AND (
INSTR (MPAARating, 'G') > 0
OR INSTR (MPAARating, 'PG') > 0
OR INSTR (MPAARating, 'PG-13') > 0
)
ORDER BY IMDBRating DESC, YearReleased DESC;
db<>fiddle demo
Notes:
Added parentheses around OR condition
Storing data in CSV format is not the best design(column does not contain atomic value).
That's bad design indeed. Your first effort should go into fixing it: each element in the CSV list should be stored in a separate row. See: Is storing a delimited list in a database column really that bad?
One workaround would be to use a series of like conditions, like so:
and (
',' || mpaarating || ',' like '%,' || {0} || ',%'
or ',' || mpaarating || ',' like '%,' || {1} || ',%'
or ',' || mpaarating || ',' like '%,' || {2} || ',%'
)
This might be what you are looking for
DECLARE #TempTable TABLE (FilmName VARCHAR(128), GENRE VARCHAR(128))
INSERT INTO #TempTable VALUES ('Film 1', '1,2,5')
INSERT INTO #TempTable VALUES ('Film 2', '1,3,4')
INSERT INTO #TempTable VALUES ('Film 3', '6')
DECLARE #SearchGenre VARCHAR(128) = '3,4,2'
DECLARE #SearchGenreT TABLE (gens VARCHAR(8))
INSERT INTO #SearchGenreT SELECT * FROM STRING_SPLIT(#SearchGenre, ',')
SELECT * FROM #TempTable
WHERE
(
SELECT COUNT(a.value) from STRING_SPLIT(GENRE, ',') a
JOIN /*STRING_SPLIT(#SearchGenre, ',')*/ #SearchGenreT b ON a.value = b.gens
) > 0
Yes, it is possible to work within your limitations. It's not pretty, but it is functional. All of the below is within SQL Server T-SQL syntax.
Find or create a function that can split your comma-delimited values into tables
Run the genres column through your function in order to split your list of genres down into a miniature tables of individual values; we do this with CROSS APPLY
In your WHERE clause, run the user's preferences through your function to split that into individual values; see the code for more clarity.
CREATE TABLE MOVIES_SINGLETABLE (
MovieID nvarchar(10) NOT NULL,
MovieTitle nvarchar(100) NOT NULL,
Genres nvarchar(100) null
)
GO
INSERT INTO MOVIES_SINGLETABLE VALUES ('M001', 'The Princess Bride', 'Fantasy, Action, Comedy')
INSERT INTO MOVIES_SINGLETABLE VALUES ('M002', 'Die Hard', 'Action')
INSERT INTO MOVIES_SINGLETABLE VALUES ('M003', 'Elf', 'Christmas, Holiday, Comedy')
INSERT INTO MOVIES_SINGLETABLE VALUES ('M004', 'Percy Jackson and the Lightning-Thief', 'Fantasy')
go
DECLARE #genreList varchar(150) = 'Comedy, Action'
-- IN SQL 2016 onward
SELECT DISTINCT MovieTitle, Genres
FROM MOVIES_SINGLETABLE m
CROSS APPLY STRING_SPLIT(m.genres, ',') mgenres
WHERE TRIM(mgenres.value) IN (SELECT TRIM(value) FROM string_split(#genreList, ','))
-- Before 2016, using a function dbo.stringSplit we create beforehand
-- notice the syntax is nearly identical
SELECT DISTINCT MovieTitle, Genres
FROM MOVIES_SINGLETABLE m
CROSS APPLY dbo.stringSplit(m.genres, ',') mgenres
WHERE LTRIM(RTRIM((mgenres.value))) IN (SELECT LTRIM(RTRIM(value)) FROM dbo.stringSplit(#genreList, ','))
SQL Server 2016 onward has the functionality built in, and appears to exist in the Express license. However, here is the dbo.stringSplit function code I used, and the source if you want different variations of it:
/* SOURCE: https://stackoverflow.com/a/19935594/14443733 */
CREATE FUNCTION dbo.stringSplit (
#list NVARCHAR(max),
#delimiter NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN (
SELECT [Value]
FROM (
SELECT [Value] = LTRIM(RTRIM(SUBSTRING(#List, [Number], CHARINDEX(#delimiter, #List + #delimiter, [Number]) - [Number])))
FROM (
SELECT Number = ROW_NUMBER() OVER (
ORDER BY name
)
FROM sys.all_columns
) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#delimiter + #List, [Number], DATALENGTH(#delimiter) / 2) = #delimiter
) AS y
);
GO

MySQL IN clause: How can I get multiple rows when I use a same value multiple times?

I'm programming a C# Windows Forms Application in Visual Studio and I'm trying to get data about prices of products and the amount a user has added a product to its shopping list from my local MySQL-database into a List(int).
What I do is following:
If a user has added a product 4 times to their shopping list, I'm adding the barcode of the product 4 times to my List(int).
This is working but when I'm reading out all items of the List with the String.Join()-method into the IN-clause of my query and execute it, it only returns a row one time altough the IN-operator has the same barcode multiple times.
The following is how I'm adding barcodes to my List(int)
int count = 0;
List<int> barcodes = new List<int>();
MySqlCommand cmd = new MySqlCommand("SELECT product_barcode, amount FROM shopping_list_items WHERE shopping_list_id = " + current_shoppingListID + ";", db.connection);
cmd.ExecuteNonQuery();
var reader = cmd.ExecuteReader();
while (reader.Read())
{
do
{
barcodes.Add(Int32.Parse(reader["product_barcode"].ToString()));
count++;
} while (count < Int32.Parse(reader["amount"].ToString()));
}
reader.Close();
This is how I'm executing my query and assign the values to variables:
MySqlCommand cmdSum = new MySqlCommand("SELECT sum(price) AS 'total', supermarket_id FROM prices WHERE barcode IN (" + String.Join(", ", barcodes) + ") GROUP BY supermarket_id;", db.connection);
cmdSum.ExecuteNonQuery();
var readerSum = cmdSum.ExecuteReader();
while (readerSum.Read())
{
switch (double.Parse(readerSum["supermarket_id"].ToString()))
{
case 1:
sumSupermarket1 = double.Parse(readerSum["total"].ToString());
break;
case 2:
sumSupermarket2 = double.Parse(readerSum["total"].ToString());
break;
case 3:
sumSupermarket3 = double.Parse(readerSum["total"].ToString());
break;
}
}
A simplified query just to make it simple may look like this:
SELECT name FROM products WHERE barcode IN (13495, 13495, 13495);
If the above one is my query then I want it to return 3 the same rows.
So my question now is, how can I get multiple rows altough I use a same value multiple times in the IN-clause of a MySQL-query?
Q: how can I get multiple rows altough I use a same value multiple times in the IN-clause of a MySQL-query?
A: We don't. That's not how IN () works.
Note that
WHERE foo IN ('fee','fi','fi','fi')`
Is shorthand for
WHERE ( foo = 'fee'
OR foo = 'fi'
OR foo = 'fi'
OR foo = 'fi'
)
Understand what's happening here. MySQL is going to examine each row, and for each row it checks to see if this condition returns TRUE or not. If the row satisfies the condition, the row gets returned. Otherwise the row is not returned.
It doesn't matter that a row with foo value of 'fi' satisfies multiple conditions. All MySQL cares about is that the condition inside the parens ultimately evaluates to TRUE.
As an illustration, consider:
WHERE ( t.picked_by = 'peter piper'
OR t.picked_amount = 'peck'
OR t.name LIKE '%pickled%'
OR t.name LIKE '%pepper%'
)
There could be a row that satisfies every one of these conditions. But the WHERE clause is only asking if the entire condition evaluates to TRUE. If it does, return the row. If it doesn't, then exclude the row. We don't get four copies of a row because more than one of the conditions is satisfied.
So how do we get a set with multiple copies of a row?
As one possible option, we could use separate SELECT statements and combine the results with UNION ALL set operator. Something like this:
SELECT p1.name FROM product p1 WHERE p1.barcode IN (13495)
UNION ALL
SELECT p2.name FROM product p2 WHERE p2.barcode IN (13495)
UNION ALL
SELECT p3.name FROM product p3 WHERE p3.barcode IN (13495)
Note that the result from this query is significantly different than the result from the original query.
There are other query patterns that can return an equivalent set.
FOLLOWUP
Without an understanding of the use case, the specification, I'm just guessing at what we are attempting to achieve. Based on the two queries shown in the code (which follows a common pattern we see in code that is vulnerable to SQL Injection),
The shopping list:
SELECT i.product_barcode
, i.amount
FROM shopping_list_item i
WHERE i.shopping_list_id = :id
What is amount? Is that the quantity ordered? We want two cans of this, or three pounds of that? Seems like we would want to multiply the unit price by the quantity ordered to get the cost. (Two cans is going to cost twice as much as one can.)
If what we are after is the total cost of the items on the shopping list from multiple stores, we could do something like this:
SELECT SUM(p.price * s.amount) AS `total`
, p.supermarket_id
FROM ( SELECT i.product_barcode
, i.amount
FROM shopping_list_item i
WHERE i.shopping_list_id = :id
) s
JOIN price p
ON p.barcode = s.product_barcode
GROUP
BY p.supermarket_id
Note that if a particular product_barcode is not available for particular supermarket_id, that item on the list will be excluded from the total, i.e. we could get a lower total for a supermarket that doesn't have everything on our list.
For performance, we can eliminate the inline view, and write the query like this:
SELECT SUM(p.price * i.amount) AS `total`
, p.supermarket_id
FROM shopping_list_item i
JOIN price p
ON p.barcode = i.product_barcode
WHERE i.shopping_list_id = :id
GROUP
BY p.supermarket_id
If we absolutely have to rip through the shopping list query, and then use the rows from that to create a second query, we could form a query that looks something like this:
SELECT SUM(p.price * i.amount) AS `total`
, p.supermarket_id
FROM ( -- shopping_list here
SELECT '13495' AS product_barcode, '1'+0 AS amount
UNION ALL SELECT '13495', '1'+0
UNION ALL SELECT '13495', '1'+0
UNION ALL SELECT '12222', '2'+0
UNION ALL SELECT '15555', '5'+0
-- end shopping_list
) i
JOIN price p
ON p.barcode = i.product_barcode
WHERE i.shopping_list_id = :id
GROUP
BY p.supermarket_id
You would probably be better off investigating LINQ to SQL rather than using direct SQL and injection.
You can use an inline table join to accomplish what you want:
"SELECT sum(price) AS 'total', supermarket_id
FROM (select "+barcodes[0]+"as bc union all select "+String.Join(" union all select ", barcodes.Skip(1).ToArray())+") w
JOIN prices p ON p.barcode = w.bc
GROUP BY supermarket_id;"
Note: If you can name the column with the inline table alias (I couldn't test that) you could simplify the inline table generation.

Sql select statement fetch value based on user choice

I am developing application where user can search using Destination put in database. Highest priority is to "destination". Then in advanced search option, I have to give choices to user like
Gender
Type of vehicle
Ac/Non Ac
Fare
layout is like this
Operations to be done:
I have to generate listview from database based on user selection. User can select max 4 parameters at a time bt may select 1 or 2 or 3 random options. Now I am confused how to call this based on my database architecture. Any suggestion will be much more helpful as I'm stuck up on this for hours. Thanx
Edit:
Now i have created one view that gives my answer. Just one last error remaining. duplicate entries are populated based on different parameters. like user A having 2 2wheeler and 1 4wheeler is shown thrice. similarly user having 2 vehicle gets duplicate name entry. screen shot as follows:
My query-
SELECT TOP (100) PERCENT T.name, TM.source_latitude, TM.source_longitude, TM.trvl_day, TM.trvl_time, TM.dest_latitude, TM.dest_longitude, T.email, T.contactno, TM.trvl_source, TM.Trvl_vehicle,
TM.Seats_available, T.gender, TM.user_id, TM.trvl_destination, dbo.tbl_vh.type, dbo.tbl_vh.AcNonAc, dbo.tbl_vh.kmrate FROM dbo.tbl_reg1 AS T INNER JOIN
dbo.Travel_master AS TM ON T.userid = TM.user_id INNER JOIN
dbo.tbl_vh ON TM.user_id = dbo.tbl_vh.userid WHERE (TM.trvl_destination LIKE '%' + #trvl_destination + '%') AND (T.gender = ISNULL(#gender, T.gender)) AND (dbo.tbl_vh.type = ISNULL(#type, dbo.tbl_vh.type)) AND
(dbo.tbl_vh.kmrate = ISNULL(#kmrate, dbo.tbl_vh.kmrate)) AND (dbo.tbl_vh.AcNonAc = ISNULL(#AcNonAc, dbo.tbl_vh.AcNonAc)) ORDER BY TM.trvl_day
Ok now i did what exactly i wanted. Did goup by on name and day column. Finalized query as follows.
SELECT DISTINCT TOP (100) PERCENT T.name, TM.trvl_day FROM dbo.tbl_reg1 AS T INNER JOIN
dbo.Travel_master AS TM ON T.userid = TM.user_id INNER JOIN
dbo.tbl_vh ON TM.user_id = dbo.tbl_vh.userid WHERE (TM.trvl_destination LIKE '%' + #trvl_destination + '%') AND (T.gender = ISNULL(#gender, T.gender)) AND (dbo.tbl_vh.type = ISNULL(#type, dbo.tbl_vh.type)) AND
(dbo.tbl_vh.kmrate = ISNULL(#kmrate, dbo.tbl_vh.kmrate)) AND (dbo.tbl_vh.AcNonAc = ISNULL(#AcNonAc, dbo.tbl_vh.AcNonAc)) GROUP BY T.name, TM.trvl_day ORDER BY TM.trvl_day

Retrieving distinct record using join in SQL

I am running a sql query using two tables namely QuestionInsert and Question_Papers.
The columns in th erespective table are as follows:-
Table:-QuestionInsert
Columns:-QuestionNum,Question,Answer,CatId,SubCatId
Table:-Question_Papers
Columns:-QuestionNum
I want an sql query which will retrieve all QuestionNum,Question,Answer from table QuestionInsert which QuestionNum is present in table Question_Papers.
Also, I want to retrieve all QuestionNum,Question,Answer from table QuestionInsert which QuestionNum is not present in table Question_Papers.
This data is displayed on a Grid View.The queries I am using are as follows:-
The Query for first condition is:
SELECT F.QuestionNum,
F.Question,
F.Answer
FROM QuestionInsert F
INNER JOIN Question_Papers FS ON F.[QuestionNum]=FS.QuestionNum
WHERE ((F.QuestionNum=FS.QuestionNum) AND (F.CatId='" +
DropDownList1.SelectedValue + "' And F.SubCatId='" + DropDownList3.SelectedValue + "'))
ORDER BY F.QuestionNum DESC;
The other query for 2nd condition. is:-
SELECT F.QuestionNum,
F.Question,
F.Answer
FROM QuestionInsert F INNER JOIN Question_Papers FS ON F.[QuestionNum]!=FS.QuestionNum
WHERE ((F.QuestionNum!=FS.QuestionNum) AND (F.CatId='" + DropDownList1.SelectedValue + "'
And F.SubCatId='" + DropDownList3.SelectedValue + "'))
ORDER BY F.QuestionNum DESC
My code is retrieving correct information but if more than one row of same QuestionNum is present in Question_Papers table, it is displaying all the rows repeatedly. I want to display the unique rows which are present and not present in table Question_Papers separately.
Kindly help me.
You could try the following for the second condition:
SELECT F.QuestionNum,F.Question,F.Answer
FROM QuestionInsert F
WHERE (F.CatId='" + DropDownList1.SelectedValue + "' And F.SubCatId='" + DropDownList3.SelectedValue + "')
AND F.QuestionNum NOT IN (SELECT QuestionNum FROM Question_Papers)
ORDER BY F.QuestionNum DESC
And this for the first condition:
SELECT F.QuestionNum,F.Question,F.Answer
FROM QuestionInsert F
WHERE (F.CatId='" + DropDownList1.SelectedValue + "'
AND F.SubCatId='" + DropDownList3.SelectedValue + "')
AND F.QuestionNum IN (SELECT QuestionNum FROM Question_Papers)
ORDER BY F.QuestionNum DESC";
However, there are serious problems with your code - have you looked into SQL injection? There are many data access frameworks, like Entity Framework, that would push you down a better route.
Your first query can be rewritten using EXISTS
SELECT F.QuestionNum,F.Question,F.Answer FROM QuestionInsert F
WHERE EXISTS (SELECT * FROM Question_Papers P WHERE P.QuestionNum = F.QuestionNum)
AND F.CatId='" + DropDownList1.SelectedValue + "'
AND F.SubCatId='" + DropDownList3.SelectedValue + "'
Second query using NOT EXISTS
SELECT F.QuestionNum,F.Question,F.Answer FROM QuestionInsert F
WHERE NOT EXISTS (SELECT * FROM Question_Papers P WHERE P.QuestionNum = F.QuestionNum)
AND F.CatId='" + DropDownList1.SelectedValue + "'
AND F.SubCatId='" + DropDownList3.SelectedValue + "'
Please note that with the way those queries are written (which was taken from your question), you are vulnerable to SQL Injection. You should use parameters instead.
There doesn't appear to be a need to use a join nor a reason for repeating the join clause within where. Form what I can gather all you need to do is check for existence, which recent versions (2005+) of sql server supports with EXISTS. Doing this as a single query than a correlated subquery can be used to check and flag existence
DECLARE #question_insert TABLE ( id INT, question VARCHAR(50), answer VARCHAR(50), catid INT, subcatid INT )
DECLARE #question_paper TABLE ( id INT, question_insert_id INT )
INSERT INTO #question_insert ( id, question, answer, catid, subcatid )
VALUES
(1, 'How old are you?', '20', 1, 1),
(2, 'Who was the first president?', '?', 2, 1)
INSERT INTO #question_paper ( id, question_insert_id )
VALUES (1, 1),(2, 1)
SELECT
qi.id,
qi.question,
qi.answer,
CASE WHEN EXISTS(SELECT 1 FROM #question_paper qp
WHERE qp.question_insert_id = qi.id)
THEN 'Yes' ELSE 'No' END AS in_question_paper
FROM #question_insert qi
--WHERE qi.catid=#catid AND qi.subcatid=#subcatid
demo
Alternatively AS individual queries
SELECT
qi.id,
qi.question,
qi.answer,
'Yes' AS in_question_paper
FROM #question_insert qi
WHERE EXISTS(SELECT 1 FROM #question_paper qp
WHERE qp.question_insert_id = qi.id)
And
SELECT
qi.id,
qi.question,
qi.answer,
'No' AS in_question_paper
FROM #question_insert qi
WHERE NOT EXISTS(SELECT 1 FROM #question_paper qp
WHERE qp.question_insert_id = qi.id)
I will reiterate that you should read up on SQL Injection and not concatenate user input into queries.
Also re. DISTINCT not being "acceptable in joins" that is not the case. What is not acceptable is to use DISTINCT and refer to a column that is not part of select list in another part of the query (in this case it would've been the WHERE clause), a way round this is to use GROUP BY instead.

SQLException at Inner Join

I am using a C# program that calls a SQL statement that is executed on an instance of SQL Server 2008 R2. Here is the SQL Call:
SELECT TOP 1 as1.AssetTagID, as1.TagID, as1.CategoryID, as1.Description,
as1.HomeLocationID, as1.ParentAssetTagID
FROM Assets AS as1 ORDER BY ar.DateScanned DESC
INNER JOIN AssetsReads AS ar
ON as1.AssetTagID = ar.AssetTagID
WHERE (ar.ReadPointLocationID='Readpoint1' OR ar.ReadPointLocationID='Readpoint2')
AND as1.TagID!='000000000000000000000000';
I am getting an SQLException around INNER. The exception text is the following:
System.Data.SqlClient.SqlException: Incorrect syntax near the keyword 'INNER'.
I can put the stack trace on here as well, but I felt like it would clutter the question. Here is the actual string in the C# code that I am using with the call:
"SELECT TOP 2 as1.AssetTagID, as1.TagID, " +
"as1.CategoryID, as1.Description, as1.HomeLocationID," +
"as1.ParentAssetTagID FROM Assets AS as1 ORDER BY ar.DateScanned DESC\n" +
"INNER JOIN AssetsReads AS ar ON as1.AssetTagID = ar.AssetTagID\n" +
"WHERE (ar.ReadPointLocationID='" + IntegrationService.Lane2Zones[0] +
"' OR ar.ReadPointLocationID='" + IntegrationService.Lane2Zones[1] + "')\n" +
"AND as1.TagID!='000000000000000000000000';"
Your ORDER BY statement can't be there. Move it to the end
I'll also give the obligatory "Don't do this" speech. Concatenating SQL strings like this opens you up to SQL injection attacks. There is plenty of information about that on SO and Google so I won't go into it, but you should definitely consider making this a parameterized query.
Like he said... your order by clause was out of order :)
SELECT TOP 1 as1.AssetTagID,
as1.TagID,
as1.CategoryID,
as1.Description,
as1.HomeLocationID,
as1.ParentAssetTagID
FROM Assets AS as1
INNER JOIN AssetsReads AS ar
ON as1.AssetTagID = ar.AssetTagID
WHERE ( ar.ReadPointLocationID = 'Readpoint1'
OR ar.ReadPointLocationID = 'Readpoint2' )
AND as1.TagID != '000000000000000000000000'
ORDER BY ar.DateScanned DESC;
I'll also note that using schema qualified objects is recommended by Microsoft (http://technet.microsoft.com/en-us/library/ms190387(v=sql.105).aspx). Also you should use parenthesis around your top (value) statement.
SELECT TOP (1) [as1].[AssetTagID],
[as1].[TagID],
[as1].[CategoryID],
[as1].[Description],
[as1].[HomeLocationID],
[as1].[ParentAssetTagID]
FROM [<schema>].[Assets] AS [as1]
INNER JOIN [<schema>].[AssetsReads] AS [ar]
ON [as1].AssetTagID = [ar].[AssetTagID]
WHERE ( [ar].[ReadPointLocationID] = 'Readpoint1'
OR [ar].[ReadPointLocationID] = 'Readpoint2' )
AND cast([as1].TagID AS [INT]) != 0
ORDER BY [ar].[DateScanned] DESC;

Categories